문제 진단: 유니콘의 요청 처리 불균형
-
성능 저하의 원인: APM 추적 결과, 데이터베이스나 외부 API 호출이 아닌 루비 애플리케이션 자체의 처리 시간(Ruby processing time)이 트래픽 스파이크 시 급증하는 것으로 나타났습니다. 이는 인프라나 외부 의존성 문제가 아닌, 애플리케이션 내부의 ‘콜드 워커(Cold Worker)’ 현상 때문이었습니다.
-
콜드 워커 현상: 대규모 루비 애플리케이션은 부팅 후 데이터베이스 연결, 인메모리 캐시 구축, JIT 컴파일 등 초기화 작업이 많습니다. 이러한 초기화 작업을 거치지 않은 워커(콜드 워커)가 첫 요청을 처리할 때 현저히 느려지는 문제가 발생했습니다.
-
유니콘(Unicorn)의 LIFO 특성: 기존에 사용하던 Unicorn 웹 서버는 Linux의 epoll을 사용하여 LIFO(Last-In, First-Out) 방식으로 요청을 처리합니다. 이로 인해 미리 스케일 아웃하여 충분한 워커를 확보했음에도 불구하고, 일부 워커만 계속 활성화되고 대다수 워커들은 ‘콜드’ 상태로 유지되는 문제가 발생했습니다.
-
자동 스케일링의 한계: AWS 오토스케일링 그룹은 새 워커를 시작하고 헬스 체크를 통과하기까지 최소 2분 이상 소요됩니다. 그러나 예약 판매 트래픽은 1분 이내에 정점에 도달하고 5분 이내에 해소되므로, 오토스케일링은 이러한 급작스러운 스파이크에 대응하기에 너무 느렸습니다.
해결책: Pichfork의 Refork 기능 도입
-
Puma 전환의 어려움: 스레드 기반의 Puma로 전환하는 것은 12년 된 대규모 Ruby on Rails 애플리케이션 전체의 스레드 안전성을 보장해야 하므로 매우 복잡하고 비용이 많이 드는 작업이었습니다.
-
Pichfork와 Refork: Unicorn의 포크 버전인 Pichfork는 refork라는 혁신적인 기능을 제공합니다. 이 기능은 이미 충분히 많은 요청을 처리하여 ‘워밍업’된 워커를 템플릿으로 사용합니다. 이 템플릿 워커를 기반으로 다른 기존 워커들을 종료하고 새로운 워커들을 포크하여 생성합니다.
-
Refork의 장점: refork를 통해 모든 워커는 디컴파일된 바이트코드, 초기화된 캐시 등 ‘웜업’된 상태를 상속받게 됩니다. 이로써 모든 워커가 처음부터 빠른 속도로 요청을 처리할 수 있게 되어 콜드 워커 문제를 근본적으로 해결합니다.
-
설정 및 주의사항:
refork_after설정은 refork가 트리거되는 요청 수를 지정합니다. 트래픽이 적은 스케일 업 기간 동안 빠르게 refork가 발생하도록 작은 값으로 설정하는 것이 중요합니다. 또한, Pichfork는worker_killer가 없으므로after_cast_complete콜백을 사용하여 직접 메모리 관리 기능을 구현해야 합니다. 포킹 과정에서 파일 디스크립터 상속이나 백그라운드 스레드 안전성 등 주의해야 할 사항이 있습니다.