백그라운드 작업의 공정성을 확보하기 위해 기존 프로세서의 한계를 분석하고 네 가지 핵심 전략을 제시합니다. Ruby 개발 환경에서 Sidekiq, SalQ, Good Job과 같은 일반적인 백그라운드 프로세서들은 단순성과 성능에 초점을 맞추어 설계되었기에, 다이내믹하게 변화하는 다중 테넌시 환경의 대기열을 효율적으로 관리하는 데 어려움이 있습니다. 특히 Redis나 관계형 데이터베이스에 대한 빈번한 폴링(polling)은 시스템에 과도한 부하를 줄 수 있습니다. 이에 대한 대안으로 다음과 같은 전략들이 제안됩니다.
공정성을 위한 네 가지 전략
- 셔플링 (Shuffling): * 여러 개의 고정된 대기열을 생성하고, 고객을 특정 기준(예: 이름 첫 글자)에 따라 결정론적으로 분배하여 각 대기열에 전담 처리자를 할당하는 방식입니다. * 장점: 구현 비용이 매우 저렴하며, 특정 대규모 작업이 전체 시스템을 완전히 블록하는 것을 방지합니다. * 단점: 특정 샤드 내에서는 여전히 블로킹이 발생할 수 있으며, 자원 활용률이 낮아질 수 있습니다. 모든 고객에게 완벽하게 공정하지는 않습니다.
- 중단 가능한 반복 (Interruptible Iteration): * 하나의 큰 작업을 작은 단위로 나누어 처리하고, 각 단위 처리 후 작업을 대기열의 끝으로 다시 보내는 방식입니다. 이를 통해 다른 고객의 작업이 중간에 처리될 기회를 얻습니다. * 장점: 자원 활용률이 높고 확장성이 좋습니다. 대규모 작업이 다른 작업을 완전히 블록하지 않습니다. Active Job Continuations (Rails 8에 도입)과 같은 기능을 활용할 수 있습니다. * 단점: 동일 고객이 여러 개의 작은 배치 작업을 동시에 생성하면 공정성이 저해될 수 있습니다.
- 스로틀링 (Throttling): * Leaky Bucket 알고리즘과 같은 방법을 사용하여 특정 고객의 과도한 요청을 감지하고, 이를 일반 대기열이 아닌 별도의 ‘느린 대기열’로 분리하여 처리합니다. * 장점: 완전한 블로킹을 방지하며, 모든 고객이 최소한의 처리 기회를 가집니다. 자원 활용률이 높고 확장성이 뛰어납니다. * 단점: 워크로드가 버스티(bursty)한 특성을 가질 경우, 공정성 문제가 발생할 수 있으며 느린 대기열에 들어간 작업은 처리가 크게 지연될 수 있습니다.
- 테넌트별 대기열 (Per-Tenant Queues) / 관리자 패턴: * 모든 고객의 작업을 관리자(arbiter)가 소유한 중앙 원장(ledger)에 저장하고, 이 관리자가 메인 작업 대기열로 작업을 공정하게 분배하는 방식입니다. 관리자는 대기열이 비어갈 때마다 원장에서 다음 작업을 가져와 메인 대기열에 넣습니다. * 장점: 완벽한 공정성과 높은 효율성을 제공합니다. 특히 AI 워크플로우, 대규모 데이터 가져오기, 미디어 처리 등 무겁고 지연 시간이 긴 작업에 매우 효과적입니다. * 단점: 관리자 자체가 잠재적인 병목 지점이 될 수 있습니다. 그러나 관리자가 작업을 처리 속도보다 빠르게 메인 대기열에 푸시할 수 있다면 실제 병목은 발생하지 않습니다.