はじめに
現場で実際に遭遇したトラブルの話です。
対象は、
- 特定フォルダに置かれたテキストファイルを読み込み
- 内容をメール送信し
- 処理後にファイルを削除する
という、ごくシンプルな常駐アプリでした。
ところが運用開始後、
ある条件が重なると、同じメールが何通も送信され続ける
という事故が発生しました。
原因の一つには
AntiVirus によるファイルロック という外部要因もありましたが、
本質的な問題はそこではありませんでした。
この記事では、
- 何が起きていたのか
- なぜ事故が拡大したのか
- どう設計を変えたのか
を整理し、設計上の教訓としてまとめます。
何が起きていたのか
運用開始からしばらくして、現場から次のような連絡が入りました。
同じ内容のメールが、短時間に何通も届いている
最初はメールサーバや外部サービスを疑いましたが、
ログを確認すると、アプリ自体はエラーを出していません。
ただし、ログをよく見ると、
- 特定のファイルだけが
- 何度も読み込まれている
ように見えました。
処理自体は成功しているように見えるのに、
結果として同じメールが何度も送信されている。
この時点で、
「処理の再実行条件」に問題があるのではないかと考え始めました。
なぜ事故が拡大したのか
フローを整理すると、次のような構造になっていました。
当時の実装では、
- ファイルが存在している = 未処理
- ファイルを削除できた = 処理完了
という前提で設計されていました。
そのため、ファイル削除に失敗すると、
- ファイルは残り続ける
- 次回の監視処理で再び読み込まれる
- 再びメールが送信される
というループに入ります。
問題だったのは、
削除失敗という軽微なエラーが、副作用であるメール送信を巻き込んでいた
点です。
ファイル削除が失敗した背景には、AntiVirus の挙動も関係していました。
多くの AV は ファイル作成直後に自動スキャンを行い、
その間は排他ロックを保持します。
OS のファイルロックはプロセス間で非同期に発生するため、
アプリ側からは「たまに削除できない」という形で現れます。
ファイルの「存在そのもの」を処理状態の指標にしていたため、
外部要因(AntiVirus による一時的なロック)に非常に弱い構造でした。
設計をどう見直したか
再設計にあたり、最初に決めたルールは一つだけでした。
同じメールを二度送らない
多少処理が遅れても、
再起動が必要になっても、
メールの多重送信だけは避けたい。
そこで、設計の考え方を次のように切り替えました。
- ファイルの存在で状態を判断しない
- 処理対象は必ず「状態」で管理する
- メール送信は一度きりの副作用として扱う
具体的には、
- 未処理
- 処理中
- 完了
という状態遷移を明示し、
「どこまで進んだか」をファイルの場所や管理情報で表現するようにしました。
改善ポイント
- 処理対象を 状態管理(未処理 / 処理中 / 完了)に変更
- メール送信は 1回だけ実行 → 冪等性確保
- 削除失敗でも無限送信が発生しない
- エラーはカウンタで管理 → 安全に再起動
- 再起動で AntiVirus ロックも回避
- 再起動前に システムエラー通知メール を送信
改善後に得られた効果
設計を見直した結果、
- 削除に失敗しても、同じメールが再送されない
- 外部要因があっても事故が拡大しない
- 異常時は管理者に通知され、状況を把握できる
という状態になりました。
また、一定回数エラーが続いた場合は、
- システムエラーとして通知
- アプリを安全に再起動
することで、
AntiVirus による一時的なロックも回避できるようになりました。
設計上の教訓
この事故は、「副作用」と「状態管理」を曖昧にしたことで拡大しました。
副作用は再実行されないよう設計する
メール送信や外部 API 呼び出しなど、
外部に影響を与える処理は一度だけ実行されるべきです。
ファイル存在 = 未処理 は想像以上に脆い
外部プロセスや AV によるロックは、
いつでも起こり得る前提で設計する必要があります。
状態遷移で処理対象を管理する
未処理 → 処理中 → 完了
この流れを明示するだけで、事故の多くは防げます。
リトライは副作用を含めない
再実行するのは「準備」や「状態確認」まで。
副作用は状態管理で制御します。
異常時は必ず人に通知する
自動復旧の前に通知を行うことで、
問題が静かに拡大するのを防げます。
おわりに
今回の事故は、
- 削除失敗
- AntiVirus のロック
といった一見よくある事象がきっかけでした。
しかし本質的には、
設計の前提が弱かったことで、事故が増幅した という話です。
再現性の低い外部要因でも安全に動き続けるためには、
状態管理と副作用の扱いを明確にすることが重要だと学びました。