GVL 계측을 통한 Rails Puma 최대 스레드 설정 튜닝

Finding ideal number of threads per process using GVL instrumentation

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

핵심 요약

  • 1 perfm Gem을 사용하여 Rails 애플리케이션의 GVL 상태 및 I/O 시간을 측정하고 분석할 수 있습니다.
  • 2 암달의 법칙으로 이론적인 최대 스레드 수를 계산한 후, 실제 환경에서 GVL 정체 시간(stall time)을 측정하여 최적의 RAILS_MAX_THREADS 값을 검증하고 조정합니다.
  • 3 Rails 애플리케이션의 I/O 작업 최소화는 GVL 정체 감소에 중요하며, 대부분의 최적화된 Rails 앱에서 max_threads 값은 3이 권장됩니다.

도입

Rails 애플리케이션의 확장을 다루는 블로그 시리즈의 3부에서는 Puma 웹 서버의 max_threads 설정을 최적화하는 실질적인 방법을 탐구합니다. 이전 편에서 다룬 프로세스 수와 암달의 법칙을 통한 이론적 스레드 수 계산에 이어, 본 글에서는 실제 프로덕션 애플리케이션에서 GVL(Global Interpreter Lock) 계측을 활용하여 각 프로세스에 대한 최적의 스레드 수를 결정하는 방법을 상세히 설명합니다. Ruby 3.2 이상에서 제공되는 GVL 계측 API와 perfm Gem을 통해 스레드 상태를 분석하고, 이를 바탕으로 성능을 극대화하면서 GVL 경합을 최소화하는 전략을 제시합니다.

본문에서는 Rails 애플리케이션의 Puma max_threads를 최적화하기 위한 구체적인 절차와 도구를 소개합니다.

GVL 상태 및 perfm Gem

Ruby GVL의 존재로 인해 스레드는 세 가지 상태 중 하나에 있을 수 있습니다:

  • Running: GVL을 소유하고 Ruby 코드를 실행 중.

  • Idle: I/O 작업을 수행 중이므로 GVL을 필요로 하지 않음.

  • Stalled: GVL을 원하지만 대기 큐에서 기다리는 중. perfm Gem은 Ruby 3.2+에서 사용 가능한 GVL 계측 API를 활용하여 각 스레드가 이 상태에서 보내는 시간을 측정합니다. 이는 Rack 미들웨어로 작동하여 지표를 수집하고 저장하며, Perfm::GvlMetricsAnalyzer 클래스를 통해 보고서를 생성합니다.

perfm을 이용한 I/O 비율 측정

perfm Gem을 Gemfile에 추가하고 bin/rails generate perfm:install로 마이그레이션을 생성한 후, config/initializers/perfm.rb에 설정을 추가합니다. ruby Perfm.configure do |config| config.enabled = true config.monitor_gvl = true config.storage = :local end Perfm.setup! 프로덕션 환경에 배포 후 약 2만 건의 요청 데이터를 수집한 뒤, monitor_gvlfalse로 설정하여 데이터 수집을 중지합니다. Rails 콘솔에서 Perfm::GvlMetricsAnalyzer를 사용하여 I/O 비율을 분석할 수 있습니다. 예시로 NeetoCal 애플리케이션은 45%의 I/O 비율을 보였습니다.

암달의 법칙을 통한 이론적 스레드 수 계산

암달의 법칙은 병렬화 가능한 부분(p)과 스레드 수(N)를 기반으로 이론적인 최대 속도 향상을 예측합니다. I/O 비율 45%(p=0.45)를 적용하면, 스레드 수가 4개일 때 이전 대비 성능 향상이 5% 미만으로 떨어져, 이론적으로 4개의 스레드가 합리적인 값임을 시사합니다.

정체 시간(Stall Time)을 이용한 스레드 수 검증

이론적으로 도출된 RAILS_MAX_THREADS 값을 실제 환경에 적용하고 GVL 정체 시간(stall time)을 측정하여 검증합니다. GVL 정체는 스레드가 GVL을 기다리는 시간으로, 75ms 미만이 권장됩니다. perfm 분석 결과를 통해 평균 정체 시간(average_stall_ms)을 확인할 수 있습니다.

  • puma_max_threads: 4 설정 시, NeetoCal의 평균 정체 시간은 110.24ms로 높게 나타났습니다.

  • puma_max_threads: 3으로 감소시키자, 평균 정체 시간은 79.38ms로 75ms에 가까워졌습니다. 이는 RAILS_MAX_THREADS에 3을 최종 값으로 결정할 수 있음을 의미합니다. 스레드 수를 더 줄이면 정체 시간은 감소하지만, 애플리케이션의 동시성을 제한하게 되므로 트레이드오프를 고려해야 합니다.

GVL 정체 감소를 위한 권장 사항

  • N+1 쿼리 제거

  • 오래 실행되는 쿼리 최적화

  • 인라인 서드파티 API 호출을 백그라운드 작업으로 이동

  • 무거운 계산 작업을 백그라운드 작업으로 이동 I/O 작업이 많은 프록시 애플리케이션의 경우, Falcon과 같은 서버로 전환하는 것을 고려할 수 있습니다.

결론

Rails 애플리케이션에서 Puma의 max_threads를 최적화하는 것은 성능 확장성에 있어 매우 중요합니다. perfm Gem을 활용한 GVL 계측은 애플리케이션의 I/O 비율을 정확히 파악하고, 암달의 법칙을 통해 이론적인 최적 스레드 수를 도출하는 데 기여합니다. 더 나아가, 실제 환경에서 GVL 정체 시간을 측정하여 이론적 값을 검증하고 조정함으로써, GVL 경합을 최소화하면서도 동시성을 극대화하는 실질적인 RAILS_MAX_THREADS 값을 확정할 수 있습니다. 대부분의 최적화된 Rails 애플리케이션에서는 max_threads의 기본값이 3으로 설정되어 있으며, 이는 광범위한 논의를 통해 결정된 값입니다. 이와 같은 접근 방식은 개발자가 Rails 애플리케이션의 성능 병목 현상을 식별하고 해결하는 데 필수적인 통찰력을 제공합니다.

댓글 0

로그인이 필요합니다

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

로그인 하러 가기

아직 댓글이 없습니다

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