InfoGrab DocsInfoGrab Docs

Sidekiq 멱등 job

요약

job은 네트워크 장애나 버그 등 다양한 이유로 실패할 수 있습니다. job이 실패 후 다시 실행되더라도 애플리케이션이나 사용자에게 큰 부작용이 없어야 하므로, Sidekiq은 job을 멱등적이고 트랜잭션적으로 만들도록 권장합니다.

job은 네트워크 장애나 버그 등 다양한 이유로 실패할 수 있습니다. 이를 해결하기 위해 Sidekiq에는 기본 재시도 메커니즘이 내장되어 있으며, GitLab 내 대부분의 워커는 이를 기본으로 사용합니다.

job이 실패 후 다시 실행되더라도 애플리케이션이나 사용자에게 큰 부작용이 없어야 하므로, Sidekiq은 job을 멱등적이고 트랜잭션적으로 만들도록 권장합니다.

일반적으로 워커를 멱등적으로 간주할 수 있는 조건은 다음과 같습니다:

  • 동일한 인수로 여러 번 안전하게 실행할 수 있어야 합니다.

  • 애플리케이션의 부작용은 한 번만 발생해야 합니다 (또는 두 번째 실행의 부작용이 영향을 미치지 않아야 합니다).

좋은 예로 캐시 만료 워커를 들 수 있습니다.

멱등 워커에 예약된 job은, 동일한 인수를 가진 아직 시작되지 않은 job이 이미 큐에 있을 경우 중복 제거됩니다.

워커가 멱등적임을 보장하기#

다음 공유 예제를 사용하여 job을 두 번 실행했을 때의 영향을 확인하세요.

it_behaves_like 'an idempotent worker'

공유 예제는 job_args가 정의되어 있어야 합니다. 정의되지 않은 경우 인수 없이 job을 호출합니다.

공유 예제가 실행될 때, job의 부작용을 피하는 mock이 설정되어 있으면 안 됩니다. 예를 들어, 워커가 서비스의 execute 메서드를 스텁하지 않고 호출할 수 있도록 허용해야 합니다. 이렇게 해야 job이 진정으로 멱등적임을 검증할 수 있습니다.

공유 예제에는 몇 가지 기본 테스트가 포함되어 있습니다. 공유 예제 블록 내에 워커에 특화된 멱등성 테스트를 추가할 수도 있습니다.

it_behaves_like 'an idempotent worker' do
  it 'checks the side-effects for multiple calls' do
    # `perform_idempotent_work` will call the job's perform method 2 times
    perform_idempotent_work

    expect(model.state).to eq('state')
  end
end

워커를 멱등적으로 선언하기#

class IdempotentWorker
  include ApplicationWorker

  # Declares a worker is idempotent and can
  # safely run multiple times.
  idempotent!

  # ...
end

idempotent! 호출은 perform 메서드가 다른 클래스나 모듈에 정의되어 있더라도 최상위 워커 클래스에만 두는 것을 권장합니다.

워커 클래스에 멱등 표시가 없으면 cop이 실패합니다. job이 여러 번 안전하게 실행될 수 없다고 판단되면 cop을 건너뛰는 것을 고려하세요.

중복 제거#

멱등 워커에 대한 job이 큐에 추가될 때, 아직 시작되지 않은 동일한 job이 이미 큐에 있다면 GitLab은 두 번째 job을 삭제합니다. 처음 예약된 job이 동일한 작업을 수행할 것이므로 작업이 건너뛰어집니다. 두 번째 job이 실행될 시점에는 첫 번째 job이 아무것도 하지 않을 것이기 때문입니다.

전략#

GitLab은 두 가지 중복 제거 전략을 지원합니다:

  • until_executing — 기본 전략

  • until_executed

더 많은 중복 제거 전략이 제안되어 있습니다. 다른 전략이 도움이 될 수 있는 워커를 구현하고 있다면 해당 이슈에 댓글을 남겨 주세요.

Until Executing#

이 전략은 job이 큐에 추가될 때 락을 획득하고, job이 시작되기 전에 락을 해제합니다.

예를 들어, AuthorizedProjectsWorker는 사용자 ID를 인수로 받습니다. 워커가 실행되면 사용자의 인가 정보를 재계산합니다. GitLab은 사용자의 인가 정보를 변경할 가능성이 있는 액션이 발생할 때마다 이 job을 예약합니다. 동일한 사용자가 동시에 두 프로젝트에 추가되면, 첫 번째 job이 아직 시작되지 않았을 경우 두 번째 job을 건너뛸 수 있습니다. 첫 번째 job이 실행될 때 두 프로젝트 모두에 대한 인가 정보를 생성하기 때문입니다.

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker

    deduplicate :until_executing
    idempotent!

    # ...
  end
end

Until Executed#

이 전략은 job이 큐에 추가될 때 락을 획득하고, job이 완료된 후 락을 해제합니다. 여러 번 동시에 job이 실행되는 것을 방지하는 데 사용할 수 있습니다.

module Ci
  class BuildTraceChunkFlushWorker
    include ApplicationWorker

    deduplicate :until_executed
    idempotent!

    # ...
  end
end

또한 if_deduplicated: :reschedule_once 옵션을 전달하면, 현재 실행 중인 job이 완료되고 중복 제거가 한 번 이상 발생한 경우 job을 한 번 재실행할 수 있습니다. 이를 통해 경쟁 조건이 발생하더라도 항상 최신 결과가 생성되도록 보장합니다. 자세한 내용은 이 이슈를 참조하세요.

미래에 예약된 job#

GitLab은 미래에 예약된 job은 건너뛰지 않습니다. job이 예약된 실행 시점까지 상태가 변경되었을 것으로 가정하기 때문입니다. 미래에 예약된 job의 중복 제거는 until_executeduntil_executing 전략 모두에서 가능합니다.

미래에 예약된 job을 중복 제거하려면, 중복 제거 전략을 정의할 때 including_scheduled: true 인수를 전달하여 워커에 지정할 수 있습니다:

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker

    deduplicate :until_executing, including_scheduled: true
    idempotent!

    # ...
  end
end

중복 제거 TTL(Time-to-Live) 설정#

중복 제거는 Redis에 저장되는 멱등 키에 의존합니다. 이 키는 일반적으로 구성된 중복 제거 전략에 의해 삭제됩니다.

그러나 다음과 같은 특정 상황에서는 TTL이 만료될 때까지 키가 남아 있을 수 있습니다:

  • until_executing을 사용하지만 Sidekiq 클라이언트 미들웨어가 실행된 후 job이 큐에 추가되거나 실행되지 않은 경우.

  • until_executed를 사용하지만 재시도 횟수 초과로 인해 job이 완료에 실패하거나, 최대 횟수만큼 중단되거나, 유실된 경우.

기본값은 10분입니다. 이 시간 동안 첫 번째 job이 실행되지 않았거나 완료되지 않았더라도 job이 큐에 추가되지 않습니다.

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

class ProjectImportScheduleWorker
  include ApplicationWorker

  idempotent!
  deduplicate :until_executing, ttl: 5.minutes
end

TTL에 도달하면 중복 job이 발생할 수 있으므로, 일부 중복을 허용할 수 있는 job에 대해서만 이 값을 낮추세요.

멱등 job에 대한 최신 WAL 위치 보존#

중복 제거는 항상 첫 번째 바이너리 복제 포인터가 아닌 최신 바이너리 복제 포인터를 고려합니다. 동일한 job이 두 번째로 예약되면 삭제되어 Write-Ahead Log(WAL)가 유실되기 때문입니다. 이로 인해 오래된 WAL 위치와 비교하거나 지연된 복제본에서 읽는 문제가 발생할 수 있습니다.

중복 제거와 로드 밸런싱의 데이터 일관성 유지를 모두 지원하기 위해, Redis에 멱등 job의 최신 WAL 위치를 보존합니다. 이를 통해 항상 최신 바이너리 복제 포인터와 비교하여, 완전히 동기화된 복제본에서 읽도록 보장합니다.

Sidekiq 멱등 job

GitLab v19.1
원문 보기
요약

job은 네트워크 장애나 버그 등 다양한 이유로 실패할 수 있습니다. job이 실패 후 다시 실행되더라도 애플리케이션이나 사용자에게 큰 부작용이 없어야 하므로, Sidekiq은 job을 멱등적이고 트랜잭션적으로 만들도록 권장합니다.

job은 네트워크 장애나 버그 등 다양한 이유로 실패할 수 있습니다. 이를 해결하기 위해 Sidekiq에는 기본 재시도 메커니즘이 내장되어 있으며, GitLab 내 대부분의 워커는 이를 기본으로 사용합니다.

job이 실패 후 다시 실행되더라도 애플리케이션이나 사용자에게 큰 부작용이 없어야 하므로, Sidekiq은 job을 멱등적이고 트랜잭션적으로 만들도록 권장합니다.

일반적으로 워커를 멱등적으로 간주할 수 있는 조건은 다음과 같습니다:

  • 동일한 인수로 여러 번 안전하게 실행할 수 있어야 합니다.

  • 애플리케이션의 부작용은 한 번만 발생해야 합니다 (또는 두 번째 실행의 부작용이 영향을 미치지 않아야 합니다).

좋은 예로 캐시 만료 워커를 들 수 있습니다.

멱등 워커에 예약된 job은, 동일한 인수를 가진 아직 시작되지 않은 job이 이미 큐에 있을 경우 중복 제거됩니다.

워커가 멱등적임을 보장하기#

다음 공유 예제를 사용하여 job을 두 번 실행했을 때의 영향을 확인하세요.

it_behaves_like 'an idempotent worker'

공유 예제는 job_args가 정의되어 있어야 합니다. 정의되지 않은 경우 인수 없이 job을 호출합니다.

공유 예제가 실행될 때, job의 부작용을 피하는 mock이 설정되어 있으면 안 됩니다. 예를 들어, 워커가 서비스의 execute 메서드를 스텁하지 않고 호출할 수 있도록 허용해야 합니다. 이렇게 해야 job이 진정으로 멱등적임을 검증할 수 있습니다.

공유 예제에는 몇 가지 기본 테스트가 포함되어 있습니다. 공유 예제 블록 내에 워커에 특화된 멱등성 테스트를 추가할 수도 있습니다.

it_behaves_like 'an idempotent worker' do
  it 'checks the side-effects for multiple calls' do
    # `perform_idempotent_work` will call the job's perform method 2 times
    perform_idempotent_work

    expect(model.state).to eq('state')
  end
end

워커를 멱등적으로 선언하기#

class IdempotentWorker
  include ApplicationWorker

  # Declares a worker is idempotent and can
  # safely run multiple times.
  idempotent!

  # ...
end

idempotent! 호출은 perform 메서드가 다른 클래스나 모듈에 정의되어 있더라도 최상위 워커 클래스에만 두는 것을 권장합니다.

워커 클래스에 멱등 표시가 없으면 cop이 실패합니다. job이 여러 번 안전하게 실행될 수 없다고 판단되면 cop을 건너뛰는 것을 고려하세요.

중복 제거#

멱등 워커에 대한 job이 큐에 추가될 때, 아직 시작되지 않은 동일한 job이 이미 큐에 있다면 GitLab은 두 번째 job을 삭제합니다. 처음 예약된 job이 동일한 작업을 수행할 것이므로 작업이 건너뛰어집니다. 두 번째 job이 실행될 시점에는 첫 번째 job이 아무것도 하지 않을 것이기 때문입니다.

전략#

GitLab은 두 가지 중복 제거 전략을 지원합니다:

  • until_executing — 기본 전략

  • until_executed

더 많은 중복 제거 전략이 제안되어 있습니다. 다른 전략이 도움이 될 수 있는 워커를 구현하고 있다면 해당 이슈에 댓글을 남겨 주세요.

Until Executing#

이 전략은 job이 큐에 추가될 때 락을 획득하고, job이 시작되기 전에 락을 해제합니다.

예를 들어, AuthorizedProjectsWorker는 사용자 ID를 인수로 받습니다. 워커가 실행되면 사용자의 인가 정보를 재계산합니다. GitLab은 사용자의 인가 정보를 변경할 가능성이 있는 액션이 발생할 때마다 이 job을 예약합니다. 동일한 사용자가 동시에 두 프로젝트에 추가되면, 첫 번째 job이 아직 시작되지 않았을 경우 두 번째 job을 건너뛸 수 있습니다. 첫 번째 job이 실행될 때 두 프로젝트 모두에 대한 인가 정보를 생성하기 때문입니다.

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker

    deduplicate :until_executing
    idempotent!

    # ...
  end
end

Until Executed#

이 전략은 job이 큐에 추가될 때 락을 획득하고, job이 완료된 후 락을 해제합니다. 여러 번 동시에 job이 실행되는 것을 방지하는 데 사용할 수 있습니다.

module Ci
  class BuildTraceChunkFlushWorker
    include ApplicationWorker

    deduplicate :until_executed
    idempotent!

    # ...
  end
end

또한 if_deduplicated: :reschedule_once 옵션을 전달하면, 현재 실행 중인 job이 완료되고 중복 제거가 한 번 이상 발생한 경우 job을 한 번 재실행할 수 있습니다. 이를 통해 경쟁 조건이 발생하더라도 항상 최신 결과가 생성되도록 보장합니다. 자세한 내용은 이 이슈를 참조하세요.

미래에 예약된 job#

GitLab은 미래에 예약된 job은 건너뛰지 않습니다. job이 예약된 실행 시점까지 상태가 변경되었을 것으로 가정하기 때문입니다. 미래에 예약된 job의 중복 제거는 until_executeduntil_executing 전략 모두에서 가능합니다.

미래에 예약된 job을 중복 제거하려면, 중복 제거 전략을 정의할 때 including_scheduled: true 인수를 전달하여 워커에 지정할 수 있습니다:

module AuthorizedProjectUpdate
  class UserRefreshOverUserRangeWorker
    include ApplicationWorker

    deduplicate :until_executing, including_scheduled: true
    idempotent!

    # ...
  end
end

중복 제거 TTL(Time-to-Live) 설정#

중복 제거는 Redis에 저장되는 멱등 키에 의존합니다. 이 키는 일반적으로 구성된 중복 제거 전략에 의해 삭제됩니다.

그러나 다음과 같은 특정 상황에서는 TTL이 만료될 때까지 키가 남아 있을 수 있습니다:

  • until_executing을 사용하지만 Sidekiq 클라이언트 미들웨어가 실행된 후 job이 큐에 추가되거나 실행되지 않은 경우.

  • until_executed를 사용하지만 재시도 횟수 초과로 인해 job이 완료에 실패하거나, 최대 횟수만큼 중단되거나, 유실된 경우.

기본값은 10분입니다. 이 시간 동안 첫 번째 job이 실행되지 않았거나 완료되지 않았더라도 job이 큐에 추가되지 않습니다.

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

class ProjectImportScheduleWorker
  include ApplicationWorker

  idempotent!
  deduplicate :until_executing, ttl: 5.minutes
end

TTL에 도달하면 중복 job이 발생할 수 있으므로, 일부 중복을 허용할 수 있는 job에 대해서만 이 값을 낮추세요.

멱등 job에 대한 최신 WAL 위치 보존#

중복 제거는 항상 첫 번째 바이너리 복제 포인터가 아닌 최신 바이너리 복제 포인터를 고려합니다. 동일한 job이 두 번째로 예약되면 삭제되어 Write-Ahead Log(WAL)가 유실되기 때문입니다. 이로 인해 오래된 WAL 위치와 비교하거나 지연된 복제본에서 읽는 문제가 발생할 수 있습니다.

중복 제거와 로드 밸런싱의 데이터 일관성 유지를 모두 지원하기 위해, Redis에 멱등 job의 최신 WAL 위치를 보존합니다. 이를 통해 항상 최신 바이너리 복제 포인터와 비교하여, 완전히 동기화된 복제본에서 읽도록 보장합니다.