Full Script는 Rescue의 불안정성과 유지보수 어려움을 해결하기 위해 Sidekiq, GoodJob(MySQL 미지원) 등의 대안을 검토한 후, Rails의 기본 어댑터로 통합된 Solid Queue를 선택했습니다. Solid Queue는 MySQL을 지원하며, 데이터베이스 기반으로 메트릭 수집이 용이하고 Rails의 지원을 받는 견고한 솔루션이라는 점이 주요 선택 이유였습니다.
마이그레이션은 다음과 같은 단계와 해결책을 통해 진행되었습니다.
1. Solid Queue 설정 및 아키텍처 결정
- Active Job 전환: 모든 작업이 Active Job을 사용하도록 보장했습니다 (사전 작업 완료).
- 데이터베이스 분리: 프로덕션 환경에서는 Solid Queue 전용 MySQL 데이터베이스를 생성하여 주 데이터베이스의 부하를 분리했습니다. 개발/스테이징 환경에서는 비용 절감을 위해 주 데이터베이스의 다른 스키마를 사용했습니다.
- 트랜잭션 무결성: 초기 마이그레이션 중에는 Rescue와 동일하게 트랜잭션 무결성을 비활성화하여 기존 동작을 유지했습니다. 향후 필요에 따라 활성화할 계획입니다. (Rails 8의
in_queue_after_transaction_commit
옵션 활용)
2. 큐 이름 재정의 및 작업자 구성
- 지연 시간 허용치 기반 큐: 기존의 우선순위/도메인 기반 큐(예:
critical
,notifications
)의 모호성을 해결하기 위해within_one_minute
,within_one_hour
와 같이 지연 시간 허용치(latency tolerance)를 명시한 큐 이름으로 변경했습니다. 이는 개발자와 운영팀에게 명확한 SLO(Service Level Objective)를 제공하고, 큐 지연 시간 모니터링 및 자동 확장의 기준이 됩니다. - 전용 작업자: 각 큐에 전용 작업자를 할당하여 리소스 공유 없이 독립적으로 확장할 수 있도록 했습니다. 빠른 큐(
within_one_minute
)의 경우 서버 부팅 시간으로 인한 SLO 위반을 방지하기 위해 작업자를 선제적으로 과다 할당했습니다.
3. 모니터링 및 마이그레이션 전략
- 메트릭 수집: Yabeda Gem을 사용하여 Solid Queue 데이터베이스에서 큐 지연 시간(queue latency) 및 작업 수(job count) 메트릭을 수집하고 Prometheus 및 대시보드를 통해 시각화하여 큐의 상태를 실시간으로 모니터링했습니다.
- 점진적 마이그레이션:
ApplicationJob
에서queue_as
메서드를 오버라이드하여 새로운 큐 이름은 Solid Queue를 사용하고, 기존 큐 이름은 Rescue를 사용하도록 설정했습니다. 초기에는 위험도가 낮은 작업부터 Solid Queue로 전환하며 시스템의 안정성을 확보한 후, 점차 고위험 작업을 마이그레이션했습니다.
4. 주요 문제 해결
- 너무 많은 인자(Arguments): 일부 작업의 인자 길이가 MySQL
TEXT
컬럼(65KB) 제한을 초과하여MEDIUMTEXT
컬럼(16MB)으로 변경했습니다. - 메모리 부족: 특정 작업 마이그레이션 후 작업자(worker)가 예기치 않게 종료되는 문제(SIGKILL)는 메모리 부족 때문이었으며, 작업자 메모리를 2GB에서 4GB로 증설하여 해결했습니다.
- 데이터베이스 연결 부족: 특정 시간에 대량의 작업이 한꺼번에 큐에 등록되면서 작업자 수가 급증하고 데이터베이스 연결이 고갈되는 문제가 발생했습니다. 데이터베이스 규모를 확장하고 작업자 자동 확장 상한선을 설정하여 해결했습니다.
- 느린 작업 처리: 빠른 큐에 긴 시간이 소요되는 작업이 들어가는 것을 방지하기 위해
around_perform
훅을 사용하여 작업 실행 시간을 모니터링하고, 허용 지연 시간의 10%를 초과할 경우 Sentry로 경고를 보내 해당 작업을 느린 큐로 이동시키거나 최적화하도록 유도했습니다. - 지연된 작업(Delayed Jobs) 마이그레이션: Redis에 저장된 수많은 지연된 작업을 Solid Queue로 마이그레이션하기 위해 복잡한 스크립트를 작성하여 처리했습니다.
- 대량 작업 큐잉 최적화: MySQL이 Redis와 다르다는 점을 인지하고, 대량의 작업을 큐잉할 때
find_in_batches
와perform_all_later
를 사용하여 SQL INSERT 문 수를 줄이고, 필요에 따라 임의의 대기 시간을 추가하여 작업 부하를 분산시켰습니다.