Ruby 웹 서버를 이해하기 위해서는 Global VM Lock(GVL)의 개념을 파악하는 것이 필수적입니다. GVL은 단일 프로세스 내에서 한 번에 하나의 스레드만 Ruby 코드를 실행할 수 있도록 보장합니다. 이는 병렬 처리를 위해 여러 Rails 프로세스가 필요함을 의미하며, 초기 Mongrel과 같은 서버의 한계를 극복하기 위해 Unicorn이 대중화한 pre-forking 모델이 등장했습니다. 이 모델은 마스터 프로세스가 애플리케이션을 부팅한 후 가볍고 빠르게 시작되는 워커 프로세스를 포크하여 중앙 집중식 관리와 Copy-on-Write(COW)를 통한 메모리 효율성을 제공합니다.
Puma는 이 pre-forking 모델에 스레드라는 또 다른 동시성 계층을 추가한 하이브리드(다중 프로세스, 다중 스레드) 아키텍처입니다. 이는 다양한 시나리오에서 잘 작동하지만, 두 가지 극단적인 상황에서 도전에 직면합니다. 첫째, IO 바운드 워크로드에서 스레드가 데이터베이스 쿼리나 API 호출 등 IO 작업 대기 중 블로킹되면 시스템 용량이 감소합니다. 둘째, GVL 경합은 스레드 수가 증가할수록 비결정적인 지연 시간을 유발하며, 이는 실제 서비스에서 Rails 기본 스레드 수 감소나 단일 스레드 Puma 사용과 같은 아키텍처적 결정으로 이어졌습니다.
이러한 Puma의 한계를 해결하기 위해 Falcon과 Pitchfork가 등장합니다. Falcon은 스레드 대신 파이버와 이벤트 루프를 기반으로 하는 완전히 다른 아키텍처를 채택합니다. 이는 IO 바운드 작업 시 파이버가 제어를 양보하여 워커 프로세스가 블로킹되지 않고 수천 개의 동시 IO 작업을 처리할 수 있게 합니다. 하지만 파이버는 협력적(cooperative) 스케줄링 방식으로, 하나의 장기 실행 CPU 바운드 파이버가 이벤트 루프를 블로킹하여 다른 파이버들을 굶게 만들 수 있으므로, IO 집약적 워크로드에 매우 효율적이지만 CPU 집약적 워크로드에는 적합하지 않습니다.
Pitchfork는 Unicorn의 직계 후손으로, ‘프로세스당 하나의 요청’ 모델을 완전히 수용하여 진정한 GVL 없는 병렬 처리와 뛰어난 복원력을 제공합니다. Pitchfork의 핵심 혁신은 ‘reforokking’입니다. 이는 워커가 완전히 웜업된 후, 해당 워커를 새로운 ‘mold’로 승격시켜 새로운, 이미 웜업된 워커들을 포크하는 방식입니다. 이를 통해 JIT 캐시 및 VM 최적화와 같이 일반적으로 사설(private)이던 메모리를 모든 워커가 공유하게 되어, 상당한 메모리 효율성을 달성하고 모든 새 워커가 완벽하게 사전 웜업된 사본이므로 낮고 일관된 지연 시간을 제공합니다. Shopify의 배포 사례에서는 메모리 사용량 30% 감소, P99 지연 시간 9% 감소라는 극적인 개선을 보여주었습니다. Pitchfork는 높은 복원력과 간단한 성능 디버깅 등의 운영적 이점도 제공하지만, 애플리케이션이 fork-safe해야 하며 일부 gem과의 비호환성이 있을 수 있습니다.
결론적으로, 각 서버는 고유한 강점과 트레이드오프를 가집니다. Puma는 범용 동시성을 제공하지만 GVL 경합과 IO 블로킹이 단점입니다. Falcon은 높은 IO 처리량을 제공하지만 협력적 스케줄러로 인해 CPU 집약적 작업에는 부적합합니다. Pitchfork는 가장 낮은 지연 시간과 메모리 사용량을 목표로 하지만, 혼합 워크로드에서의 잠재적 처리량 감소와 fork-safety 확보를 위한 엔지니어링 부담이 따릅니다.