Sidekiq 멱등 job
GitLab v19.1job은 네트워크 장애나 버그 등 다양한 이유로 실패할 수 있습니다. 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_executed 및 until_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 위치를 보존합니다. 이를 통해 항상 최신 바이너리 복제 포인터와 비교하여, 완전히 동기화된 복제본에서 읽도록 보장합니다.