14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファイル削除失敗でメールが無限送信された事故 ― 副作用と状態管理の設計の落とし穴

Last updated at Posted at 2025-12-21

はじめに

現場で実際に遭遇したトラブルの話です。

対象は、

  • 特定フォルダに置かれたテキストファイルを読み込み
  • 内容をメール送信し
  • 処理後にファイルを削除する

という、ごくシンプルな常駐アプリでした。

ところが運用開始後、
ある条件が重なると、同じメールが何通も送信され続ける
という事故が発生しました。

原因の一つには
AntiVirus によるファイルロック という外部要因もありましたが、
本質的な問題はそこではありませんでした。

この記事では、

  • 何が起きていたのか
  • なぜ事故が拡大したのか
  • どう設計を変えたのか

を整理し、設計上の教訓としてまとめます。


何が起きていたのか

運用開始からしばらくして、現場から次のような連絡が入りました。

同じ内容のメールが、短時間に何通も届いている

最初はメールサーバや外部サービスを疑いましたが、
ログを確認すると、アプリ自体はエラーを出していません。

ただし、ログをよく見ると、

  • 特定のファイルだけが
  • 何度も読み込まれている

ように見えました。

処理自体は成功しているように見えるのに、
結果として同じメールが何度も送信されている。

この時点で、
「処理の再実行条件」に問題があるのではないかと考え始めました。

なぜ事故が拡大したのか

フローを整理すると、次のような構造になっていました。

当時の実装では、

  • ファイルが存在している = 未処理
  • ファイルを削除できた = 処理完了

という前提で設計されていました。

そのため、ファイル削除に失敗すると、

  • ファイルは残り続ける
  • 次回の監視処理で再び読み込まれる
  • 再びメールが送信される

というループに入ります。

問題だったのは、
削除失敗という軽微なエラーが、副作用であるメール送信を巻き込んでいた
点です。

ファイル削除が失敗した背景には、AntiVirus の挙動も関係していました。
多くの AV は ファイル作成直後に自動スキャンを行い、
その間は排他ロックを保持します。
OS のファイルロックはプロセス間で非同期に発生するため、
アプリ側からは「たまに削除できない」という形で現れます。

ファイルの「存在そのもの」を処理状態の指標にしていたため、
外部要因(AntiVirus による一時的なロック)に非常に弱い構造でした。


設計をどう見直したか

再設計にあたり、最初に決めたルールは一つだけでした。

同じメールを二度送らない

多少処理が遅れても、
再起動が必要になっても、
メールの多重送信だけは避けたい。

そこで、設計の考え方を次のように切り替えました。

  • ファイルの存在で状態を判断しない
  • 処理対象は必ず「状態」で管理する
  • メール送信は一度きりの副作用として扱う

具体的には、

  • 未処理
  • 処理中
  • 完了

という状態遷移を明示し、
「どこまで進んだか」をファイルの場所や管理情報で表現するようにしました。

改善ポイント

  • 処理対象を 状態管理(未処理 / 処理中 / 完了)に変更
  • メール送信は 1回だけ実行 → 冪等性確保
  • 削除失敗でも無限送信が発生しない
  • エラーはカウンタで管理 → 安全に再起動
  • 再起動で AntiVirus ロックも回避
  • 再起動前に システムエラー通知メール を送信

改善後に得られた効果

設計を見直した結果、

  • 削除に失敗しても、同じメールが再送されない
  • 外部要因があっても事故が拡大しない
  • 異常時は管理者に通知され、状況を把握できる

という状態になりました。

また、一定回数エラーが続いた場合は、

  • システムエラーとして通知
  • アプリを安全に再起動

することで、
AntiVirus による一時的なロックも回避できるようになりました。


設計上の教訓

この事故は、「副作用」と「状態管理」を曖昧にしたことで拡大しました。

副作用は再実行されないよう設計する

メール送信や外部 API 呼び出しなど、
外部に影響を与える処理は一度だけ実行されるべきです。

ファイル存在 = 未処理 は想像以上に脆い

外部プロセスや AV によるロックは、
いつでも起こり得る前提で設計する必要があります。

状態遷移で処理対象を管理する

未処理 → 処理中 → 完了
この流れを明示するだけで、事故の多くは防げます。

リトライは副作用を含めない

再実行するのは「準備」や「状態確認」まで。
副作用は状態管理で制御します。

異常時は必ず人に通知する

自動復旧の前に通知を行うことで、
問題が静かに拡大するのを防げます。


おわりに

今回の事故は、

  • 削除失敗
  • AntiVirus のロック

といった一見よくある事象がきっかけでした。

しかし本質的には、
設計の前提が弱かったことで、事故が増幅した という話です。

再現性の低い外部要因でも安全に動き続けるためには、
状態管理と副作用の扱いを明確にすることが重要だと学びました。

14
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?