더 이상 두렵지 않은: 커넥션 고갈 해소를 위한 실천적 접근

もう並列実行は怖くない コネクション枯渇とデッドロック解決の実践的アプローチ / katakyo - Kaigi on Rails 2025

작성자
Kaigi on Rails
발행일
2025년 11월 25일

핵심 요약

  • 1 병렬 처리를 위한 데이터베이스 커넥션 풀 및 스레드 수 파라미터는 예상 가능한 모든 서비스의 최대 연결 수를 비관적으로 고려하여 설정해야 합니다.
  • 2 커넥션 고갈을 방지하기 위해 데이터베이스 작업과 외부 I/O 통신을 명확히 분리하고 `ActiveRecord::Base.connection_pool.with_connection`을 활용하여 커넥션 관리를 안전하게 수행해야 합니다.
  • 3 LLM API와 같이 I/O 바운드 작업이 병목인 경우, 암달의 법칙을 활용하여 최적의 스레드 수를 결정하고, 장시간 트랜잭션을 피하며 성능 프로파일링을 통해 병목 지점을 개선하는 것이 중요합니다.

도입

본 발표는 마이베스트(MyBest)의 AI 워크플로우 개발 과정에서 직면한 데이터베이스 커넥션 고갈 문제를 해결하기 위한 실천적 접근 방안을 제시합니다. 초기에는 데드록 해결을 목표로 했으나, 병렬 실행과의 연관성이 적음을 확인하고 커넥션 고갈에 초점을 맞추게 되었습니다. 루비 온 레일즈 환경에서 액티브 레코드(Active Record)의 커넥션 메커니즘을 이해하고, RDS의 최대 커넥션 수, 액티브 레코드의 커넥션 풀 사이즈, 사이드킥(Sidekiq) 스레드 수 등 병렬 및 동시 처리 파라미터를 적절하게 산정하는 방법론과 절차를 다룹니다. 특히, AI 워크플로우와 같이 I/O 바운드 작업이 많은 환경에서 발생할 수 있는 커넥션 관련 문제를 예방하고 성능을 최적화하는 구체적인 방법을 소개합니다.

병렬 실행 배경 및 문제점

마이베스트는 약 1,000만 개의 상품 데이터베이스를 구축 중이며, 상품 정보 입력 및 스펙 정보 등록 작업이 대부분 수동으로 이루어져 시간이 매우 오래 걸렸습니다. 이를 개선하기 위해 AI를 활용한 상품 정보 자동 입력 프로젝트가 시작되었고, OpenAI, Gemini, Claude 등 LLM API를 여러 번 호출하는 AI 워크플로우가 구축되었습니다. 그러나 이 워크플로우는 하나의 상품 리서치에 약 2분이 소요되었고, 월 120만 개 상품 처리 목표에 비해 하루 3,500개 처리량으로는 한 달 내 완료가 불가능했습니다. 또한, 관리 화면에서 AI 워크플로우를 버튼으로 실행할 때 사이드킥 스레드 부족으로 작업이 지연되는 문제가 발생했습니다.

커넥션 고갈 문제 분석

초기 병렬 처리를 위해 parallel Gem의 멀티스레드 방식을 사용했을 때 커넥션 풀 고갈 문제가 발생했습니다. 이는 스레드 수에 비해 커넥션 풀 크기가 적절하게 설정되지 않아 checkout_timeout이 초과되었기 때문입니다. 액티브 레코드의 커넥션 풀은 데이터베이스 연결 및 해제의 높은 비용을 줄이기 위해 미리 일정 수의 연결을 확보해 재사용하는 메커니즘입니다. 하나의 스레드는 일반적으로 여러 데이터베이스 커넥션을 생성하지 않으므로, 커넥션 풀 크기는 스레드 수와 일치해야 합니다. (예: 8스레드 -> 풀 사이즈 8)

사이드킥의 경우, 새로운 프로세스를 띄워 concurrency를 10, 커넥션 풀을 10으로 설정했을 때도 고갈이 발생했습니다. 이는 사이드킥 프로세스 자체가 시작 시 하나의 커넥션을 점유하므로, 실제 사용 가능한 스레드 수에 맞춰 커넥션 풀은 concurrency + 1로 설정해야 함을 알게 되었습니다.

안전한 병렬 처리를 위한 접근 방식

1. 파라미터 설정의 비관적 예측

  • RDS Max Connections 산정: 웹 애플리케이션, 배치, 백그라운드(Sidekiq) 서비스의 예상 최대 커넥션 수를 합산하여 RDS의 MAX_CONNECTIONS를 초과하지 않도록 합니다. 배포 시 구 서비스와 신 서비스가 일시적으로 공존하여 ECS 태스크 수가 두 배가 될 수 있으므로, MAX_CONNECTIONS의 절반을 최대 허용치로 간주해야 합니다. (예: RDS 4000 -> 최대 2000).

  • I/O 비율 측정 및 스레드 최적화: AI 워크플로우의 I/O 대기 비율이 90% 이상임을 측정하고, 암달의 법칙을 활용하여 최적의 스레드 수를 계산했습니다. Rake 태스크의 경우 8스레드가 가장 효율적이었으며, 이후에는 서버 인스턴스 수를 늘려 병렬 처리를 수행했습니다.

  • Sidekiq concurrency 설정: 사이드킥 공식 권장 사항에 따라 concurrency는 50 이하로 설정하고, 그 이상이 필요할 경우 프로세스 수를 늘립니다. 새로운 AI 리서치용 사이드킥 프로세스를 별도로 띄우고 concurrency를 50으로 설정했습니다.

  • 설정 확인: ActiveRecord::Base.connection_pool.stat 메서드를 사용하여 커넥션 풀의 크기 및 현재 연결 수를 모니터링하여 배포 전 설정이 올바른지 확인합니다.

2. 커넥션 고갈 방지 코드 패턴

  • DB 및 I/O 작업 분리: 애플리케이션 코드 내에서 데이터베이스 커넥션이 발생하는 읽기/쓰기 쿼리 및 트랜잭션과 외부 LLM API 통신과 같은 I/O 작업을 명확히 분리합니다.

  • ActiveRecord::Base.connection_pool.with_connection 활용: 데이터베이스 커넥션이 필요한 블록에서만 이 래퍼 메서드를 사용하여 커넥션을 안전하게 빌려 쓰고 블록 종료 시 자동으로 반환하도록 합니다. 이는 멀티스레드 환경에서 커넥션 누수를 방지하고 타임아웃 발생 확률을 낮춥니다.

3. 성능 개선 (Performance Tuning)

  • 장시간 트랜잭션 주의: 장시간 트랜잭션은 테이블 록, LockWaitTimeoutError를 유발하고 커넥션을 점유하여 성능 저하를 초래할 수 있습니다. 트랜잭션 범위를 최소화하고, 외부 API 호출과 같은 무거운 작업은 트랜잭션 외부로 이동시키며, 불필요한 테이블 전체 록을 피하고 록의 세분성을 고려합니다.

  • 성능 프로파일링: rack-mini-profiler Gem을 사용하여 워크플로우 전체의 처리 시간을 측정하고, N+1 쿼리, 비효율적인 쿼리, 트랜잭션 내 이미지 업로드(외부 I/O)와 같은 병목 지점을 찾아 개선했습니다. 예를 들어, S3 이미지 업로드 처리와 레코드 저장 처리를 분리하고 이미지 업로드를 비동기화했습니다.

결론

본 발표를 통해 병렬 실행 환경에서 커넥션 고갈 문제를 해결하기 위한 체계적인 접근법을 제시했습니다. 핵심은 데이터베이스 커넥션 풀과 스레드 수 파라미터를 비관적으로 예측하여 설정하고, 코드 레벨에서 데이터베이스 작업과 I/O 작업을 명확히 분리하여 커넥션 관리를 효율화하는 것입니다. 특히 `ActiveRecord::Base.connection_pool.with_connection` 사용과 장시간 트랜잭션 회피, 그리고 `rack-mini-profiler`와 같은 도구를 활용한 지속적인 성능 프로파일링은 안정적이고 고성능의 시스템을 구축하는 데 필수적입니다. 이와 같은 실천적 접근을 통해 마이베스트는 AI 워크플로우의 처리 속도를 120배 향상시키고, 월 120만 개 상품 처리를 3일 만에 완료하는 등 상당한 성과를 달성했습니다. 이는 LLM API와 같이 I/O 바운드 작업이 병목인 경우, 병렬화를 신중하게 검토하고 적절한 커넥션 관리 전략을 수립하는 것이 얼마나 중요한지를 보여주는 사례입니다.

댓글 0

로그인이 필요합니다

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

로그인 하러 가기

아직 댓글이 없습니다

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