Rails 8.1의 Active Job Continuations: 중단 및 재개 가능한 Job

Donal McBreen - Resumable Jobs with Active Job Continuations

작성자
Ruby on Rails Youtube
발행일
2025년 09월 16일

핵심 요약

  • 1 Rails 8.1에 도입되는 Active Job Continuations는 장시간 실행되는 Active Job을 중단하고 재개할 수 있도록 지원하여 배포 중 작업 중단 문제를 해결합니다.
  • 2 이 기능은 `step` 메서드로 작업을 단계별로 분할하고, `checkpoint`로 수동 저장 지점을 설정하며, `cursor`로 반복 작업의 진행 상황을 추적하여 구현됩니다.
  • 3 Basecamp의 Kamal 배포 환경에서 장시간 Job의 강제 종료 문제를 해결하기 위해 개발되었으며, Job의 단순한 구조를 유지하면서 견고성을 높이는 것을 목표로 합니다.

도입

37Signals의 리드 개발자 Donal McBeer는 Rails 8.1에 새로 도입될 Active Job Continuations에 대해 발표했습니다. 이 기능은 Rails Job을 중단하고 재개할 수 있도록 하여, 장시간 실행되는 Job이 애플리케이션 재시작이나 배포로 인해 강제 종료되는 문제를 해결합니다. 기존에는 Job 실행 시간을 고려하지 않으면 코드가 단순해지는 반면, Job이 취약해지는 문제가 있었습니다. 반대로 Job 실행 시간을 고려하면 복잡한 오케스트레이션 프레임워크를 개발해야 하는 딜레마가 있었습니다. Active Job Continuations는 이러한 문제를 해결하여 Job의 단순성을 유지하면서도 견고성을 확보하는 것을 목표로 합니다.

장시간 Job의 문제점과 해결책

37Signals의 Basecamp는 Capistrano를 사용하여 배포 시 기존 Job을 완료할 때까지 기다렸습니다. 이는 자체 하드웨어에서 애플리케이션 재시작이 드물었기 때문에 가능했지만, 오래된 코드가 계속 실행되는 등의 문제가 있었습니다. Basecamp를 Docker 기반 배포 프레임워크인 Kamal로 전환하면서, 배포 시 Worker가 30초 후에 강제 종료되는 상황에 직면했습니다. 이로 인해 장시간 실행되는 Job들이 중단되고 처음부터 다시 시작해야 하는 문제가 발생했으며, 이는 고객 경험에 직접적인 영향을 미쳤습니다.

이 문제를 해결하기 위해 37Signals는 약 30개의 Job에 적용할 수 있는 범용적인 중단 및 재개 메커니즘을 모색했습니다. 목표는 기존 Job 코드의 단순한 구조를 유지하면서 최소한의 변경으로 Active Job만 사용하여 이 기능을 구현하고, 이를 Rails에 업스트림하는 것이었습니다.

Active Job Continuations API 및 작동 방식

Active Job Continuations는 다음과 같은 API 단계를 통해 Job을 중단 및 재개 가능하게 만듭니다:

  • ActiveJob::Continuations 모듈 포함: Job 클래스에 이 모듈을 포함합니다.

  • step 메서드를 사용한 Job 분할: Job의 각 논리적 단위를 step 블록으로 묶습니다. step 메서드 사이에는 암묵적인 ‘체크포인트’가 생성됩니다. ```ruby class ExportJob < ActiveJob::Base include ActiveJob::Continuations

    def perform step(:write_files) { # … } step(:zip_files) { # … } step(:upload_files) { # … } end end ```

  • checkpoint 메서드를 사용한 수동 체크포인트: 장시간 실행되는 step 내부에서 step.checkpoint를 호출하여 수동으로 체크포인트를 설정할 수 있습니다. 이는 Worker 종료 신호를 받았을 때 Job이 Grace Period 내에 작업을 중단하고 진행 상황을 저장하도록 돕습니다. ```ruby class DestroyRecordsJob < ActiveJob::Base include ActiveJob::Continuations

    def perform step(:destroy_records) do |step| Account.find_each(batch_size: 100) do |account| account.records.in_batches do |batch| batch.destroy_all step.checkpoint # 수동 체크포인트 end end end end end ```

  • cursor를 사용한 반복 작업 추적: 반복 작업에서 진행 상황을 정확히 추적하기 위해 step.cursor를 사용합니다. step.set!(value) 또는 step.advance_from(value) 메서드를 호출하여 커서 값을 저장하고 동시에 체크포인트를 생성합니다. Job 재개 시 이 커서 값이 복원되어 중단된 지점부터 작업을 이어서 수행할 수 있습니다. ```ruby class NotifyUsersJob < ActiveJob::Base include ActiveJob::Continuations

    def perform step(:notify_users) do |step| Account.find_each(start: step.cursor) do |account| account.users.find_each(start: step.cursor&.last) do |user| user.notify! step.set!([account.id, user.id + 1]) # 커서 설정 및 체크포인트 end step.set!([account.id + 1, nil]) # 다음 계정으로 이동 end end end end ```

Job이 중단되면 Interrupt 예외가 발생하고, around_perform 콜백에 의해 이 예외가 처리됩니다. 이후 Active Job은 현재 Job을 재시도(retry)하며, 이 과정에서 완료된 step 목록과 cursor 값을 저장합니다. 새로운 Worker가 재시도된 Job을 처리할 때, 저장된 정보를 바탕으로 이미 완료된 step을 건너뛰고 중단된 지점부터 작업을 재개합니다.

주의사항 및 고려사항

  • 단계 독립성: 각 step은 독립적으로 실행되어야 하며, 이전 step의 내부 상태에 의존해서는 안 됩니다.

  • 체크포인트 빈도: Worker의 Grace Period보다 더 자주 checkpoint를 호출해야 합니다. 그렇지 않으면 진행 상황이 저장되지 않은 채 Job이 강제 종료될 수 있습니다.

  • 데이터베이스 트랜잭션: 데이터베이스 트랜잭션 내부에서는 checkpoint를 호출할 수 없습니다. Rails는 이를 감지하고 예외를 발생시켜 데이터 일관성 문제를 방지합니다.

  • isolated Step: isolated로 표시된 step은 항상 별도의 Job 호출에서 실행되어 단계 간 독립성을 보장하고, 장시간 step이 시작되기 전에 진행 상황을 저장하는 데 유용합니다.

  • 커서의 직렬화: cursor는 Integer, String, Array 등 직렬화 가능한 모든 Ruby 객체가 될 수 있습니다.

  • 테스트 도구: active_job_continuation_test_helpers 모듈을 통해 interrupt_job_during_step과 같은 헬퍼 메서드를 사용하여 Job의 중단 및 재개 로직을 테스트할 수 있습니다.

실제 적용 및 기대 효과

Active Job Continuations는 Basecamp에서 6개월 이상 성공적으로 운영되고 있으며, 배포 시마다 10~40개의 Job이 중단되고 재개되는 상황을 처리하고 있습니다. 이는 Job이 Worker 종료 신호에 반응하여 스스로 빠르게 중단되도록 함으로써 배포 시간을 단축하는 부가적인 이점도 제공합니다. 이 기능은 Rails 8.1에 포함될 예정이며, SolidQueue와 Sidekiq 어댑터는 이미 지원하고 있습니다. 다른 Job 백엔드의 경우, queue_adapter.stopping? 메서드를 몽키 패치하여 쉽게 통합할 수 있습니다.

결론

Active Job Continuations는 Rails 개발자가 장시간 실행되는 Job을 처리하는 방식에 혁신을 가져올 것입니다. 기존에는 Job 실행 시간을 단축하기 위해 복잡한 오케스트레이션 로직을 도입하거나, Job이 강제 종료될 위험을 감수해야 했습니다. 하지만 Active Job Continuations를 통해 개발자는 Job의 전체 스토리를 설명하는 단순하고 긴 Job을 작성할 수 있게 되었습니다. 이 Job들은 필요에 따라 자동으로 중단되고 재개될 수 있어, 애플리케이션 배포 및 Worker 재시작 시에도 견고성을 유지합니다. Rails 8.1에 도입될 이 기능은 Job 처리의 신뢰성을 높이고, 개발 복잡성을 줄이며, 궁극적으로 더 빠르고 안정적인 배포 환경을 구축하는 데 기여할 것입니다.

댓글 0

로그인이 필요합니다

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

로그인 하러 가기

아직 댓글이 없습니다

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