Sidekiq 개발 지침

우리는 백그라운드 작업 프로세서로 Sidekiq를 사용합니다. 이 가이드는 GitLab.com에서 잘 작동하고 기존 워커 클래스와 일관성을 유지하는 작업을 작성하기 위한 것입니다. GitLab의 관리에 대한 정보는 Sidekiq 구성을 참조하세요.

다음 주제에 대한 자세한 내용이 포함 된 페이지가 있습니다.

  1. 업데이트 간 호환성
  2. 작업 Idempotence와 작업 복제
  3. 제한된 용량 워커: 지정된 동시성으로 계속해서 작업 수행
  4. 로깅
  5. 워커 속성
    1. 작업 긴급성은 대기 및 실행 SLO를 지정합니다.
    2. 작업량 설명을위한 리소스 경계외부 의존성
    3. 기능 분류
    4. 데이터베이스 부하 분산

ApplicationWorker

모든 워커는 Sidekiq::Worker 대신 ApplicationWorker를 포함해야하며, 이는 라우팅 규칙에 기반하여 큐를 자동으로 설정하는 몇 가지 편의 메서드를 추가합니다.

샤딩

Sidekiq API를 호출 할 때 모두 샤딩을 고려해야합니다. 이를 달성하기 위해 Sidekiq::Client.via 블록 내에서 Sidekiq API를 활용하여 올바른 Sidekiq.redis 풀이 사용되도록 합니다. Gitlab::SidekiqSharding::Router.get_shard_instance 메서드를 호출하여 적합한 Redis 풀을 얻으십시오.

pool_name, pool = Gitlab::SidekiqSharding::Router.get_shard_instance(worker_class.sidekiq_options['store'])
Sidekiq::Client.via(pool) do
  ...
end

재시도

Sidekiq는 각 재시도 사이에 백오프가 있는 25번의 재시도를 사용하도록 기본 설정되어 있습니다. 25번의 재시도는 마지막 재시도가 첫 번째 시도 후 약 세 주 후에 발생한다는 것을 의미합니다 (이전 24번의 재시도가 모두 실패한다고 가정).

이것은 작업이 예약 됐을 때와 실행 사이에 많은 일이 발생할 수 있다는 것을 의미합니다. 따라서 예정된 작업이 상태 변경 후 25 회 실패하지 않도록 워커를 보호해야 합니다. 예를 들어, 작업은 예정된 프로젝트가 삭제 될 때 실패해서는 안됩니다.

다음과 같이 해주세요.

def perform(project_id)
  project = Project.find_by_id(project_id)
  return unless project
  # ...
end

대부분의 워커 - 특히 idempotent workers의 경우 - 25번의 재시도가 충분합니다. 우리의 오래된 워커 중 많은 워커는 사실상 기본적으로 3번의 재시도를 선언합니다. 3번의 재시도는 수 분 동안 발생하므로 작업이 완전히 실패하기 쉽습니다.

아래 중 하나라도 해당되는 경우 낮은 재시도 횟수가 적용될 수 있습니다.

  1. 워커가 외부 서비스에 연락하고 배달을 보장하지 않는 경우. 예를 들어, 웹훅.
  2. 워커가 동형성이 없고 여러 번 실행하면 시스템이 일관되지 않은 상태가 될 수 있습니다. 예를 들어, 시스템 노트를 게시하고 동작을 수행하는 워커: 두 번째 단계가 실패하고 워커가 다시 실행되면 시스템 노트가 다시 게시됩니다.
  3. 워커가 자주 실행되는 cron 작업인 경우. 예를 들어, cron 작업이 매 시간 실행되면 한 시간을 넘어서 재시도 할 필요가 없습니다.

워커의 각 재시도는 우리의 지표에서 실패로 계산됩니다. 항상 9 회 실패하고 10 회째에 성공하는 워커는 90%의 오류율을 갖게됩니다.

예외를 Sentry에서 추적하지 않고 워커를 매뉴얼으로 재시도하려면 Gitlab::SidekiqMiddleware::RetryError에서 상속된 예외 클래스를 사용하십시오.

ServiceUnavailable = Class.new(::Gitlab::SidekiqMiddleware::RetryError)

def perform
  ...
  
  raise ServiceUnavailable if external_service_unavailable?
end

실패 처리

실패는 일반적으로 Sidekiq 자체에 의해 처리되며, 이는 위에서 언급된 기본 재시도 메커니즘을 활용합니다. 작업을 다시 예약할 수 있도록 예외가 발생할 수 있도록해야합니다.

작업이 모든 재시도 시도 후에 실패하면 작업을 처리하기 위해 sidekiq_retries_exhausted 메서드에 추가합니다.

sidekiq_retries_exhausted do |msg, ex|
  project = Project.find(msg['args'].first)
  project.perform_a_rollback # 영구적인 실패 처리
end

def perform(project_id)
  project = Project.find(project_id)
  project.some_action # 예외가 발생합니다
end

Sidekiq 워커 연기

Sidekiq 워커는 두 가지 방법으로 연기됩니다.

  1. 매뉴얼: 특정 작업을 명시적으로 연기하는데 피처 플래그를 사용할 수 있습니다.
  2. 자동: 배치 마이그레이션의 쓰로틀링 메커니즘과 유사하게, 데이터베이스 건강 지표를 사용하여 Sidekiq 워커를 연기합니다.

    자동 연기 메커니즘을 사용하려면, 워커는 defer_on_database_health_signal을 호출하고 gitlab_schema, delay_by (지연 시간) 및 테이블 (autovacuum db 지표에서 사용)와 함께 옵트인해야합니다.

    예시:

     module Chaos
       class SleepWorker # rubocop:disable Scalability/IdempotentWorker
         include ApplicationWorker
            
         data_consistency :always
            
         sidekiq_options retry: 3
         include ChaosQueue
            
         defer_on_database_health_signal :gitlab_main, [:users], 1.minute
            
         def perform(duration_s)
           Gitlab::Chaos.sleep(duration_s)
         end
       end
     end
    

연기된 작업의 로그에는 다음이 포함되어 있습니다.

  • job_status: deferred
  • job_deferred_by: feature_flag 또는 database_health_check

Sidekiq 큐

이전에 각 워커마다 자체 큐가 있었는데, 이는 자동으로 워커 클래스 이름을 기준으로 설정되었습니다. ProcessSomethingWorker라는 워커의 경우 큐 이름은 process_something이었습니다. 이제 큐 라우팅 규칙을 사용하여 워커를 특정 큐로 라우팅 할 수 있습니다. GDK에서는 새 워커가 default이름의 큐로 라우팅됩니다.

어떤 워커가 어떤 큐를 사용하는지 확실하지 않은 경우 SomeWorker.queue를 사용하여 확인하십시오. sidekiq_options queue: :some_queue를 사용하여 매뉴얼으로 큐 이름을 재정의 할 이유는 거의 없습니다.

새로운 워커를 추가 한 후에는 bin/rake gitlab:sidekiq:all_queues_yml:generate를 실행하여 app/workers/all_queues.yml 또는 ee/app/workers/all_queues.yml을 재생성하여 일부 routing 규칙을 사용하지 않는 설치에서 sidekiq-cluster에서 가져올 수 있도록합니다. 가능한 변경 사항에 대한 자세한 내용은 epic 596을 참조하십시오.

또한, config/sidekiq_queues.yml를 다시 생성하려면 bin/rake gitlab:sidekiq:sidekiq_queues_yml:generate을 실행하십시오.

큐 네임스페이스

다른 워커들은 큐를 공유할 수 없지만, 큐 네임스페이스는 공유할 수 있습니다.

워커에 대한 큐 네임스페이스를 정의하면 해당 네임스페이스의 모든 워커 작업을 자동으로 처리하는 Sidekiq 프로세스를 시작할 수 있습니다. 예를 들어, sidekiq-cron에서 관리하는 모든 워커가 cronjob 큐 네임스페이스를 사용하는 경우, 이러한 종류의 예약된 작업에 대해 특별히 Sidekiq 프로세스를 시작할 수 있습니다. 나중에 cronjob 네임스페이스를 사용하는 새로운 워커가 추가되더라도 설정을 변경할 필요 없이 (다시 시작된 후에) Sidekiq 프로세스가 해당 워커의 작업도 수행합니다.

큐 네임스페이스는 queue_namespace DSL 클래스 메서드를 사용하여 설정할 수 있습니다.

class SomeScheduledTaskWorker
  include ApplicationWorker
  
  queue_namespace :cronjob
  
  # ...
end

이를 통해 SomeScheduledTaskWorker.queuecronjob:some_scheduled_task로 설정됩니다. 일반적으로 사용되는 네임스페이스는 해당 워커 클래스에 쉽게 포함시킬 수 있는 공통 모듈이 있으며, 큐 네임스페이스 이외에도 다른 Sidekiq 옵션을 설정할 수 있습니다. 예를 들어 CronjobQueue는 네임스페이스를 설정하고 재시도를 비활성화합니다.

bundle exec sidekiq는 네임스페이스를 인식하며, --queue(-q) 옵션이나 config/sidekiq_queues.yml:queues: 섹션에서 단순한 큐 이름 대신 네임스페이스가 제공된 경우 해당 네임스페이스의 모든 큐를 수신 대기합니다 (기술적으로는 네임스페이스 이름으로 접두사가 붙은 모든 큐).

기존 네임스페이스에 새로운 워커를 추가하는 것은 주의해서 수행해야 합니다. 네임스페이스를 처리하는 Sidekiq 프로세스의 이용 가능한 자원이 적절하게 조정되지 않은 경우 추가 작업은 이미 존재하는 워커의 작업 자원을 소비하게 됩니다.

버전

버전은 각 Sidekiq 워커 클래스에 지정할 수 있습니다. 이렇게 하면 작업이 생성될 때 버전이 함께 전송됩니다.

class FooWorker
  include ApplicationWorker
  
  version 2
  
  def perform(*args)
    if job_version == 2
      foo = args.first['foo']
    else
      foo = args.first
    end
  end
end

이 스키마에 따르면 어떤 워커든지 해당 워커의 이전 버전으로 인큐된 모든 작업을 처리할 수 있어야 합니다. 따라서 워커가 받는 인수를 변경하는 경우 version를 증가시켜야 하지만, 또한 해당 워커가 이전 버전의 인수로 큐잉된 작업을 처리할 수 있도록 해야 합니다. 작업의 perform 메서드에서 특정 버전의 작업에 따라 분기를 원할 경우 self.job_version을 읽을 수 있으며, 제공된 인수의 개수나 유형을 읽을 수도 있습니다.

작업 크기

GitLab은 Sidekiq 작업과 해당 인수를 Redis에 저장합니다. 과도한 메모리 사용을 피하기 위해 Sidekiq 작업의 인수가 원본 크기보다 100KB 이상인 경우 인수를 압축합니다.

압축 후에도 크기가 5MB를 초과하면 작업을 예약할 때 ExceedLimitError 오류가 발생합니다.

이런 경우 Sidekiq에서 데이터를 제공하는 다른 방법에 의존해야 합니다. 데이터를 다시 데이터베이스나 다른 곳에서 로드하여 Sidekiq에서 데이터를 다시 만들거나, 작업을 예약하기 전에 데이터를 객체 리포지터리에 저장하고 작업 내에서 검색할 수 있습니다.

작업 가중치

일부 작업에는 선언된 가중치가 있습니다. 이는 Sidekiq에서 기본 실행 모드로 실행할 때에만 사용됩니다 - sidekiq-cluster를 사용하는 경우에는 가중치가 고려되지 않습니다.

무료 버전에서 sidekiq-cluster 사용으로 이동하고 있기 때문에 새로 추가된 워커들은 가중치를 지정할 필요가 없습니다. 기본 가중치(1)를 사용할 수 있습니다.

테스트

각 Sidekiq 워커는 다른 클래스와 마찬가지로 RSpec을 사용하여 테스트해야 합니다. 이러한 테스트는 spec/workers에 배치되어야 합니다.

Sidekiq Redis 및 API와의 상호 작용

애플리케이션은 일반적인 응용 프로그램 로직에서 Sidekiq.redis 및 Sidekiq APIs와의 상호 작용을 최소화해야 합니다. 이러한 상호 작용은 Sidekiq 미들웨어에 대한 추상화로 이동하여 각 팀에서 재사용할 수 있어야 합니다. Sidekiq 데이터 리포지터리에서 응용프로그램 로직을 분리함으로써 GitLab 백그라운드 처리 환경을 수평 확장할 수 있는 유연성을 제공합니다.

이 규칙의 일부 예외는 마이그레이션 관련 로직이나 관리 작업 등일 수 있습니다.