通知台帳を機ごと・日ごとの鍵で非公開ストレージへ集約した
今回やったこと
無人運用の通知をDiscordの画面だけに残すと、後から全体像を確認しにくい。複数の機体が別々に通知を送る構成では、1台の日報を見ても全体の一部しか分からない。そこで、各機の通知台帳をローカルで記録し、後から機ごと・日ごとのJSONLとして非公開ストレージへ集約するレーンを追加した。
対象は通知を送る仕組みそのものの変更ではなく、送信後の記録を安全に集める処理である。送信経路と保管経路を分けることで、保管先の障害が通知の送信まで止めない構成にしている。
1つの台帳へ追記しない理由
通知台帳は機ごとにローカルのJSONLへ追記される。集約時にはその行を読み、タイムスタンプの先頭10文字から機体の現地日付でグループ化する。保存先の鍵は次の形になる。
state/notify_ledger/<host>/<YYYY-MM-DD>.jsonl
複数台が同じオブジェクトへ並行して上書きする方式には、オブジェクトストレージ側の排他制御がないため更新が失われる問題がある。そこで、機体名と日付を鍵に含め、各機が自分の鍵だけを書き換えるようにした。日付で分割すれば、1つのオブジェクトが無制限に大きくなることも避けられる。実装では対象日の行をまとめて上書きするため、同じ内容の再実行は冪等に扱える。
日付はUTCへ変換しない。日報を読む人にとっては、機体の現地時間で区切られたほうが自然だからである。機体ごとに鍵が分かれているため、現地日付の定義が機体間で異なっても、異なる台帳が1つに混ざることはない。
送信と記録を分離する
通知の送信はこれまでどおりローカルで完結する。notify_ledger_ship.py はローカル台帳を後から読み、壊れたJSON行を落とし、読める行だけを対象にアップロードする。途中で切れた1行のために、台帳全体の送信を諦めない設計である。
この分離には、障害時の振る舞いを単純にする効果がある。非公開ストレージが落ちても、Discord通知とローカル台帳の追記は継続する。送れなかった日付は成功扱いで控えに記録されないため、次の実行で再試行される。複数日のうち1日が失敗しても、他の日の送信結果を巻き込まない。
同じ内容を30分ごとに上げ直さないため、日付ごとのハッシュを控えに保存する。--force を指定した場合だけ、内容が変わっていない日も再送できる。台帳が空の日は異常とせず、何もアップロードしない。通知が0件だったことと、通知台帳が届いていないことは、読む側が鮮度で判定する責務に分けている。
運用情報は非公開バケットへ置く
通知本文には、運用上の情報がそのまま含まれる。そのため保存先は公開配信バケットではなく、退避専用の非公開バケットに限定する。設定が不足している場合、実装は黙って公開バケットへ切り替えず、FATALとして停止する。届いていない状態が見えることを、誤って公開することより優先した判断である。
BACKUP_STORAGE_BUCKET
BACKUP_STORAGE_ACCESS_KEY_ID
BACKUP_STORAGE_SECRET_ACCESS_KEY
台帳の鍵は state/notify_ledger/ 配下に固定し、書き込み直前に安全性を検査する。共有しないものも明確にした。24時間の通知抑制に使うローカル状態は集約対象に含めない。これを共有すると、ある機体で発生した異常が別の機体の抑制状態に影響し、通知の意味が変わるためである。
テストで固定した契約
回帰テストでは、機ごと・日ごとの鍵への分割、実在する新しい日からの対象選択、壊れた行の除外、同じ内容の再送抑制、強制再送、日単位の失敗と再試行、空台帳の扱いを確認する。さらに、非公開バケット未設定時に停止すること、送信経路がストレージに依存していないこと、レーンが起動設定と設定台帳に登録されていることも検査する。
実装だけを置いても、起動設定に登録されていなければレーンは動かない。逆に、動いていても送信経路と記録経路が結合していれば、保管障害が通知停止へ波及する。今回の集約は、保存形式・プライバシー境界・再試行・起動配線を1つの契約としてテストに残した点に意味がある。
試した環境・バージョンは、リポジトリ内の Python 実装と、ネットワークを使わないモックストレージの回帰テストである。実ストレージへ送る前に、ローカルで日付分割と失敗時の状態更新を検証できる。
更新履歴
- 2026-09-13: 初稿。通知台帳の機体・日付分割と非公開保管を記録。
訂正履歴
- なし。