二重発送事件 — Jobの成功と業務の成功は違う
目標
1つの注文を処理するJobが成功したのに、配送が2回作られるという出来事を再現し、再試行・冪等キー・実行期限を根拠にして説明します。
なぜ重要なのか
completionsとparallelismが1でも、外部の業務が1回だけ処理されるという保証にはなりません。失敗したプロセスが、すでに保存を終えている可能性があるからです。Kubernetesが管理する実行の寿命と、元帳が管理する業務の寿命を分けて観測してこそ、再実行するかどうかを判断できます。
個人VMの本物のk3sと、HTTP・SQLiteの擬似元帳を使います。実際の配送・決済・個人情報はありません。元帳のsafeモードは、サーバーの書き込みトランザクションとビジネスキーの一意性で、重複した行をまとめます。このラボはその契約の適用と検証を扱うもので、分散システム全体で正確に1回の処理を保証するものではありません。元帳のPodを削除すると、データも消えます。
最初の準備には最大6分が許容されます。すべてのファイルは/root/ckad-job-effectsの下に置きます。セッションが終了すると作業内容は消えるため、必要な記録はあらかじめダウンロードしてください。ラボは55分が目安で、さらに必要なら期限が切れる前に時間を延長してください。
使用するテンプレートと実行ヘルパー
job-template.jsonは、実際のworkerコマンドと固定イメージ、権限制限を含むJobのJSONです。ステップごとにコピーして、metadata.name、spec.backoffLimit、コンテナenvのCASE_ID・MODE・FAULTの値を変更します。Podテンプレートのspec.template.metadata.labelsにあるlabhub.io/caseも、そのCASE_IDと同じ値に変更します。WORK_IDはorder-001、POD_UIDはDownward APIのまま維持します。parallelism・completionsは1、restartPolicyはNeverを維持します。他のセキュリティ・アプリの設定も変更しません。期限が必要なステップにだけ、spec.activeDeadlineSecondsを追加します。
直接kubectl createで実行せず、ファイルを作成してからpython3 /opt/fixtures/ckad_job_effects_lab.py run <단계>(プレースホルダーはステップ番号です)を実行します。ヘルパーは学生のファイルをそのままAPIに送信し、実行中のPodの観測を保存します。すでに完了した同じステップをもう一度実行しても、新しいJobは作らず、採点だけを行います。観測の待機が中断された場合は、同じコマンドで再開し、Jobの削除・再作成は行いません。作成のレスポンス自体を失った場合は、重複作成の代わりに、新しいラボの開始を求めます。
ステップ
- baseline.json: name=baseline、CASE_ID=baseline、MODE=unsafe、FAULT=none、backoffLimit=0。run 1で、正常な比較群を観測します。
- unsafe-retry.json: name=unsafe-retry、CASE_ID=unsafe-retry、MODE=unsafe、FAULT=after-commit、backoffLimit=1。run 2で、保存後の失敗と代替Podを観測します。
- observation-2.jsonを読み、retry-analysis.jsonにjob_uid、failed_pod_uid、succeeded_pod_uid、request_count、shipment_count、retry_layerを書きます。retry_layerは、job-controllerまたはkubelet-containerのうち、観測に合うほうの値です。run 3で、分析を記録します。
- safe-retry.json: name=safe-retry、CASE_ID=safe-retry、MODE=safe、FAULT=after-commit、backoffLimit=1。run 4で、同じ失敗でも冪等な結果になることを確認します。
- safe-replay.json: name=safe-replay、CASE_ID=safe-retry、MODE=safe、FAULT=none、backoffLimit=0。run 5で、新しいJobでも同じビジネスキーが引き継がれるかを確認します。
- no-retry.json: name=no-retry、CASE_ID=no-retry、MODE=unsafe、FAULT=after-commit、backoffLimit=0。run 6で、失敗しても配送が残るかを確認します。
- deadline.json: name=deadline、CASE_ID=deadline、MODE=safe、FAULT=deadline、backoffLimit=0、activeDeadlineSeconds=20。run 7で、期限超過と元帳を観測します。現在のPod一覧が空でも、observed_podsにある過去の観測は消しません。
- 6つの実行を比較して、final-analysis.jsonを書きます。request_count・shipment_countは元帳全体の合計で、safe_replay_shipment_idは再実行で維持された配送番号です。timeout_cancels_effectとledger_survives_ledger_pod_replacementは、それぞれ、期限超過が業務を取り消すか、元帳のPodを入れ替えてもデータが保存されるか、のブール値です。idempotency_scopeは、実際の重複判定に使うフィールド名を+でつないで書きます。outcomesには、6つのJob名をキーとして置き、condition(Complete/Failed)とshipments(各実行の直後の、該当する業務の配送数)を書きます。run 8で、総合分析を記録します。
参考
- 現在のAPIと元帳は、
python3 /opt/fixtures/ckad_job_effects_lab.py observeで照会できます。正解のレポートは生成しません。 - observation-N.jsonは、ヘルパーが記録した資料です。podsは収集時点の一覧、observed_podsは実行中に保存した最後のPodの観測です。期限超過で削除されたPodの過去のRunningを、現在の状態や終了コードと解釈しないでください。
- 誤って作成した学生の入力ファイルは、実行前に直せます。完了したステップのファイルと観測は保存してください。同じ名前のJobを削除すると、元帳の履歴と実行の関連が切れます。
- ステップ準備の機能は、異なる業務の先行Jobを同時に実行することがあります。記録の順序は学習の順序であり、実際の開始順序ではありません。観測元帳に別のケースの行が先に現れることがあるので、CASE_IDで分けて比較してください。同じ業務のsafe-replayは、safe-retryの観測が保存されたあとにのみ実行します。準備が中断されたら、案内された同じprepareコマンドで再開し、現在のステップの答案は作りません。
- backoffLimit=0やJobの削除は、業務の取り消しではありません。元帳の行を手で消して、数字を合わせないでください。
正常な配送の比較群
/root/ckad-job-effects/baseline.jsonをテンプレートから作成し、ステップ1の案内にある名前・ビジネスキー・モード・失敗条件・リトライバジェットで、run 1を実行してください。
正常な実行でも、Jobと元帳の観測を別々に残します。
成功したのに2件ある
/root/ckad-job-effects/unsafe-retry.jsonに、保存後の失敗と1回の再試行を宣言し、run 2を実行してください。
Neverは、同じPodのコンテナの再起動ポリシーです。代替PodのUIDを見てください。
犯人はどの再試行か
observation-2.jsonからJob・失敗Pod・成功PodのUIDと、リクエスト・配送の数を読み取り、retry-analysis.jsonに記録して、run 3を実行してください。
同じownerReferencesと、異なるPod UID、restartCountを一緒に比較します。
同じ失敗、1件の配送
/root/ckad-job-effects/safe-retry.jsonに、safeモードと安定したビジネスキーを宣言し、run 4を実行してください。
リクエストが繰り返されても、同じ配送番号につながるかを確認してください。
別のJobで再実行する
/root/ckad-job-effects/safe-replay.jsonを別のJob名で作成し、CASE_ID=safe-retryは維持したまま、run 5を実行してください。
実行IDは変わる必要があり、ビジネスIDは維持される必要があります。
再試行を止めても残るもの
/root/ckad-job-effects/no-retry.jsonに、backoffLimit=0と保存後の失敗を宣言し、run 6を実行してください。
Failed条件は、保存のロールバックを意味しません。
時間切れでも残るもの
/root/ckad-job-effects/deadline.jsonに、activeDeadlineSeconds=20と保存後の待機を宣言し、run 7を実行してください。
実行の終了と業務の取り消しを区別し、削除前のPodの観測を保存してください。
配送事故の総合レポート
ステップ1・2・4・5・6・7の観測から、final-analysis.jsonの合計・再利用された配送番号・取り消しと保存の判断・ビジネスキーの範囲・6つのoutcomesを作成し、run 8を実行してください。
全体のリクエスト数、業務ごとの配送数、実行の成功条件は、別々の指標です。