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

작업자의 경우 job.perform 대신 perform_multiple 메서드를 직접 사용하십시오 (이 도우미 메서드는 워커에 자동으로 포함됩니다).

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

class IdempotentWorker
  include ApplicationWorker

  # 워커가 멱등하다고 선언하고
  # 여러 번 안전하게 실행할 수 있음을 나타냅니다.
  idempotent!

  # ...
end

워커 클래스를 멱등하다고 표시하는 것이 좋으며, 심지어 perform 메서드가 다른 클래스나 모듈에 정의되어 있더라도 그 클래스의 가장 상위에 idempotent! 호출을 갖도록합니다.

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

중복 제거

멱등한 워커를 위해 작업이 이미 대기 중인 동일한 매개변수를 가진 작업이 추가되면 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

deduplication 시간-지속(Live) 설정

Deduplication은 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에 유지하고 있습니다. 이를 통해 항상 최신 바이너리 복제 지점을 비교하여, 따라잡힌 레플리카에서 읽도록 합니다.