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 등 여러 테이블을 활용하여 잡의 라이프사이클을 관리합니다. 이러한 내부 구현을 이해하는 것은 모니터링, 성능 튜닝, 장애 대응에 필수적입니다.
마이그레이션은 다음 단계로 진행되었습니다.
-
Delayed Job 고유 메서드 제거:
.delay,.delay.method와 같은 Delayed Job 전용 메서드를 Active Job 기반으로 전환했습니다. -
Active Job으로 전환 (Delayed Adapter 사용): Solid Queue 관련 Gem을 설치하더라도
queue_adapter = :delayed설정을 통해 기존 Delayed Job 환경에서 Active Job이 동작하도록 하여 점진적인 전환을 가능하게 했습니다. -
Solid Queue 설정:
config/initializers/solid_queue.rb파일에서 디스패처(Dispatcher) 및 워커(Worker) 설정을 정의했습니다. 특히config.solid_queue.use_skip_locked = true설정은 선형적 스케일 아웃의 핵심입니다. 큐별로 워커 프로세스 수와 폴링 간격을 다르게 설정하여 리소스 효율성을 높였습니다. -
모니터링 구축: Solid Queue는 기본 감시 기능을 제공하지 않으므로, 큐 상태, 성능, 에러 알림 등을 직접 구현하여 Datadog에 연동했습니다.
-
Solid Queue로 최종 전환: 모든 잡의
queue_adapter설정을:solid_queue로 변경하여 완전 이행을 완료했습니다. -
Delayed Job 제거: 모든 잡이 Solid Queue로 전환된 것을 확인한 후 Delayed Job Gem을 제거했습니다.
이행 과정에서 발생한 주요 장애 및 해결책
-
enqueue_after_transaction_commit미지원 문제: Rails 7.1에서는enqueue_after_transaction_commit옵션이 없었기 때문에, 트랜잭션 내부에서 비동기 잡을 호출할 경우 메인 데이터베이스의 트랜잭션이 커밋되지 않은 상태에서 큐 데이터베이스에 잡이 먼저 들어가 처리되는 문제가 발생했습니다. 이는 잡이 존재하지 않는 데이터를 참조하여 실패하거나 잘못된 데이터를 처리하는 결과를 초래했습니다. 해결책으로 Rails를 7.2로 업그레이드하여 해당 기능을 활성화했습니다. -
Solid Queue의 과도한 처리 속도 문제: Solid Queue의 월등히 빠른 처리 속도(Delayed Job의 수십 배)로 인해, 대량의 고부하 잡이 동시에 병렬 실행되어 메인 데이터베이스에 과도한 부하를 주어 시스템이 다운되는 장애가 발생했습니다. 이를 해결하기 위해 긴급 복구의 일환으로 고부하 잡 전용 큐와 워커를 별도로 분리하고, 해당 워커를 싱글 프로세스/싱글 스레드 방식으로 설정하여 병렬 처리를 의도적으로 제한했습니다. 이는 Solid Queue의 장점을 일부 포기하는 조치였으나, 시스템 안정화를 위한 불가피한 선택이었습니다.