銀行エンジニアリング 32:障害に備える(書込停止・旧 writer・RTO と RPO)
主系が壊れたとき、バックアップから戻すだけでは足りません。古い側がまだ書いていないか、どこまで失ったか、いつ戻れるかを、先に決めておく必要があります。MiniBank v17の仕組みで確かめます。
v8以降に実装した台帳・記帳エンジンなどを実際に動かし、正常時と異常時の両方から銀行システムの構造を学ぶ。
Prerequisite: BANKING SYSTEMLEARNING GOALS
このページで学ぶこと
- RTO と RPO の測り方と、その限界
- 書込停止(fence)と、旧 writer を止める書込世代
- 復旧の状態と、二者確認による再開
PLAIN LANGUAGE
まず、ここだけつかむ
障害のときは、どこまで失ったか(RPO)、いつ戻れるか(RTO)、古い側がまだ書いていないかを、先に決めておきます。
書込停止(fence)は、営業日のゲートと同じ場所で、お金の動く書込みを止めます。再開は、レビューを出した人とは別の管理責任者が確認します。
書込世代(epoch)が再開のたびに上がり、古い設定のアプリの書込みを断ります。
DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
RTO/RPO
元どおり使えるまでの時間/失ってよいデータの範囲です。
復旧の目標を、数字で話せるようにするためです。
演習で測った値と条件を recovery.drills に保存。目標や保証ではない。
書込停止(fence)
お金の動く書込みと、業務の状態を変える書込み(金額が 0 円のものも)を止めることです。
復元やの確認の最中に、古い側や別の処理が書き込まないようにするためです。
ops.require_business_day_open に足した検査。RY001。
書込世代(epoch)
再開のたびに1つ上がる番号です。
古い設定のまま生き残ったアプリの書込みを見分けるためです。
X-Writer-Epoch ヘッダーと MINIBANK_WRITER_EPOCH。不一致は RY002。
Level 1 基礎:障害のときに困る3つのこと
データベースが壊れて、バックアップから戻すことを考えます。困ることは3つあります。
①バックアップのあとに確定した取引は、戻したデータには無い(どこまで失ったか=RPO)。②壊れたと思った古い側が、実は動いていて書き続けている(二重運用)。③戻すのに時間がかかる(どれだけ止まるか=RTO)。
- どちらも『この機械・このデータ量・この手順で測った値』です。目標値でも保証値でもありません。測るときは、データ量や機械の条件を一緒に残します。
| 用語 | 意味 | MiniBankでの測り方 |
|---|---|---|
| RPO | どれだけ前の状態まで戻ってしまうか | 復元先の最後の仕訳の時刻と、顧客に成功と答えた最後の取引の時刻の差、そして失われた取引の数 |
| RTO | 元どおり使えるまでにかかる時間 | 書込停止を宣言してから、二者確認で再開するまでの時間(演習では人の待ち時間を0とするので、技術的な下限) |
Level 2 銀行業務:復旧の状態と、書ける状態
状態は決められた順序でしか進みません。停止 → 復元 → 照合 → レビューの提出 → 別の管理責任者が再開。復元しない予防的な停止では、復元の段階を飛ばせます。各遷移には理由が必要で、追記専用の履歴に残ります。
| 状態 | 意味 | お金の動く書込み |
|---|---|---|
| RUNNING/OPEN | 通常運転/復旧後に再開 | できる |
| FENCED | 書込停止(fence) | できない(RY001) |
| RESTORING | バックアップから復元した | できない |
| RECONCILING | 照合中 | できない |
| REVIEW | 二者確認待ち | できない |
Level 3 システム:MiniBankでの実装
- 金融の書込みはすべて、15の表に付いた営業日のゲート(ops.require_business_day_open)を通る。v17はこのゲートに、復旧の状態の検査を営業日の判定の前に足した。新しい入口を作らないので、あとから追加した機能も自動で止まる
- fence は EOD と同じ排他の advisory lock(74821,1)を取る。すでに走っている書込みが終わるのを待ち、その後ろに並んだ書込みは RY001 で失敗する。確定した書込みと止まった書込みの境目が明確になる
- 書込世代(epoch)は再開のたびに上がる(外に控えた『それまでに発行した最大の世代』の1つ上。復元先が覚えている古い世代へは戻らない)。enforce_epoch を有効にすると、X-Writer-Epoch ヘッダー(アプリの環境変数 MINIBANK_WRITER_EPOCH)が現在の世代と一致しない書込みを RY002 で断る。古い設定のまま生き残ったアプリが書けなくなる
- 再開(confirm_reopen)は、レビューを提出した人とは別の管理責任者だけ(RY005)。提出と再開には、最新の検証で manifest との比較を除く全項目が OK であること(RY004)、復元を経ている場合は、その間にバックアップの記録(manifest)との比較まで OK になった検証があることが必要
- ゲートの表に載らない書込み(他行送金の送信、決済バッチ、営業日の開始・終了)にも、書込停止と書込世代だけを見る検査を付けた(recovery.require_writable)。決済バッチは、書込停止中に『一部失敗』として記録せず、RY001 で止まる
- 復元を始める(begin_restore)には、障害の前にデータベースの外へ控えた manifest の digest と、それまでに発行した最大の書込世代が要る(RY007)。digest は方式の版つきで、既定は現行の 3。旧方式(2=group 等まで、1=元帳・口座・イベントだけ)で控えた値は、その版を明示して渡す(版が違えば NG。安全側)。運用者の一覧(external_operators)も、任意で渡せる。画面は、復元されたデータベースの中の値を入力の既定値にしない(それが外に控える意味を消すため)。提出者は自分の提出を差し戻せない(RY005)
- 入力を間違えたまま詰まらないよう、復元中・照合中・レビュー中は abort_restore(復元のやり直し)で書込停止に戻せる。検証を省くものではなく、最初の宣言からやり直し、検証・提出・別の人の再開を通る
- 運用者の権限(admin.admin_operators)は、digest に入れない。書込停止中でも運用者の『無効化』だけは許す(事故対応)ので、権限を digest に入れると、正当な無効化のせいで再開が詰まる。代わりに、障害の前に運用者の一覧(user_id・role・status。画面の『運用者の一覧(外に控える用)』。1〜500 件の配列)を外に控え、復元の宣言で渡す。復元後の検証(EXTERNAL_OPERATORS)は、一覧にある人の権限が一覧より『増えて』いたら NG にする(無効のはずが有効、運用者のはずが管理責任者)。減る向き(無効化)と、一覧にない人は NG にしない(一覧にない有効な運用者の人数 unlisted_active は、情報として出すだけ)。一覧を渡さなかったときは検査が走らず、検証に『検査せず』(declared:false)と出る。一方向の検査であり、外に控えた一覧を渡さなければ何も守らない
- 遷移の履歴(recovery.transitions)は追記専用。更新や削除は PF001 で拒否される
- 書込停止の間も、読取りと管理画面の閲覧はできる。許される書込みは、復旧の制御と検証、監査の記録、観測の記録(risk/twin のスナップショットと実行)、Open Banking の利用時刻の記録、再開時のトークン失効、運用者の無効化(事故対応)など。金額が 0 円でも、融資・価格・Open Banking・リスク/Twin の設定・日次の締め・運用者の権限(無効化以外)・KYC の状態など、業務の状態を変える書込みの多くは RY001 で止まる。ただし、AML のケース、調査、通知、清算の記録など、止めていない領域もある(V20-FREEZE-HARDENING.md の一覧)
異常時:古いアプリが、復旧後も書こうとする
| 段階 | 内容 |
|---|---|
| 症状 | 復旧の後に、古い設定のアプリからの書込みが RY002 で断られている(画面には『古い設定のまま動いている』と出る) |
| 原因 | アプリの環境変数 MINIBANK_WRITER_EPOCH が、復旧前の世代のまま |
| 確認 | 管理画面『復旧・DR』の書込世代と、アプリの設定を照らし合わせる |
| 対応 | アプリの設定を現在の世代にして再デプロイする。DB側の enforce_epoch を無効にして通すことは、原因が分かるまで避ける |
Level 4 アーキテクチャ:フェンシング・トークン
古い書き手を止める定番の方法が、単調に増える番号(フェンシング・トークン)です。書き込む側が自分の世代を示し、受ける側が古い世代を断ります。MiniBankの世代はアプリの自己申告で、悪意のある書き手を防ぐものではありません(それは認証と権限の役割です)。防ぐのは『自分たちの古い設定のアプリ』です。
どこを確認すればよいか
- 管理画面『復旧・DR』:状態、世代、履歴、検証、演習の記録
- 運用センター:書込停止中は Recovery が赤になり、『書込停止中』の例外が出る
CHECK YOUR UNDERSTANDING
3問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1書込停止の間、顧客の残高の照会はできますか?
できます。止めるのは、お金の動く書込みと、融資・価格・Open Banking・リスク/Twin の設定など業務の状態を変える書込みです(金額が 0 円でも止めます)。照会(読取り)は止めません。
Q2レビューを提出した人が、再開を確認できますか?
できません。別の管理責任者だけです(RY005)。
Q3復旧の後に古い世代のアプリが書こうとすると?
RY002で断られます。アプリの設定を現在の世代にします。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
SOURCE TRAIL
根拠と関連教材
CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。
確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。
Implementation Status: IMPLEMENTED IN MINIBANK(この教材が扱うコードの範囲)
- RELATED LESSON/docs/be-business-day-eod営業日のゲートと EOD の排他ロック
- RELATED LESSON/docs/be-dr-restore-verify復元と検証
- CURRENT IMPLEMENTATION
supabase/migrations/20260930130000_v17_reliability.sqlops.require_business_day_open金融の書込みの共通のゲート。v17で、書込停止(RY001)と書込世代の不一致(RY002)を営業日の判定の前に追加 - CURRENT IMPLEMENTATION
supabase/migrations/20260930190000_v20_digest_v3_review.sqlrecovery.transition復旧の状態機械。遷移は管理責任者・理由つきで、追記専用に記録。再開は提出者とは別の管理責任者(RY005)。Freeze Hardening で、誤った宣言をやり直す abort_restore と、digest の方式の版を追加。digest 版 3 で、版は 1・2・3(既定 3)になり、外に控えた運用者の一覧(external_operators。任意)も受け取る。再監査で、一覧は 1〜500 件の配列だけを受け付け(空の配列は RY007)、保存するのは user_id・role・status の 3 つだけにした - CURRENT IMPLEMENTATION
src/app/admin/(protected)/recovery/page.tsxRecoveryPage()管理画面 復旧・DR(表示は RecoveryAdminView) - CURRENT IMPLEMENTATION
scripts/banking/v17-recovery.test.mjs状態機械・書込停止・書込世代・manifest と検証・トークン失効・訂正・演習記録のDBテスト