본 아티클은 웹 크롤링과 같이 I/O 작업이 많은 시나리오에서 Ruby가 다른 언어들(Go, JavaScript, Elixir 등)에 비해 불리하다는 인식을 전환하고자 합니다. 특히 Ruby의 Async 라이브러리를 활용하면 가독성과 표현력을 유지하면서도 효율적인 비동기 및 동시 처리가 가능함을 논증합니다. 먼저 동시성 없이 순차적으로 동작하는 기본적인 웹 크롤러를 구현하고, 이후 Async 라이브러리를 적용하여 점진적으로 성능과 안정성을 개선하는 과정을 상세히 설명합니다.
웹 크롤링의 기본 원리는 시드(seed) URL부터 시작하여 페이지를 다운로드하고 HTML을 파싱하여 링크를 추출한 뒤, 추출된 링크에 대해 동일한 과정을 반복하는 것입니다. 순차적 크롤러는 각 페이지를 하나씩 처리하므로 속도가 느리며, 특히 매 요청마다 발생하는 TLS 핸드셰이크가 성능 저하의 주된 원인입니다. 이를 개선하기 위해 Ruby의 Fiber와 Async 라이브러리를 활용합니다. Async는 Fiber 스케줄러와 비동기 작업 정의를 위한 DSL을 제공합니다. Sync
블록은 스케줄러와 이벤트 루프를 설정하고, Async
블록은 비동기 작업을 생성합니다. 이를 통해 fetch
함수 호출을 비동기 Task로 만들어 동시에 실행할 수 있습니다. 그러나 이 방식은 Task가 기하급수적으로 생성되어 서버에 과부하를 줄 수 있습니다. 이를 해결하기 위해 Async::Semaphore
를 사용하여 동시에 실행되는 다운로드 Task의 수를 제한합니다. 또한, 매번 새로운 연결을 맺는 비효율성을 개선하고자 Async::HTTP::Client
를 도입하여 지속적인 HTTP 연결을 관리하고 TLS 핸드셰이크 오버헤드를 줄여 성능을 극대화합니다. 마지막으로, 크롤링 중 예외 발생 시 모든 Task가 안전하게 종료되도록 Async::Barrier
를 사용하여 Task들을 관리하는 방법을 제시합니다. Barrier는 Semaphore와 함께 사용하여 안정적인 동시성 제어를 가능하게 합니다. 아티클은 또한 robots.txt 존중, User-Agent 명시, 동시 요청 제한 등 웹 크롤러 제작 시 지켜야 할 ‘예의’에 대해서도 강조합니다.
결론적으로, Ruby는 Async 라이브러리와 함께 사용될 때 I/O 바운드 워크로드를 효과적으로 처리할 수 있는 강력한 언어임이 입증되었습니다. Async는 콜백 지옥이나 Async/Await 패턴의 복잡성 없이도 Ruby의 장점인 깔끔하고 표현력 있는 코드를 유지하면서 동시성을 구현할 수 있도록 지원합니다. 따라서 이벤트 기반 비동기 I/O가 필요한 경우에도 Ruby를 충분히 고려할 가치가 있음을 시사합니다.