Delayed Job에서 Solid Queue로의 완전 이행 여정

非同期処理実行基盤、Delayed脱出〜SolidQueue完全移行への旅路。 / Shohei Kobayashi - Kaigi on Rails 2025

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

핵심 요약

  • 1 오래된 Rails 서비스의 비동기 처리 시스템인 Delayed Job의 선형적 스케일 아웃 불가능 문제와 트랜잭션 락킹 이슈를 해결하기 위해 Solid Queue로의 전환을 단행했습니다.
  • 2 Solid Queue는 `FOR UPDATE SKIP LOCKED` 메커니즘을 통해 높은 성능과 선형적 확장성을 제공하며, 기존 Aurora 인스턴스를 활용하여 새로운 인프라 리소스 추가 없이 운영 효율성을 확보했습니다.
  • 3 Rails 7.1에서의 `enqueue_after_transaction_commit` 미지원 문제로 인한 트랜잭션 내 비동기 잡 호출 실패 및 Solid Queue의 과도한 처리 속도로 인한 DB 과부하 등 실제 이행 과정에서 발생한 장애 사례와 해결책을 상세히 공유합니다.

도입

본 발표는 10년 이상 된 Rails 7.1 기반의 매뉴얼 작성 및 공유 서비스 'TeachinBiz'에서 비동기 처리 시스템을 Delayed Job에서 Solid Queue로 성공적으로 전환한 여정을 다룹니다. 기존 Delayed Job은 데이터베이스를 큐로 사용하는 방식의 한계로 인해 데드락 발생 및 선형적 스케일 아웃이 불가능하다는 고질적인 문제를 안고 있었습니다. 이에 따라 성능 향상, 유연한 잡 관리, 개발 및 운영 효율성 증대라는 목표 하에 새로운 비동기 처리 시스템 도입을 모색하게 되었습니다.

Delayed Job의 한계 및 Solid Queue 선택 배경

Delayed Job은 데이터베이스를 큐로 사용하여 잡을 처리하는 방식이었습니다. 그러나 이는 다음과 같은 주요 문제점을 야기했습니다.

  • 선형적 스케일 아웃 불가능: 여러 워커가 동시에 잡을 가져가려 할 때, FOR UPDATE 쿼리로 인해 잡 테이블에 락이 발생하여 다른 워커들이 대기하는 현상이 발생했습니다. 이는 워커 수를 늘려도 처리 속도가 비례적으로 증가하지 않는 병목 현상으로 이어졌습니다.

  • 유연성 부족: 큐 지정 및 우선순위 설정이 어렵고, Delayed Job에 종속적인 코드 작성 방식은 향후 마이그레이션을 어렵게 만들었습니다.

Sidekiq Pro 또한 검토되었으나, Redis와 같은 새로운 인프라 리소스 추가 필요성 및 유료 라이선스, Active Job과의 완벽한 호환성 문제 등으로 인해 최종적으로는 Solid Queue를 선택하게 되었습니다. Solid Queue는 Rails 코어 팀에서 개발하여 Rails와의 높은 친화성을 가지며, 특히 FOR UPDATE SKIP LOCKED SQL 구문을 활용하여 워커 간 락 대기 없이 잡을 효율적으로 가져갈 수 있어 선형적 스케일 아웃에 매우 유리하다는 장점이 있었습니다.

Solid Queue의 내부 구현 및 마이그레이션 과정

Solid Queue는 solid_queue_jobs, solid_queue_scheduled_executions, solid_queue_ready_executions, solid_queue_claimed_executions, solid_queue_failed_executions 등 여러 테이블을 활용하여 잡의 라이프사이클을 관리합니다. 이러한 내부 구현을 이해하는 것은 모니터링, 성능 튜닝, 장애 대응에 필수적입니다.

마이그레이션은 다음 단계로 진행되었습니다.

  1. Delayed Job 고유 메서드 제거: .delay, .delay.method와 같은 Delayed Job 전용 메서드를 Active Job 기반으로 전환했습니다.

  2. Active Job으로 전환 (Delayed Adapter 사용): Solid Queue 관련 Gem을 설치하더라도 queue_adapter = :delayed 설정을 통해 기존 Delayed Job 환경에서 Active Job이 동작하도록 하여 점진적인 전환을 가능하게 했습니다.

  3. Solid Queue 설정: config/initializers/solid_queue.rb 파일에서 디스패처(Dispatcher) 및 워커(Worker) 설정을 정의했습니다. 특히 config.solid_queue.use_skip_locked = true 설정은 선형적 스케일 아웃의 핵심입니다. 큐별로 워커 프로세스 수와 폴링 간격을 다르게 설정하여 리소스 효율성을 높였습니다.

  4. 모니터링 구축: Solid Queue는 기본 감시 기능을 제공하지 않으므로, 큐 상태, 성능, 에러 알림 등을 직접 구현하여 Datadog에 연동했습니다.

  5. Solid Queue로 최종 전환: 모든 잡의 queue_adapter 설정을 :solid_queue로 변경하여 완전 이행을 완료했습니다.

  6. Delayed Job 제거: 모든 잡이 Solid Queue로 전환된 것을 확인한 후 Delayed Job Gem을 제거했습니다.

이행 과정에서 발생한 주요 장애 및 해결책

  1. enqueue_after_transaction_commit 미지원 문제: Rails 7.1에서는 enqueue_after_transaction_commit 옵션이 없었기 때문에, 트랜잭션 내부에서 비동기 잡을 호출할 경우 메인 데이터베이스의 트랜잭션이 커밋되지 않은 상태에서 큐 데이터베이스에 잡이 먼저 들어가 처리되는 문제가 발생했습니다. 이는 잡이 존재하지 않는 데이터를 참조하여 실패하거나 잘못된 데이터를 처리하는 결과를 초래했습니다. 해결책으로 Rails를 7.2로 업그레이드하여 해당 기능을 활성화했습니다.

  2. Solid Queue의 과도한 처리 속도 문제: Solid Queue의 월등히 빠른 처리 속도(Delayed Job의 수십 배)로 인해, 대량의 고부하 잡이 동시에 병렬 실행되어 메인 데이터베이스에 과도한 부하를 주어 시스템이 다운되는 장애가 발생했습니다. 이를 해결하기 위해 긴급 복구의 일환으로 고부하 잡 전용 큐와 워커를 별도로 분리하고, 해당 워커를 싱글 프로세스/싱글 스레드 방식으로 설정하여 병렬 처리를 의도적으로 제한했습니다. 이는 Solid Queue의 장점을 일부 포기하는 조치였으나, 시스템 안정화를 위한 불가피한 선택이었습니다.

결론

Delayed Job에서 Solid Queue로의 성공적인 마이그레이션은 성능 및 확장성 향상, 유연한 잡 관리라는 초기 목표를 달성했습니다. 이 프로젝트는 단순히 기술 스택을 변경하는 것을 넘어, 새로운 시스템의 내부 구현을 깊이 이해하고, 이에 맞는 설계 및 단계적인 릴리스 계획을 수립하는 것이 얼마나 중요한지를 보여주었습니다. 특히, 기존 시스템과의 상호작용 방식, 트랜잭션 문제, 잠재적 장애 시나리오에 대한 철저한 사전 검토와 대응 전략의 중요성을 강조합니다. 모든 기술 부채는 당시의 최선을 다한 결과라는 인식 아래, 앞으로도 지속적인 개선과 발전을 추구해야 할 것입니다.

댓글 0

로그인이 필요합니다

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

로그인 하러 가기

아직 댓글이 없습니다

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