Puma 7으로 업그레이드하여 공정하게 스케줄링된 Keep-Alive의 성능을 잠금 해제하세요

Puma 7 Fair Scheduled Keep-alive Enhancements | Heroku

작성자
발행일
2025년 11월 05일

핵심 요약

  • 1 Puma 7은 기존 Puma 6 및 이전 버전에서 발생했던 Keep-Alive 연결 요청 처리 불공정 버그를 해결하여 애플리케이션 지연 시간을 대폭 개선했습니다.
  • 2 이 버그는 파이프라인 최적화 코드가 Keep-Alive 요청에도 재사용되면서 특정 Keep-Alive 연결이 스레드를 독점하여 다른 요청의 처리를 지연시키는 문제였습니다.
  • 3 Puma 7은 요청 처리 순서의 공정성을 확보하고, 작업자 간 부하를 균등하게 분배하는 새로운 전략을 도입하여 안정적인 성능을 제공합니다.

도입

Puma 7이 출시되면서 Ruby 애플리케이션의 Keep-Alive 연결 처리 방식에 중요한 개선이 이루어졌습니다. 수년간 Puma에 존재했던 이 버그는 특정 Keep-Alive 요청이 다른 요청보다 우선 처리되어 전체 요청의 약 10%가 최대 10배 이상 지연되는 문제를 야기했습니다. 이 글은 웹 서버의 작동 방식, Puma에서 이러한 불공정한 동작이 발생한 원인, 그리고 MSP-Greg이 제안한 아키텍처 변경을 통해 Puma 7에서 어떻게 해결되었는지 상세히 설명합니다. 이 버그는 특히 서버가 과부하 상태일 때 더욱 두드러져 애플리케이션의 전반적인 성능과 사용자 경험에 부정적인 영향을 미쳤습니다.

Puma 6 Keep-Alive 버그의 원인

Puma 6 및 이전 버전에서는 스레드가 모두 사용 중일 때 새로운 Keep-Alive 요청이 “새치기”하는 문제가 있었습니다. 이는 동일한 Keep-Alive 연결에서 다음 요청(예: C, D)이 처리 대기 중인 다른 요청(예: B)보다 먼저 처리되는 현상을 초래했습니다. 이로 인해 특정 Keep-Alive 연결이 Puma 스레드를 독점하여 다른 요청의 지연을 야기했습니다. 이러한 동작은 특히 Puma가 스레드 수보다 많은 요청으로 “과부하” 상태일 때 발생했습니다.

“Fast Inline Optimization”의 부작용

이러한 새치기 현상은 의도된 것이 아니라, 파이프라인 연결을 위한 “Fast Inline Optimization”의 부산물이었습니다. 이 최적화는 뮤텍스 처리와 같은 오버헤드를 줄이기 위해 파이프라인 요청을 동일한 스레드에서 순서대로 처리하도록 하는 루프를 추가했습니다. 문제는 이 코드 경로가 Keep-Alive 연결에도 재사용되면서, 나중에 도착한 Keep-Alive 요청까지 마치 동시에 도착한 파이프라인 요청처럼 처리하여 불공정한 순서를 만들었다는 점입니다. max_fast_inline 설정으로 최대 10개의 요청으로 제한되었지만, 본질적인 문제는 해결되지 않았습니다.

Puma 7의 해결책: 공정한 스케줄링 및 부하 분산

Puma 7은 이 문제를 해결하기 위해 두 가지 주요 목표를 설정했습니다.

  1. 요청 처리 순서의 공정성: 소켓의 accept 호출을 통해 들어온 요청이든, 기존 Keep-Alive 연결을 통해 들어온 요청이든, 수신된 순서대로 처리되어야 합니다.

  2. 부하의 균등 분배: 서버 내 모든 Puma 프로세스(워커)에 요청 부하가 균등하게 분배되어야 합니다. Puma 7은 파이프라인 요청을 더 이상 단일 스레드가 독점하지 않고, 다음 요청이 도착한 것처럼 큐의 맨 뒤에 배치하여 다른 요청과 인터리빙(interleaving)되도록 했습니다. 또한, Puma 6의 wait_until_not_full 최적화(스레드 여유가 없을 때 accept 루프를 블로킹)를 제거하고, accept 루프가 절대 완전히 블로킹되지 않도록 변경했습니다. 대신, wait_for_less_busy_worker라는 기존 sleep-sort 전략을 확장하여, 더 바쁜 프로세스가 더 오래 슬립하도록 비례적으로 계산함으로써 워커 간 부하를 균등하게 분배합니다. 이러한 변경으로 Puma 7은 Keep-Alive 요청 처리의 공정성을 확보하고 서버 과부하 시에도 지연 시간이 공정하게 분배되도록 합니다.

결론

결론적으로 Puma 7은 수년간 Ruby 애플리케이션의 성능을 저하시켰던 Keep-Alive 버그를 성공적으로 해결했습니다. 이제 Puma 7은 Keep-Alive 연결에서 들어오는 새로운 요청(C, D)이 이전에 수락된 요청(B) 뒤에서 자신의 차례를 기다리도록 보장하여 공정성을 확보합니다. 또한 `accept` 루프는 더 이상 완전히 블로킹되지 않고 시스템 부하에 비례하여 속도를 늦춰, 모든 Puma 프로세스에 요청 부하가 균등하게 분배되도록 합니다. Puma 7이 애플리케이션의 요청 처리 용량을 직접적으로 늘리지는 않지만, 동일한 리소스로 동일한 작업을 수행하더라도 지연 시간이 공정하게 분배되어 사용자 경험을 크게 향상시킵니다. Heroku Router 2.0 사용자들은 이미 응답 시간의 엄청난 감소를 보고하고 있으며, Keep-Alive 연결을 사용하는 모든 애플리케이션은 이점을 얻을 수 있습니다. 따라서 Ruby 애플리케이션의 대기 시간을 줄이고 안정적인 성능을 확보하기 위해 Puma 7.1.0 이상으로 업그레이드할 것을 강력히 권장합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!