二重に請求してしまった処理
チェックポイントとは、すでに終わった仕事を保存した記録です。これがあれば、長い処理を止めても、最初からやり直さずに再開できます。エージェントは数分から数時間動き、プロセスは途中で落ちます。
ある処理が5件の注文に請求しました。3件目で落ち、最初からやり直しました。2人の客が二重に支払いました。
このレッスンのゴール
メモ
長い処理を、安全に止めて再開できるようにできる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| チェックポイント | 終わった仕事を保存した記録 | |
| 冪等 | 2回実行しても、効果が1回分で済むこと | |
| 再開 | 最後のチェックポイントから続けること | |
| 副作用 | プログラムの外に起きる変化 | 請求 |
| イベントログ | 起きたことを順に並べた一覧 |
学生が入力するセルは、このプレビューでは空白です。
チェックポイントは終わったことを示す
ステップが終わるたびに、その名前をファイルか表に書きます。開始するときにその記録を読み、すでに終わったものを飛ばします。
ステップの前に開始、後に完了を書きます。すると再開のとき、一度も始まっていないステップと、途中で切れたステップを区別できます。
例: 障害が起きる場所
| 障害の場所 | 記録の内容 | 再開のとき |
|---|---|---|
| ステップの開始前 | そのステップのことは何もない | 実行する |
| ステップの中、効果の前 | 開始 | 再試行する |
| 効果のあと、完了の前 | 開始 | 再試行する。効果が繰り返される |
| 完了のあと | 完了 | 飛ばす |
学生が入力するセルは、このプレビューでは空白です。
再開できる処理を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const fs = require(\"fs\");' 'const CHECK = \"demo/checkpoint.json\";' 'const LOG = \"demo/charges.log\";' 'const orders = [\"O1\", \"O2\", \"O3\", \"O4\", \"O5\"];' 'const crashAt = process.argv[2];' 'fs.mkdirSync(\"demo\", { recursive: true });' 'function load() {' ' try { return JSON.parse(fs.readFileSync(CHECK, \"utf8\")); } catch (error) { return { done: [], started: null }; }' '}' 'function save(state) { fs.writeFileSync(CHECK, JSON.stringify(state)); }' 'function charge(id) {' ' const seen = fs.existsSync(LOG) ? fs.readFileSync(LOG, \"utf8\").split(\"\n\") : [];' ' if (seen.indexOf(id) >= 0) { console.log(id + \": 請求済みなので、もう一度は請求しません\"); return; }' ' fs.appendFileSync(LOG, id + \"\n\");' '}' > batch1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
再開できる処理を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const state = load();' 'console.log(\"ここまでに完了: \" + (state.done.join(\",\") || \"なし\") + \"。中断した場所: \" + state.started);' 'for (const id of orders) {' ' if (state.done.indexOf(id) >= 0) { console.log(id + \": 完了済みなので飛ばします\"); continue; }' ' state.started = id;' ' save(state);' ' charge(id);' ' if (id === crashAt) { console.log(id + \" の作業中に障害が起きました\"); process.exit(1); }' ' state.done.push(id);' ' state.started = null;' ' save(state);' ' console.log(id + \": 完了\");' '}' 'console.log(\"終了。請求の回数: \" + fs.readFileSync(LOG, \"utf8\").trim().split(\"\n\").length);' >> batch1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
最初から実行して O3 で落とす
docker exec -w /home/node/project agent sh -c "rm -rf demo; node batch1.js O3"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
もう一度実行して再開する
docker exec -w /home/node/project agent node batch1.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出力の一例
| 実行 | 表示される内容 |
|---|---|
| 1回目 | O1 完了、O2 完了、そのあと O3 の作業中に障害 |
| 2回目 | 中断した場所は O3、O1 と O2 は飛ばされる |
| 2回目の O3 | 請求済みなので、もう一度は請求せず、そのあと完了 |
| 最後の行 | 終了。請求の回数: 5。注文ごとに1回 |
学生が入力するセルは、このプレビューでは空白です。
各ステップを、繰り返しても安全にする
ステップが冪等とは、2回実行しても、1回実行したのと同じ効果になることです。効果ごとに、注文番号のようなキーを付けます。動く前に、そのキーがすでに使われたかを確かめます。
外部のサービスが対応しているなら、リクエストにキーを付けて送ります。サービス側が、繰り返しを断れます。
よくある間違い
注意
「チェックポイントがあれば、再試行は安全だ」と考える人が多くいます。チェックポイントは、終わったことを記録するだけです。効果のあとで切れたステップは再実行されるので、ステップ自体が繰り返しに安全でなければなりません。記録と冪等なステップを、両方使います。