Sidekiq idempotent jobs

작업은 여러 이유로 실패할 수 있다는 것이 알려져 있습니다. 예를 들어, 네트워크 장애나 버그 등이 있습니다. 이를 해결하기 위해 Sidekiq에는 기본으로 내장된 재시도 메커니즘이 있으며, 이는 GitLab의 대부분의 워커에서 기본적으로 사용됩니다.

작업이 실패한 후에도 애플리케이션이나 사용자에게 주요한 영향이 없이 작업이 다시 실행될 것으로 예상되므로, Sidekiq은 작업이 멱등(반복 실행에 안전하고, 트랜잭션적)하도록 장려합니다.

일반적인 규칙으로, 워커는 다음과 같은 경우에 멱등으로 간주될 수 있습니다:

  • 동일한 매개변수로 안전하게 여러 번 실행할 수 있다.
  • 애플리케이션에서의 부작용이 한 번만 발생할 것으로 예상된다 (또는 두 번째 실행의 부작용에 영향이 없어야 함).

이에 대한 좋은 예는 캐시 만료 워커일 것입니다.

멱등 워커로 예약된 작업은 대기열에 이미 동일한 매개변수를 가진 실행되지 않은 작업이 있는 경우 중복 처리됩니다.

워커가 멱등하다는 것을 보장하기

다음 공유 예제를 사용하여 워커 테스트가 통과되었는지 확인하세요.

it_behaves_like 'an idempotent worker' do
  it 'marks the MR as merged' do
    # 이 블록 내에서 subject를 사용하면 작업이 여러 번 처리됩니다.
    subject
    
    expect(merge_request.state).to eq('merged')
  end
end

워커에 포함된 perform_multiple 메서드를 사용하여 직접 job.perform 대신에 사용하세요 (이 도우미 메서드는 자동적으로 워커에 포함됩니다).

워커를 멱등하다고 선언하기

class IdempotentWorker
  include ApplicationWorker
  
  # 워커가 멱등하고 여러 번 안전하게 실행될 수 있다고 선언합니다.
  idempotent!
  
  # ...
end

이 상위 워커 클래스에만 idempotent! 호출을 갖도록 권장되며, perform 메서드가 다른 클래스나 모듈에서 정의된 경우에도 마찬가지입니다.

워커 클래스가 멱등하게 표시되어 있지 않으면 어떤 판정에 의해 실패합니다. 작업이 여러 번 안전하게 실행될 수 있다고 확신하지 못하는 경우 코프를 건너뛰는 것을 고려해 주세요.

중복 처리

멱등 워커의 작업이 대기열에 추가될 때 이미 실행되지 않은 동일한 매개변수를 가진 작업이 대기하고 있는 경우, GitLab은 두 번째 작업을 삭제합니다. 첫 번째로 예약된 작업이 수행할 것으로 예정되었으므로 작업이 생략됩니다. 두 번째 작업을 실행할 때는 첫 번째 작업이 아무것도 안하지만 있을 것입니다.

전략

GitLab은 두 가지 중복 처리 전략을 지원합니다:

  • until_executing, 이 전략은 기본 전략입니다
  • until_executed

더 많은 중복 처리 전략이 제안되었습니다. 다른 전략에서 이점을 얻을 수 있는 워커를 구현중인 경우 해당 이슈에 의견을 남기세요.

실행 직전까지

이 전략은 작업이 대기열에 추가될 때 잠금을 걸고, 작업이 시작하기 전에 해당 잠금을 해제합니다.

예를 들어, AuthorizedProjectsWorker는 사용자 ID를 사용합니다. 이 워커가 실행될 때, 사용자의 권한을 재계산합니다. GitLab은 이 작업을 사용자의 권한이 변경될 때마다 작업을 예약합니다. 동시에 두 개의 프로젝트에 동일한 사용자가 추가된 경우, 첫 번째 작업이 실행되기 전에 두 번째 작업이 건너뛸 수 있습니다. 이는 첫 번째 작업이 실행될 때 두 프로젝트에 대한 권한을 생성하기 때문입니다.

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker
    
    deduplicate :until_executing
    idempotent!
    
    # ...
  end
end

실행 종료까지

이 전략은 작업이 대기열에 추가될 때 잠금을 걸고, 작업이 종료된 후에 해당 잠금을 해제합니다. 이는 작업을 여러 번 동시에 실행하는 것을 방지하는 데 사용될 수 있습니다.

module Ci
  class BuildTraceChunkFlushWorker
    include ApplicationWorker
    
    deduplicate :until_executed
    idempotent!
    
    # ...
  end
end

또한, if_deduplicated: :reschedule_once 옵션을 전달하여 현재 실행 중인 작업이 완료되고 중복 처리가 한 번 이상 발생한 후에 작업을 한 번 다시 실행할 수 있도록 할 수 있습니다. 만약 레이스 조건이 발생했더라도 항상 최신 결과가 생성되도록 보장합니다. 더 많은 정보는 이 이슈에서 확인하십시오.

미래에 작업 예약하기

GitLab은 작업이 미래에 예정되어 있을 때 이 상태가 변경된 것으로 간주하여 작업이 실행될 때까지 생략하지 않습니다. until_executeduntil_executing 전략 모두 미래에 예정된 작업의 중복 처리가 가능합니다.

미래에 예정된 작업의 중복 처리를 원하는 경우, 중복 처리 전략을 정의할 때 including_scheduled: true 인수를 전달하여 이를 워커에서 지정할 수 있습니다.

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker
    
    deduplicate :until_executing, including_scheduled: true
    idempotent!
    
    # ...
  end
end

중복 처리 TTL(Time-To-Live) 설정

중복 처리는 Redis에 저장된 멱등 키에 의존합니다. 이는 보통 구성된 중복 처리 전략에 따라 정리됩니다.

그러나 중복 처리 키는 다음과 같은 경우에는 TTL까지 남을 수 있습니다:

  1. until_executing이 사용되지만 작업이 Sidekiq 클라이언트 미들웨어가 실행된 후에 대기열에 추가되거나 실행되지 않은 경우.

  2. until_executed가 사용되지만 작업이 재시도가 고갈되어 실패하거나 최대 횟수만큼 중단되거나 손실된 경우.

기본값은 6시간입니다. 이 기간 동안은 첫 번째 작업이 실행되거나 완료되지 않아도 작업이 대기열에 추가되지 않습니다.

TTL은 다음과 같이 구성할 수 있습니다:

class ProjectImportScheduleWorker
  include ApplicationWorker
  
  idempotent!
  deduplicate :until_executing, ttl: 5.minutes
end

TTL이 만료될 경우 중복 작업이 발생할 수 있으므로, 일부 중복 허용이 가능한 작업에만 이 값을 낮추도록 하세요.

멱등 작업을 위한 최신 WAL 위치 보존

중복 처리는 항상 최신 이진 복제 지점을 고려하며, 첫 번째 지점이 아니라 최신 지점을 비교합니다. 이는 두 번째로 예약된 동일한 작업을 삭제하고 Write-Ahead Log (WAL)이 잃어버리기 때문에 발생합니다. 이로 인해 이전 WAL 위치를 비교하고 오래된 복제본에서 읽을 수 있습니다.

중복 처리와 로드 밸런싱과의 데이터 일관성 유지를 동시에 지원하기 위해, 우리는 멱등 작업에 대한 최신 WAL 위치를 Redis에 보존하고 있습니다. 이를 통해 항상 최신 바이너리 복제 지점을 비교하여 완전히 따라 잡힌 복제본에서 읽고 있는지 확인합니다.