Sidekiq偶発ジョブ
ジョブが複数の理由で失敗することはよく知られています。例えば、ネットワーク障害やバグなどです。このアドレスに対応するため、Sidekiqには組み込みのリトライメカニズムがあり、GitLab内のほとんどのワーカーでデフォルトで使用されています。
Sidekiqがジョブをidempotentかつtransactionalにすることを推奨しているのはそのためです。
一般的なルールとして、ワーカーは以下の場合にべき等とみなされます:
- 同じ引数で複数回安全に実行できること。
- アプリケーションの副作用は一度しか起きないと予想されます(あるいは、2回目の実行による副作用は影響しません)。
その良い例がキャッシュ期限切れワーカーです。
idempotentワーカーにスケジュールされたジョブは、同じ引数を持つ未開始のジョブが既にキューにある場合に、重複排除されます。
ワーカーが冪等であることの保証
ワーカーのテストが合格することを、次の共有例を使って確認してください:
include_examples 'an idempotent worker' do
it 'marks the MR as merged' do
# Using subject inside this block will process the job multiple times
subject
expect(merge_request.state).to eq('merged')
end
end
job.perform
の代わりにperform_multiple
メソッドを直接使ってください (このヘルパーメソッドはワーカーに自動的に組み込まれます)。
ワーカーを冪等であると宣言する方法
class IdempotentWorker
include ApplicationWorker
# Declares a worker is idempotent and can
# safely run multiple times.
idempotent!
# ...
end
perform
メソッドが他のクラスやモジュールで定義されている場合でも、idempotent!
呼び出しは一番上のワーカークラスでのみ行うことを推奨します。
ワーカークラスが冪等であるとマークされていない場合、cop は失敗します。ジョブを安全に複数回実行できる自信がない場合は、cop をスキップすることを検討してください。
重複排除
あるidempotent workerのジョブがキューに入っている間に別の未着手のジョブがキューに入ると、GitLabは2番目のジョブを削除します。なぜなら、同じ作業は最初にスケジュールされたジョブが行うからです。2番目のジョブが実行される頃には、1番目のジョブは何もしていないでしょう。
戦略
GitLabは2つの重複排除ストラテジーをサポートしています:
-
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
オプションを渡すと、現在実行中のジョブが終了し、重複排除が少なくとも1回起こった後に、ジョブを1回再実行することができます。これにより、レースコンディションが発生しても、常に最新の結果が生成されます。詳細はこのイシューを参照してください。
将来のジョブのスケジューリング
GitLabは未来にスケジューリングされたジョブをスキップしません。ジョブの実行がスケジューリングされるまでに状態が変化していることを想定しているからです。未来にスケジュールされたジョブの重複排除は、until_executed
とuntil_executing
戦略の両方で可能です。
将来スケジュールされるジョブを重複排除したい場合は、重複排除ストラテジーを定義する際にincluding_scheduled: true
引数を渡すことで、ワーカー側で指定することができます:
module AuthorizedProjectUpdate
class UserRefreshOverUserRangeWorker
include ApplicationWorker
deduplicate :until_executing, including_scheduled: true
idempotent!
# ...
end
end
重複排除のタイムトゥライブの設定(TTL)
重複排除はRedisに保存されるidempotentキーに依存します。これは通常、設定された重複排除ストラテジーによってクリアされます。
しかし、以下のような特定のケースでは、キーがTTLまで残ることがあります:
-
until_executing
が使用されているにもかかわらず、Sidekiqクライアントミドルウェアが実行された後、ジョブがエンキューされることも実行されることもありませんでした。 -
until_executed
が使用されているが、リトライの枯渇によりジョブが終了しなかった、最大回数中断された、または失われた場合。
デフォルト値は6時間です。この時間内は、最初のジョブが実行されなかったり終了しなかったりしても、ジョブはキューに入れられません。
TTLは以下で設定できます:
class ProjectImportScheduleWorker
include ApplicationWorker
idempotent!
deduplicate :until_executing, ttl: 5.minutes
end
TTLに達するとジョブの重複が発生する可能性があるため、多少の重複を許容できるジョブにのみTTLを下げてください。
べき乗ジョブの最新WALロケーションを保持
- GitLab 14.3で導入されました。
- GitLab14.4 でGitLab.com で有効に。
- GitLab 14.6ではセルフマネージドで有効。
- GitLab 14.9で一般的に利用可能に。機能フラグ
preserve_latest_wal_locations_for_idempotent_jobs
を削除しました。
重複排除は常に、最初のポインタではなく最新のバイナリレプリケーションポインタを考慮します。これは、2回目にスケジュールされた同じジョブをドロップし、Write-Ahead Log(WAL) が失われたために起こります。これは、古いWALの場所を比較し、古いレプリカから読み取ることにつながる可能性があります。
重複排除とロードバランシングによるデータの一貫性維持の両方をサポートするために、Redisではidempotentジョブの最新のWALロケーションを保存しています。こうすることで、常に最新のバイナリレプリケーションポインタを比較し、完全に追いついているレプリカから読み込むようにしています。