TL;DR
- 並行実行しているジョブの成果物を、出力ディレクトリの更新時刻で特定するのは危険
- 「一番新しい」は「一番最近そこで何かが起きた」以上のことを保証しない
- ファイルサイズが「近い」で納得せず、疑わしければハッシュで一致を確認する
- 成果物の特定は、ジョブ自身の完了報告(出力パス・サイズ)に一本化する
何が起きたか
AI による画像生成を複数件、同時並行で走らせていた。それぞれ別のお題を渡していて、完了したものから順に指定フォルダへ保存される。1件あたり数分かかる処理だ。
そのうちの1件だけが、想定より大幅に遅れていた。
待っている間、保存先の親フォルダを覗いた。直近数分以内に作成されたフォルダが1つある。「遅れているジョブの出力だろう」と考えて、中の画像を目的の場所へコピーした。サイズも、先に完了していた別ジョブの出力と近い値だった。
気づいたきっかけ
コピー後、念のため先に完了していたファイルとサイズを比較した。近いのではなく、バイト単位で完全に一致していた。
生成系の出力でバイト数がぴったり揃うのは不自然なので、ハッシュを取った。
$ md5sum a.png b.png
d41d8cd98f00b204e9800998ecf8427e a.png
d41d8cd98f00b204e9800998ecf8427e b.png
一致した。中身が同一のファイルだった。
拾ったのは「新しい生成物」ではなく、先に完了していた別ジョブが内部の一時領域に残した複製だった。待っていたジョブは、この時点でまだ走っていた。
なぜ更新時刻では特定できないのか
並行実行の環境では、次のことが同時に成立しうる。
1. ジョブが、指定した出力先以外にも書き込む
処理系によっては、最終出力とは別に中間生成物やキャッシュを残す。呼び出し側から見ると「頼んでいないフォルダ」が出力先の近くに生える。今回はこれに当たった。
2. 書き込み主が1つとは限らない
同時に N 個走っていれば、書き込みイベントも N 系統ある。「直近に更新された」という事実からは、どのジョブが書いたかを逆算できない。
3. ディレクトリの mtime と、中身の書き込み完了時刻は別物
ディレクトリが作られた時刻は、その中のファイルが書き終わった時刻を意味しない。作成直後に覗けば空、ということもある。
つまり更新時刻は「イベントが起きた時刻」であって「イベントの発生源」ではない。発生源の情報が要るところに、時刻の情報を代入していたのが誤りだった。
対処
成果物の特定を、ファイルシステムの観測からジョブの戻り値に移した。
- 各ジョブの完了報告(保存先パス・ファイルサイズなど)だけを正とする。完了報告が来ていないジョブの成果物は「まだ存在しないもの」として扱う
- 完了前に出力先を覗いてしまった場合、そこにあるファイルは「自分宛てだと未確定のもの」として扱い、コピーしない
- 誤って拾った場合は、上書きや静かな復元ではなくその場で削除して仕切り直す。中途半端に残すと次の判断でまた同じものを掴む
判定を強めたいなら、ジョブ側の出力パスをジョブ ID で分ける(out/<job_id>/)のが根本的だ。共有ディレクトリに複数ジョブが書き込む構成そのものが、この曖昧さの発生源になっている。
まとめ
| 症状 | 原因 | 対処 |
|---|---|---|
| 最新のファイル=目的の成果物だと判断した | 更新時刻は発生源を保証しない | ジョブの完了報告を正とする |
| サイズが近いので同一だと判断した | 「近い」と「同じ」を区別していなかった | ハッシュで完全一致を確認する |
| 別ジョブの複製を拾った | 共有ディレクトリに複数ジョブが書き込んでいた | 出力先をジョブ単位で分離する |
並行実行は速い。ただしその速さは、「いま見えているものが自分の待っていた結果か」を確認する手間とセットで来る。急いでいるときほどその確認を飛ばしたくなるが、飛ばした先に事故がある。