본문으로 건너뛰기

From Delayed Job to Solid Queue: 10년 된 Rails 앱의 선형 확장 달성기

From Delayed Job to Solid Queue: How a 10-Year Rails App Finally Achieved Linear Scaling – Linking Ruby knowledge from the most remote places in the world.

작성자
jeff
발행일
2026년 02월 16일

핵심 요약

  • 1 10년 이상 운영된 Rails 애플리케이션에서 기존 Delayed Job의 행 잠금으로 인한 병목 현상을 해결하고 시스템 전체의 처리 성능을 획기적으로 개선했습니다.
  • 2 Solid Queue의 FOR UPDATE SKIP LOCKED 기능을 활용하여 데이터베이스 기반 큐에서도 워커 수에 비례하는 선형적인 확장성을 성공적으로 확보했습니다.
  • 3 별도의 Redis 인프라 구축 없이 기존 데이터베이스 시스템을 활용함으로써 운영의 복잡성을 최소화하고 Rails 공식 표준인 Active Job으로의 전환을 완수했습니다.

도입

10년 이상 운영된 대규모 Rails 시스템에서 백그라운드 작업은 이메일 발송, AI 처리, 데이터 정리 등 핵심적인 역할을 수행합니다. 본 아티클은 Kaigi on Rails 2025에서 발표된 Shohei Kobayashi의 사례를 바탕으로, Rails 3.2에서 시작해 7.1까지 업그레이드된 성숙한 서비스가 겪었던 비동기 처리의 한계와 이를 극복하기 위해 Solid Queue를 도입한 과정을 상세히 다룹니다. 특히 기존 Delayed Job이 가졌던 확장성 문제의 근본 원인을 분석하고, 현대적인 Rails 생태계가 제안하는 해결책을 제시합니다.

1. Delayed Job의 한계: ‘하나의 계산기’ 문제

기존 시스템에서 사용하던 Delayed Job은 데이터베이스의 행 잠금(Row Locks)을 통해 작업을 가져옵니다. 워커의 수를 늘리면 처리량이 늘어날 것으로 기대하지만, 실제로는 여러 워커가 동일한 행을 잠그기 위해 경쟁하면서 병목 현상이 발생합니다. 발표자는 이를 ‘여러 명의 점원이 하나의 계산기를 공유하는 상황’에 비유했습니다. 한 워커가 트랜잭션을 완료할 때까지 다른 워커들은 대기 상태에 머물게 되어, 결과적으로 워커를 추가해도 전체 처리량은 정체되는 현상이 나타납니다.

2. 비동기 작업의 두 가지 유형

발표자는 시스템 내의 비동기 작업을 두 가지로 분류하여 접근했습니다. - 유형 A (장시간 실행 작업): 그룹 생성, 계정 삭제 등 지연을 어느 정도 허용할 수 있는 작업입니다. - 유형 B (준실시간 작업): 이메일 알림, 챗봇 응답, AI 생성 등 비동기임에도 불구하고 사용자 경험을 위해 즉각적인 처리가 필요한 작업입니다. 기존 시스템은 이 두 유형을 효과적으로 분리하여 처리하는 데 한계가 있었으며, Solid Queue 도입을 통해 큐 격리와 우선순위 제어를 실현하고자 했습니다.

3. 왜 Solid Queue를 선택했는가?

팀은 Sidekiq Pro와 같은 대안도 검토했으나, 다음과 같은 이유로 Solid Queue를 최종 선택했습니다. - 인프라 단순성: Redis와 같은 새로운 구성 요소를 추가하지 않고 기존 Aurora 데이터베이스를 그대로 활용할 수 있습니다. 이는 ‘운영 구성 요소를 불필요하게 늘리지 않는다’는 SRE 원칙에 부합합니다. - 선형 확장성: FOR UPDATE SKIP LOCKED 구문을 사용하여 워커들이 이미 잠긴 행을 건너뛰고 다른 작업을 즉시 처리할 수 있게 합니다. 이는 워커 수에 비례하여 처리량이 증가하는 선형 확장을 가능하게 합니다. - Rails 친화적: Rails 코어 팀에서 개발한 공식 백엔드로, Active Job과의 완벽한 통합을 지원하며 향후 유지보수에 유리합니다.

4. 단계별 마이그레이션 전략

대규모 운영 시스템의 안정성을 위해 다음과 같은 5단계 전략을 수행했습니다. 1. 의존성 제거: Delayed Job 전용 API(예: .delay.method)를 제거하고 코드 간의 결합도를 낮췄습니다. 2. Active Job 표준화: 모든 비동기 작업을 ActiveJob::Base를 상속받는 명시적 클래스로 재작성했습니다. 3. Solid Queue 설정: 큐별로 워커와 디스패처를 구성하고, 폴링 간격 및 동시성 제어를 설정했습니다. 4. 점진적 전환: 특정 작업부터 순차적으로 Solid Queue 백엔드로 전환하며 프로덕션 환경의 안정성을 검증했습니다. 5. 기존 시스템 제거: 안정성이 확인된 후 Delayed Job 라이브러리와 관련 데이터를 완전히 삭제했습니다.

5. 관측성(Observability) 및 내부 구조 이해

Solid Queue의 안정적인 운영을 위해 팀은 내부 테이블 구조(Scheduled, Ready, Claimed, Failed)를 심층 분석했습니다. 또한, 다음과 같은 커스텀 메트릭을 구축하여 모니터링 공백을 메웠습니다. - 큐 상태: 대기 중인 작업 수, 예약된 작업, 실행 중인 작업, 재시도 횟수 등을 모니터링합니다. - 성능 지표: 분당 처리량, 최대/중간 대기 시간, 실행 시간 등을 추적하여 지연 발생 시 즉각 대응할 수 있는 체계를 마련했습니다. 이를 통해 시스템의 가시성을 확보하고 장애 대응 능력을 높였습니다.

결론

이번 전환 사례는 단순히 도구를 교체한 것을 넘어, 성숙한 Rails 시스템이 어떻게 현대적인 아키텍처로 진화해야 하는지를 보여주는 이정표입니다. Solid Queue는 데이터베이스 기반 큐도 올바르게 설계된다면 대규모 시스템에서 충분히 선형적인 확장이 가능하다는 것을 증명했습니다. 별도의 인프라 도입 없이도 운영 효율성을 극대화하고 Rails 표준을 준수함으로써, 유지보수 비용을 낮추고 시스템의 예측 가능성을 높인 점은 장기 운영되는 서비스에 중요한 시사점을 제공합니다.

댓글0

댓글 작성

댓글 삭제 시 비밀번호가 필요합니다.

이미 계정이 있으신가요? 로그인 후 댓글을 작성하세요.

0/1000
정중하고 건설적인 댓글을 작성해 주세요.