本文へ移動
松葉 / Matsuba ・ 開発過程資料

長期保管/ Long-term Storage

2 年を超えた月を、検証してから外へ移す

記録は 2 年保持です。それを超えた分をデータベースに置き続けるとテーブルが膨らみ続けるので、月ごとに外へ移しています。この章の主題は「移す」ことよりも、移し終わったことをどう確かめてから消すかです。消したあとに「移せていなかった」と分かっても、取り返す手段はありません。

01

2 年で線を引くTwo Years

画面から引ける範囲は 2 年です。切り離しの設定は 25 ヶ月で、1 ヶ月ぶん余裕を持たせています。

Two Numbers, One Month Apart
値意味
2 年(24 ヶ月)「この期間は必ず辿れる」という業務上の約束
25 ヶ月データベースから切り離す設定値。約束の 1 ヶ月先

差を付けているのは、切り離しが週に 1 回しか動かないためです。24 ヶ月ちょうどで設定すると、約束の期間内の月が、次の整備が回るまでの数日のあいだ「切り離し待ち」に入る余地が生まれます。1 ヶ月の余裕があれば、その隙間は構造的に生じません。

02

子テーブルの作成と切り離しPremake & Detach

月ごとの子テーブルを作ること、そして古い月を切り離すことは、データベースの中の拡張機能が受け持っています。退避と削除を受け持つ月次のバッチとは、担い手が分かれています。

Who Does What
担い手周期やること
データベース内の拡張機能
(pg_partman + pg_cron)
週次
数ヶ月先までの子テーブルを作る/25 ヶ月を超えた月を親から切り離し、退避待ちの置き場(別のスキーマ)へ移す
月次のバッチ
(audit-log-partition-rotator)
月次
先の月の残数を点検する/退避待ちを圧縮して外へ置く/突き合わせて台帳に記録する/消す/取り出し台帳を掃除する/退避済みの存在を確かめる

この分担には意味があります。切り離す側は消しません。切り離しは「親から外して別のスキーマへ移す」までで、データはそのまま残ります。消すのは、退避が終わって突合まで通った後段のバッチだけです。つまり、退避される前にデータが消える経路が構造的に存在しません。

起動の時刻は、重ならないようにずらしています。週次の整備と日次の集計が同じ時刻に走るため、月次の退避はそれと重ならない時刻に置いています。

03

退避の 4 手順Four Passes

月次のバッチは 4 つの手順を順に回します。それぞれ独立に試し、途中で失敗したものは記録して次へ進み、最後にまとめて失敗として投げ直します。1 つの失敗で残りを止めると、直せる分まで止まってしまうためです。

図 07 · 退避の 4 手順と、合わなかったときの分岐
月次で 4 手順を順に回す① 残数を数える下限が未来にある子を数え、一定数未満なら止めるテーブル名は解析せず、区切りの値で判定する受け皿の行数も実測する② 圧縮して置く退避待ちのスキーマに在ること自体が対象の合図素の 13 列だけを出す(他のテーブルの現在の名前を焼き込まない)③ 突き合わせる取り出した行数と件数の一致保存後の検査値と容量通ったものだけ台帳へ記録同じ名前への上書き追加なので再試行が成立する④ 子テーブルを捨てる突合できたものだけロック待ちは短時間で打ち切る記録の無い削除経路をコードに存在させない合わなかったときの分岐① 先の月の残数が一定数未満処理を止めて重大なアラートを上げる(自動整備そのものの故障は、機械に直させず人が直す)③ 行数か検査値が合わないそのテーブルは消さない。台帳に失敗として残し、次のテーブルへ進む(次の月の実行で、同じ名前に上書きして再試行される)同じバッチが毎月やる、もう 2 つのこと取り出し台帳の掃除長時間「実行中」のままの依頼を失敗へ倒す(処理が強制終了すると、再試行しても何もせず終わるため放置すると永久に「実行中」で残る)/90 日超の行を削除退避済みの存在確認台帳の全行について、オブジェクトが実在するかを確かめる欠けていれば処理を止める(台帳が「ある」と言っているのに実物が無い状態を、気づかないまま続けさせない)

① で「テーブル名を解析しない」のは、命名の規則が拡張機能の版によって変わった実績があるためです。判定はすべて区切りの値(この子テーブルはいつからいつまでを受け持つか)で行っています。② で「そのスキーマに在ること自体が合図」なのも同じ考えで、名前を当てにしないための設計です。

04

検証してから消すVerify Then Drop

この章でいちばん気を配ったところです。消す判断の材料を 2 系統持っています。

Two Independent Checks Before Dropping
確かめること方法これが守るもの
行が全部出たか取り出し処理が返した件数と、テーブルを直接数えた件数を突き合わせる途中で切れた取り出しを「完了」と誤認しない
壊れずに届いたか保存後のオブジェクトから検査値(crc32c)と容量を取得し、台帳に残す転送中に壊れたファイルを「保管済み」と誤認しない

どちらかが合わなければ、そのテーブルは消しません。台帳に失敗として残し、次のテーブルへ進みます。台帳は同じ名前への上書き追加なので、翌月の実行で自動的に再試行されます(失敗のまま残り続けることはありません)。

記録の無い削除経路を作らない

退避待ちのテーブルが0 行だった場合、期間(いつからいつまでの分か)を導き出せません。この場合は触らず、警告だけを出して次へ進みます。期間を適当に埋めた台帳の行を作って消す、という選択はしていません。

月に 1 件も監査の記録が無いというのは現実にはほぼ起きませんが、そのために自動の掃除を作るよりも、痕跡を残して人が判断するほうを選びました。結果として、コードのどこにも「台帳に記録を残さずにテーブルを消す」経路が存在しません。

実測に基づく判断:統計の推定値を使わない
受け皿の子テーブルに誤って行が入っていないかの点検は、実際に数える方式にしています。データベースが持っている推定値(統計情報)を使う実装から切り替えました。推定値は、集計がまだ走っていない間は「不明」を表す値になっていて1 度も鳴らず、集計後も少数の流入は 0 のまま見逃し、古い値のままだと逆にありもしない誤配を報告します。切り替え前の完了ログには、この観測値がそもそも 1 つも出ていませんでした(鳴らない警報がログにも現れていなかった)。
05

退避分の取り出しRetrieval

外へ出た月を調べたいときは、15 分だけ有効な URL を都度発行します。復元の手続きは要りません。

置き場のクラスは Archive です。保管の料金がいちばん安い代わりに、最低 365 日は置いた扱いで課金されます(それより早く消すと 1 年分かかる)。25 ヶ月を超えたデータは、どう考えても 1 年以上は残すものなので、この選択で困りません。

重要なのは、このクラスが読み出しに待ち時間を要求しないことです。冷たい保管というと「取り出しの申請をして数時間待つ」形を想像しますが、このクラスは普通のファイルと同じようにミリ秒で読めます。そのため、復元の機構を持っていません。クラスを一時的に戻すような処理は、コードのどこにも書いていません(書ける経路を作らない、という形にしてあります)。

How Retrieval Is Restrained
項目内容
対象の確認台帳に「退避済み」として在る月だけ。無ければ 404
理由10〜256 文字を必須。記録の専用の欄に保存する
URL の寿命15 分。ファイル名を指定した形で発行する
記録発行のたびに 1 行。どの月に対してかも残す
回数の制限掛けない(通常の取り出しと枠を共有すると片方が枯れる)
06

失敗したときに起きることOn Failure

月次のバッチが失敗すると、重大なアラートが上がります。起票は全バッチ共通のヘルパ 1 本が唯一の実装で、バッチごとに独自の書き方を作りません。

Failure Modes
失敗の種類そのとき何が起きるか
先の月が足りない処理を止め、重大なアラート。自動で作り直さない(週次の整備そのものが動いていないことを意味するので、機械が繰り返すより人が原因を見るべき状態)
取り出しや保存に失敗そのテーブルを台帳に失敗として記録し、消さずに残す。ほかのテーブルの処理は続ける。最後にまとめて失敗として投げ直す
突合が合わない同上。翌月の実行で同じ名前に上書きして再試行される
退避済みの実物が無い処理を止めて失敗させる。台帳が「保管済み」と言っているのに実物が欠けている状態を、気づかないまま続けさせない
退避待ちが 0 行触らず警告だけを出して次へ進む(期間を導けないため、台帳の行を作れない)
想定外の名前のテーブルがある触らずエラーとして記録する。名前を判定に使わない原則の唯一の例外で、「触らない」方向にだけ使う
本番での検証: 退避の対象が 1 件も無い状態で、バッチを手動で実行して端から端まで通しています。先の月の残数の点検が通り、受け皿の行数が実測値として完了ログに出て、警告もエラーも 0 件、重大なアラートも上がらないことを確認済みです。「対象 0 件で正常終了する」ことは、実際に走らせてみるまで分かりません。
07

置き場所と寿命Storage & Lifecycle

使うバケットは 監査エクスポートバケット の 1 つだけで、用途をプレフィクス(置き場所の接頭辞)で分けています。役割が違うので、クラスも寿命も別に設定しています。

One Bucket, Two Prefixes
プレフィクス中身クラス寿命
exports/CSV の取り出しの結果(依頼ごとの一時的な出力)Standard
(個別に指定)
7 日で自動削除
audit-archive/2 年を超えて外へ出た月(YYYY-MM.csv.gz)Archive
(バケットの既定)
設定なし = 無期限

バケットの既定は Archive にしてあり、取り出しの一時出力だけを書き込むときに個別で Standard に指定しています。あとからクラスを付け替える処理(書き直しが発生する)は使っていません。7 日で消えるものに Archive の最低保管期間を適用すると、消しても 1 年分の料金が発生してしまうためです。

取り出しの依頼の台帳は 90 日で消えます。オブジェクト(7 日)より台帳(90 日)のほうが長いので、8 日目から 90 日目までは「依頼の記録はあるが、実物は消えている」状態になります。この期間の依頼に対しては、窓口が期限切れと判定してダウンロード用の URL を発行しません。押せるのに落ちてこない URL を出さないためです。

退避したデータをいつ消すか: audit-archive/ には寿命の設定を入れていません。「2 年を超えた記録を、さらに何年で消すか」は法務・税務の要件と併せて決めるべき事項で、技術側で先に決めてしまわないほうがよい、という判断です。台帳(audit_partition_archives)には期間・行数・検査値・容量が残っているので、方針が決まった時点でその台帳を元に消せます。