Rails 애플리케이션 스케일링: Active Record 데이터베이스 연결 풀 심층 분석

Understanding Active Record Connection Pooling

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

핵심 요약

  • 1 Active Record 연결 풀은 Rails 애플리케이션의 성능 최적화를 위해 프로세스당 데이터베이스 연결을 관리하며, Rails 7.2부터 쿼리당 연결을 대여/반환하는 방식으로 변경되었습니다.
  • 2 연결 풀의 pool 크기, checkout_timeout, idle_timeout 등 핵심 설정은 애플리케이션의 동시성 및 데이터베이스 부하에 맞춰 신중하게 구성해야 합니다.
  • 3 ActiveRecord::ConnectionTimeoutError는 풀 크기 부족, 사용자 정의 스레드, Active Storage 프록시 모드, Rack Timeout 등으로 발생할 수 있으며, PgBouncer와 같은 외부 풀러를 통해 해결할 수 있습니다.

도입

Rails 애플리케이션에서 데이터베이스 연결은 비용이 많이 드는 작업이므로, 성능 향상을 위해 연결 풀링 기법이 필수적입니다. 이 글은 Rails의 Active Record가 데이터베이스 연결 풀을 어떻게 구현하고 관리하는지, 그리고 애플리케이션 스케일링 과정에서 고려해야 할 주요 설정 및 문제 해결 방안을 심층적으로 다룹니다. 특히 Rails 7.2 이후의 변경 사항과 load_async의 영향까지 설명하여 효율적인 데이터베이스 자원 활용 전략을 제시합니다.

데이터베이스 연결 풀링의 필요성

  • 데이터베이스 연결 생성 및 종료는 시간과 리소스가 많이 소요되는 작업입니다.

  • 연결 풀은 미리 설정된 연결을 재사용하여 이러한 오버헤드를 줄이고 애플리케이션의 응답 속도를 향상시킵니다.

Active Record 연결 풀 구현

  • Active Record는 각 웹 및 백그라운드 프로세스(예: Puma, Sidekiq)마다 독립적인 연결 풀을 관리합니다.

  • Rails 7.2 이전: 웹 요청 또는 백그라운드 작업 완료 시까지 연결을 유지했습니다.

  • Rails 7.2 이후: 각 개별 쿼리마다 연결을 풀에서 대여하고 사용 후 즉시 반환하는 방식으로 변경되었습니다. 이는 I/O 작업이 많은 애플리케이션에서 연결 점유 시간을 줄여 동시 쿼리 실행 능력을 향상시킵니다.

  • 쿼리 캐시는 이제 풀이 소유하며, 모든 연결이 공유하여 사용합니다.

주요 연결 풀 설정 옵션 (database.yml)

  • pool: 풀이 유지할 최대 연결 수. RAILS_MAX_THREADS에 기본 연결되지만, 데이터베이스가 허용하는 최대치로 설정하는 것이 일반적입니다.

  • checkout_timeout: 스레드가 연결을 얻기 위해 기다리는 최대 시간 (기본 5초). 초과 시 ActiveRecord::ConnectionTimeoutError 발생.

  • idle_timeout: 유휴 연결이 풀에서 제거되기 전까지의 시간 (기본 300초).

  • reaping_frequency: Reaper가 유휴/죽은 연결을 제거하기 위해 실행되는 빈도 (기본 60초).

Active Record 연결 풀 Reaper

  • Reaper는 데이터베이스 재시작, 네트워크 문제 등으로 발생한 “죽은” 연결과 idle_timeout을 초과한 유휴 연결을 주기적으로 검사하고 제거합니다.

  • 유휴 연결은 불필요한 메모리 소비와 CPU 오버헤드를 유발하므로, Reaper는 풀의 건전성과 리소스 효율성에 중요합니다.

최대 데이터베이스 연결 수 계산

  • 웹 프로세스 (Puma): 웹 다이노 수 * Puma 프로세스 수 * max_threads

  • 백그라운드 프로세스 (Sidekiq): 워커 다이노 수 * Sidekiq 프로세스 수 * concurrency

  • load_async 사용 시: 프로세스 레벨 동시성 + global_executor_concurrency + 1 (메인 스레드 + 비동기 쿼리 스레드)

  • 데이터베이스 플랜의 최대 동시 연결 수를 초과하지 않도록 계산하고 설정해야 합니다. Preboot 활성화 시에는 두 배의 연결이 필요할 수 있습니다.

ActiveRecord::ConnectionTimeoutError 원인 및 해결

  • 풀 크기 부족: pool 설정이 애플리케이션의 동시성보다 낮을 때 발생.

  • 사용자 정의 스레드: Thread.new 등으로 생성된 스레드가 연결을 점유하는 경우.

  • Active Storage 프록시 모드: 스트리밍 응답을 위해 추가 스레드와 연결을 사용합니다.

  • Rack Timeout: 요청 종료 시 데이터베이스 작업 중 예외가 발생하면 연결 정리가 제대로 이루어지지 않을 수 있습니다.

  • 해결: pool 크기를 데이터베이스 플랜의 최대 허용치로 설정하고, 에러 발생 시 연결 풀 상세 정보를 로깅하여 문제 스레드를 식별하는 것이 중요합니다.

PgBouncer의 활용

  • PgBouncer는 PostgreSQL을 위한 외부 연결 풀러로, Active Record 풀이 단일 프로세스 내에서 작동하는 것과 달리, 애플리케이션과 데이터베이스 사이에 위치하여 모든 애플리케이션 프로세스에 걸쳐 연결을 관리합니다.

  • 이는 데이터베이스 연결 한계에 도달하는 대규모/고동시성 애플리케이션에 효과적인 솔루션입니다.

연결 풀 통계 모니터링

  • rufus-scheduler Gem 등을 사용하여 주기적으로 Active Record 연결 풀 통계를 수집하고 New Relic과 같은 APM 서비스로 전송하여 시각적으로 모니터링함으로써 잠재적 문제를 조기에 파악할 수 있습니다.

결론

Rails 애플리케이션에서 데이터베이스 연결 풀링은 성능 최적화의 핵심 요소입니다. Active Record의 연결 풀 메커니즘을 이해하고 pool 크기, idle_timeout 등의 설정을 애플리케이션의 특성과 데이터베이스 용량에 맞춰 적절히 구성하는 것이 중요합니다. load_async와 같은 최신 기능의 영향을 고려하고, ActiveRecord::ConnectionTimeoutError 발생 시 원인 분석 및 PgBouncer와 같은 외부 솔루션 도입을 통해 안정적이고 확장 가능한 서비스를 유지할 수 있습니다. 지속적인 모니터링은 풀의 건전성을 보장하는 데 필수적입니다.

댓글 0

로그인이 필요합니다

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

로그인 하러 가기

아직 댓글이 없습니다

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