Async Ruby는 Ruby의 다른 동시성 모델인 다중 프로세스, Ractors, 그리고 스레드와는 차별화됩니다. 특히, 전역 인터프리터 락(GIL)의 제약을 받는 스레드와 달리, Async Ruby는 ‘파이버(Fibers)’를 핵심 프리미티브로 사용하여 경량 스레드와 유사하게 작동합니다. 파이버는 시스템 호출 없이 생성 및 전환이 가능하여 오버헤드가 매우 낮고, 수만에서 수백만 개의 동시 작업을 처리할 수 있습니다. 이는 스레드가 수십 개 단위에서 한계를 보이는 것과 대조적입니다.
Async Ruby는 Active Record의 load_async
나 Sidekiq의 perform_async
와 같이 이름에 ‘async’가 포함되어 있지만 내부적으로 스레드를 사용하는 기능들과는 다릅니다. Async Ruby의 주요 구성 요소로는 사용자 친화적인 async
젬, 저수준 C 확장으로 구현된 io-event
젬, 그리고 Ruby 3.0에 추가된 Fiber Scheduler
인터페이스가 있습니다. Fiber Scheduler
는 블로킹 I/O 작업(예: HTTP 요청 대기) 시 다른 파이버로 전환하여 유휴 시간을 줄이는 역할을 합니다. 이러한 설계 덕분에 Async Ruby는 JavaScript의 async/await
와 달리, 동기 코드와 동일한 방식으로 비동기 코드를 작성할 수 있는 ‘colorless’ 패러다임을 제공하여 코드의 가독성과 유지보수성을 높입니다.
강연에서는 Async Ruby의 실제 활용 예시를 통해 그 강력함을 보여줍니다. Async
블록은 비동기 환경을 설정하고 Fiber Scheduler
를 활성화하며, Async::Task
는 파이버를 감싸는 래퍼로 여러 작업을 동시에 실행할 수 있게 합니다. Async::Barrier
는 여러 비동기 작업을 그룹화하고, 특정 조건(예: 가장 빠른 응답)이 충족되면 나머지 작업을 중단하는 데 사용될 수 있습니다. Async::Semaphore
는 동시 실행되는 작업의 수를 제한하여 API 호출 속도 제한과 같은 시나리오에 유용합니다.
확장성 측면에서, Async Ruby는 수천 개의 HTTP 요청, Redis 작업, PostgreSQL 쿼리, 시스템 프로세스 실행 등을 동시에 처리하는 시뮬레이션을 통해 그 효율성을 입증합니다. 특히 async-http
와 같은 최적화된 클라이언트를 사용하면 수천 개의 HTTP/2 요청을 단일 TCP 연결로 처리하여 오버헤드를 극적으로 줄일 수 있습니다.
스레드와 Async Ruby의 비교는 다음과 같습니다: * 기본 단위: 스레드는 운영체제 스레드, Async Ruby는 파이버. * 오버헤드: 스레드는 상대적으로 높고, 파이버는 시스템 호출이 없어 매우 낮음. * 동시성: 스레드는 수십 개 단위가 한계, 파이버는 수만에서 수백만 개 가능. * 스케줄링: 스레드는 선점형(임의 전환), 파이버는 협력형(I/O 대기 시에만 전환). * 제어: 스레드는 제어하기 어렵고 버그 발생 가능성 높음, 파이버는 세밀한 제어 및 관리가 용이. * 적합성: 스레드는 CPU 바운드 작업에 유리, Async Ruby는 I/O 바운드 작업에 압도적으로 유리합니다.
따라서 일반적인 Rails 애플리케이션에서 뷰 렌더링과 같은 CPU 바운드 작업이 많다면 Puma와 같은 스레드 기반 웹 서버가 유리할 수 있지만, API 전용 애플리케이션이나 네트워크 I/O가 많은 경우 Falcon과 같은 Async Ruby 기반 웹 서버가 이상적입니다.