設備監視CSVを途中で壊さない:Pythonの一時ファイルとos.replace
なぜ直接上書きしないのか
`open("equipment.csv", "w")` は既存の内容を切り詰めます。途中でセンサ取得に失敗したり、入力に不正値が見つかったりすると、半端なCSVが残り得ます。監視画面がそのCSVを読む場合、正常な前回のデータまで失う可能性があります。
代わりに、同じディレクトリに一時ファイルを作成し、全レコードを検証して書き終わってから `os.replace()` で置き換えます。Python公式資料では、POSIX上で成功した置換は原子的とされています。別ファイルシステム間では失敗し得るため、一時ファイルの作成先を対象と同じディレクトリにします。
実装
公開した atomic_csv_save.py に、Python標準ライブラリのみで動く `write_snapshot(path, records)` を収録しました。主要な処理は次のとおりです。
fd, temp_path = tempfile.mkstemp(dir=target.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="") as stream:
writer = csv.writer(stream, lineterminator="\n")
writer.writerow(["timestamp", "status"])
# ここで各行を検証し、writer.writerow(...) で書き込む
stream.flush()
os.fsync(stream.fileno())
os.replace(temp_path, target)
except BaseException:
try:
os.unlink(temp_path)
except FileNotFoundError:
pass
raise
この抜粋は処理の概略です。実行する場合は検証を含む完全版コードを使用してください。テストコードも公開しています。完全版では入力日時をタイムゾーン付き `datetime` に限定し、状態を `RUNNING`、`STOPPED`、`UNKNOWN` の3種類に制限します。日時は秒精度で出力するため、ミリ秒が必要な場合は仕様変更が必要です。
動作確認
2026年10月18日、MacのPython 3.12で `python3 -m unittest discover -p 'test_atomic_csv_save.py' -v` を実行し、6件すべて成功しました。確認したのは新規保存、全件置換、不正な状態値、タイムゾーンなし日時、途中例外、空レコードです。エラー時に前回のCSVが残り、一時ファイルが削除されることも確認しました。
これは模擬データの単体テストであり、実PLCやネットワークドライブ、Windowsのファイルロック、電源断の試験ではありません。
実運用の注意
• `os.replace()` は既存ファイルを上書きします。バックアップや履歴が必要なら別途保存してください。
• 複数の書き手が同時に更新すると、最後の更新で前の内容が失われ得ます。単一ライターや排他制御が必要です。
• `flush()` と `os.fsync()` を行っても、親ディレクトリの同期やストレージの特性まで含む停電耐性は保証できません。
• 置換後のファイルの権限・所有者・ACLは旧ファイルと同一とは限りません。
• 入力件数の上限、時刻の単調増加、設備ID、ファイルサイズ上限などは別途検証してください。
• 既に開いている読み手は旧ファイルを読み続ける場合があります。更新を確認する際は開き直してください。
一次資料
• Python公式 `os.replace`:https://docs.python.org/3/library/os.html#os.replace
• Python公式 `os.fsync`:https://docs.python.org/3/library/os.html#os.fsync
• Python公式 `tempfile.mkstemp`:https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp
• Python公式 `csv.writer`:https://docs.python.org/3/library/csv.html#csv.writer
関連記事(既公開):設備の稼働・停止を記録するCSVの設計(`csv-state-logging.html`)。
※コード抜粋は実装の概略です。動作環境や権限、同時書き込みには注意してください。