タスクキュー(実行待ちタスクの並び)に267件のtodoが積まれていました。
在庫指標では「使えるカード180枚」と出ていました。ここでいうカードは、実行に必要な入力ファイルと成果物を記したタスク定義ファイルのことです。
ところが実測すると、構造的に起動可能だったのは22枚だけでした。残り245枚は、処理の中身に入る前に落ちました。内訳は、同名ファイルの重複指定が169枚、存在しないパスの指定が76枚です。例外は1日あたり552件出ていました。
この話の教訓は単純です。
キューの健全性を件数で測ると嘘をつきます。起動可能性で測るべきです。
件数は「在庫」ではなく「未検査の山」だった
「267件ある」「使えるカード180枚ある」という数字は、一見すると前向きな指標です。タスクが枯れていない。処理すれば成果物が出る。そう見えます。
ただし、それは各タスクが起動できるという前提がある場合だけです。
今回の実測値は次の通りでした。
todo総数: 267
構造的に起動可能: 22
構造的に起動不能: 245
- 同名ファイルの重複指定: 169
- 存在しないパスの指定: 76
例外数: 552件/日
在庫指標の表示: 使えるカード180枚
ここで事実として言えるのは、「267件のうち22件だけが構造的に起動可能だった」ということです。
解釈として言うなら、在庫指標は「処理できる仕事の数」ではなく、「まだ実行器に渡していない定義ファイルの数」に近いものを見ていた可能性があります。推測ですが、件数だけをKPIにすると、入力定義の壊れ方を見落としやすくなります。
タスクが失敗すること自体は普通にあります。APIが落ちる、対象サイトのUIが変わる、データが足りない、権限がない。そういう失敗は、業務ロジックに入ってから初めて分かることもあります。
しかし今回の245枚は違います。起動前に落ちています。つまり「処理に失敗したタスク」ではなく、「処理の入口に立てないタスク」です。
起動前例外は業務失敗として数えない
今回の失敗を「タスク実行失敗245件」とだけ集計すると、改善対象を間違えます。
実行器のリトライ回数を増やす。タイムアウトを伸ばす。ワーカーを増やす。ログを詳しくする。そうした対策は、処理中の失敗には効くかもしれません。
でも、入力ファイル指定が壊れているタスクには効きません。
同名ファイルの重複指定は169枚ありました。これは、複数の入力をファイル名だけで扱った結果、一意性が崩れた状態だと考えられます。存在しないパスの指定は76枚ありました。これは、タスク定義が参照している前提と、実際に配置されたファイルの状態がずれている状態です。
どちらも、実行前に検出できます。
たとえばタスク定義に対して、最低限この3つを検査します。
{
"task_id": "example-001",
"inputs": [
{ "role": "source", "path": "inputs/source.md" },
{ "role": "ledger", "path": "inputs/published-ledger.jsonl" }
],
"deliverable": "output.md",
"preflight": {
"require_existing_paths": true,
"require_unique_input_paths": true,
"require_unique_basenames_when_flattened": true
}
}
ここで重要なのは、inputs.length ではなく、各入力が「存在するか」「同じものとして潰れないか」「実行器のステージング方法で一意性を保てるか」を見ることです。
もう少し実装寄りに書くなら、起動前にこういう判定を返します。
type PreflightResult =
| { ok: true; runnable: true }
| {
ok: false;
runnable: false;
reason:
| "missing_input_path"
| "duplicate_input_path"
| "duplicate_basename_after_staging";
detail: string;
};
この結果を持っていれば、「todoは267件あるが、runnableは22件」と言えます。かなり痛い数字ですが、少なくとも嘘ではありません。
見るべき指標は runnable_count
キューの健康診断で最初に見るべきなのは、総件数ではありません。
見るべきなのは、起動可能件数です。
runnable_count = preflightでokになったタスク数
blocked_count = preflightで起動不能と判定されたタスク数
blocked_reason_count = 起動不能理由ごとの件数
今回の数字なら、runnable_count=22、blocked_count=245 です。さらに理由別に、duplicate_input_path=169、missing_input_path=76 のように分けます。
この分解があると、対策の優先順位が変わります。
例外552件/日という数字だけを見ると、実行基盤が不安定に見えます。しかし、起動前に同じ種類の例外を繰り返しているなら、実行基盤を強くするより、タスク投入時の検査を強くするほうが先です。
事実は「1日あたり552件の例外が出ていた」です。解釈は「同じ壊れ方のタスクを何度も起動し直していた可能性が高い」です。この2つは分けて扱います。
投入時に落とす、実行時に悩まない
この種の問題は、ワーカー側で頑張りすぎると悪化します。
ワーカーが起動してから「ファイルがありません」「同名で潰れました」と言うのでは遅い。キューに積む前、またはキューから取り出す直前に、preflightを通すべきです。
実装方針は次のように分けられます。
enqueue時:
タスク定義のスキーマ検証
入力パスの存在確認
ステージング後のbasename衝突確認
dispatch時:
runnable=false のタスクを実行器に渡さない
理由別に隔離する
在庫指標には runnable_count だけを使う
report時:
total_count と runnable_count を分けて表示する
blocked_reason_count を必ず出す
「キューに267件あります」は、運用上ほとんど意味を持ちません。
「267件のうち、起動可能は22件です。245件は入力指定の不正で、169件が同名ファイル重複、76件が存在しないパスです」なら、次に直す場所が分かります。
在庫は、数ではなく起動可能性で測る。
キューが大きく見えているときほど、その数字はまず疑ったほうがいいです。大きいキューは安心材料ではありません。preflightを通っていないキューは、未検査の山です。