Dev Study
← 出して、戻して、直す — 変更と復旧の実務
レッスン 9 / 12·約 9 分·第24期 出して、戻して、直す

バックアップは、まだ走らせていない復元

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

壊れたときに打てる手は、壊れる前に用意した分しかありません。今回は「用意したつもりのバックアップ」が、戻す段になって何を欠いているかを見ます。

まず結論

バックアップは、戻してみるまで「戻せるはず」という仮説です。PostgreSQL 18.6 の実演では、pg_dump の出力を新しいサーバへ流すと psql は終了コード0で終わり100件が戻りましたが、アプリのロールが存在せず、そのロールではログインできませんでした。

WAL を1本欠いたアーカイブからの復元も、回復目標を指定しなければ欠けた手前で黙って起動しました。「戻せた」と言えるのは、アプリと同じ条件で読めることを確かめたときです。

? 読む前に予測

ロール app_owner が所有し、app_reader に SELECT を許した表を pg_dump で書き出し、ロールを作っていない新しいサーバへ psql の既定設定で流しました。どうなるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

欲しいのはバックアップではなく復元

「バックアップは取るだけでは不十分で、定期的に復元を試して初めて使えると分かる」。これは正しい説明です。この回で詰めたいのはその先、何を確かめれば「復元できた」と言えるのかです。

SRE Book 第26章は、よく言われる言葉として次を引きます(As is sometimes observed: の後)。

No one really wants to make backups; what people really want are restores.

同じ章は、戻す手順が崩れる理由も挙げます。changes to code and schema may render older backups expensive or impossible to use。取った時点では正しかったバックアップも、周りが変わると戻せなくなります。

だから、バックアップを取った時点で分かっているのは「戻せるはず」までです。戻す操作には、バックアップの外にあるもの(ロール、設定、接続先)が要ります。それが揃っているかは、実際に戻さないと見えません。

「戻した」と「戻せた」のあいだ
バックアップを取る取った時点では仮説
戻すコマンドを実行
終了コード0・サーバ起動ここで止めると見落とす
アプリと同じ条件で確かめる
アプリのロールで接続権限・所有者
件数と最新の行を照合欠落・古い地点
  • ・中段で終わる確認は、実演のロール欠落と WAL 欠落(目標なし)をどちらも成功と数える
具体実物

pg_dump を新しいサーバへ戻す

Docker 上の PostgreSQL 18.6 で、データベース memo(所有者 app_owner)の表 notes に100件を入れ、app_reader に SELECT を許しました。これを pg_dump -d memo(プレーン SQL)で書き出し、ロールを作っていない新しいコンテナへ流しています。パスワードはダミーです。

本教材の実演の出力(スクリプト非掲載)— PostgreSQL 18.6、run 1(run 2 も同じ出力)コマンド出力
=== run 1 2026-09-26 09:34:48
pg_dump exit=0 lines= 166
--- dump 中のロール参照:
27:-- Name: notes; Type: TABLE; Schema: public; Owner: app_owner
36:ALTER TABLE public.notes OWNER TO app_owner;
39:-- Data for Name: notes; Type: TABLE DATA; Schema: public; Owner: app_owner
147:-- Name: notes notes_pkey; Type: CONSTRAINT; Schema: public; Owner: app_owner
155:-- Name: TABLE notes; Type: ACL; Schema: public; Owner: app_owner
158:GRANT SELECT ON TABLE public.notes TO app_reader;
--- (1) 新しいコンテナ(ロールなし)へ、psql 既定のまま戻す
psql exit=0
ERROR: role "app_owner" does not exist
ERROR: role "app_reader" does not exist
rows: 100 owner: postgres acl:
app_reader でログイン:
psql: error: connection to server at "127.0.0.1", port 5432 failed: FATAL: role "app_reader" does not exist
--- (2) ON_ERROR_STOP=on で戻す
psql exit=3
ERROR: role "app_owner" does not exist
notes exists?: 1 rows: 0
--- (3) 先に pg_dumpall --globals-only を戻してから同じダンプを戻す(別の新しいコンテナ)
globals psql exit=0
ERROR: role "postgres" already exists
psql exit=0
app_reader でログイン:
100

(1) が今回の要点です。psql の終了コードは0で、行も100件あります。しかし所有者は postgres、権限(acl)は空で、アプリが使う app_reader はログインの段階で存在しません。ダンプには所有者の文(36行)と GRANT(158行)が入っていますが、それが指すロールは入っていません。公式文書のとおりです。

Before restoring an SQL dump, all the users who own objects or were granted permissions on objects in the dumped database must already exist.

psql は By default, the psql script will continue to execute after an SQL error is encountered. です。終了コードだけを見る確認は (1) を成功と数えます。

(2) の ON_ERROR_STOP=on は失敗を終了コード3で知らせますが、表は0件で、戻ってはいません。(3) でロールを先に戻し、データベースを app_owner の所有で作ってから流すと、app_reader が100件を読めました。globals の段で出た role "postgres" already exists は、公式文書が "This error is harmless and should be ignored." と書く既存の管理ロールのものです。なお、この接続はコンテナ内から行っており、パスワード認証までは試していません。

✓ ここまでの確認

実演のダンプ(プレーン SQL)に入っておらず、復元先に先に用意する必要があったのはどれですか。

抽象原理

復元の確認は、アプリと同じ条件で読むこと

(1) は「戻す操作は動いたが、戻したいものは戻っていない」状態でした。一般化すると、復元の確認は、戻す操作の成否ではなく、アプリが必要とする条件で読めるかで判定する、になります。

SRE Book 第26章の結論はこうです。

If you take away just one lesson from this chapter, remember that you only know that you can recover your recent state if you actually do so.

同じ段落は the only test that should let you sleep at night is a full end-to-end test と書きます。入口(コマンド)から出口(アプリの読み取り)まで通す試験だけが、戻せることの証拠になります。

逆向きの誤りもあります。「どうせ戻せないならバックアップは無意味だ」も成り立ちません。(3) はロールを先に戻す手順を足すだけで戻りました。欠けていたのはバックアップではなく、戻す手順の1段です。

マネージド DB でも同じ形になります。マネージドでバックアップを取る側を誰が担うかの線引きは第8期で扱いました。ところが Amazon RDS の文書は、戻すと新しい実体ができると書きます。

You can't restore from a DB snapshot to an existing DB instance; you create a new DB instance when you restore the snapshot.

アプリの接続先をその新しいインスタンスへ向け直すのは利用者の作業です。付くパラメータグループは、選ばなければ既定のものです(The default DB parameter group is associated with the restored instance, unless you choose a different one.)。取る作業を任せても、戻して使える状態にする作業は残ります。

具体実物

WAL を1本欠いたアーカイブから戻す

もう1つの実演です。ベースバックアップ(100件)の後に50件ずつ3回追加し、それぞれ別の WAL ファイル(…04・…05・…06)に収めました。アーカイブから …05 を削除し、新しいコンテナで復元しています。削除は教材上の故障注入で、実運用で何が原因で欠けるかは試していません。

本教材の実演の出力(スクリプト非掲載)— PostgreSQL 18.6、各モード run 1 から行を抜粋(run 2 も同じ結果)コマンド出力
=== MODE notarget run 1 2026-09-26 09:35:20
batch 1: rows 101-150 written in WAL segment 000000010000000000000004
batch 2: rows 151-200 written in WAL segment 000000010000000000000005
batch 3: rows 201-250 written in WAL segment 000000010000000000000006
primary rows: 250
removed from archive: 000000010000000000000005
pg_ctl start exit=0
restored rows: 150
2026-09-26 00:35:38.254 UTC [33] LOG: restored log file "000000010000000000000004" from archive
cp: cannot stat '/arch/000000010000000000000005': No such file or directory
2026-09-26 00:35:38.326 UTC [33] LOG: selected new timeline ID: 2
2026-09-26 00:35:38.362 UTC [33] LOG: archive recovery complete
=== MODE target run 1 2026-09-26 09:35:56
removed from archive: 000000010000000000000005
pg_ctl start exit=1
2026-09-26 00:36:14.485 UTC [32] FATAL: recovery ended before configured recovery target was reached

回復目標なし(notarget)では、…05 が無いところで回復を終え、150件で普通に起動しました。pg_ctl の終了コードは0です。…06 は残っていても、つながらないので使われません。途中の cp: cannot stat は、公式文書が通常の終わり方として書く形と同じです。

Normally, recovery will proceed through all available WAL segments, … Therefore, a normal recovery will end with a “file not found” message

回復目標を追加の3回より後の時刻にした場合(target)は、起動しませんでした。

In any case, if a recovery target is configured but the archive recovery ends before the target is reached, the server will shut down with a fatal error.

同じ欠落が、目標を書けば止まって気づき、書かなければ黙って古い地点で動きます。後者を見つけられるのは、戻したあとに件数や最新の行を元と照合したときだけです。

✓ ここまでの確認

回復目標を指定せずに、途中の WAL が1本欠けたアーカイブから復元しました。実演ではどうなりましたか。

⇄ 2つを見比べる
具体例 A

論理ダンプ(pg_dump)を新しいサーバへ

ダンプは1つのデータベースだけを書き出し、ロールを含みません。実演では psql が終了コード0で終わり100件が戻りましたが、アプリのロールでは接続できませんでした。ロールを先に戻すと読めました。

具体例 B

マネージド DB(Amazon RDS)のスナップショット復元

既存のインスタンスには戻せず、新しいインスタンスが作られます。別に選ばなければ既定のパラメータグループが付きます。接続先の切り替えと設定の選び直しは利用者の作業です。

自前のダンプとマネージドの復元。仕組みは違いますが、戻した直後に同じ種類の確認が要ります。それは何でしょうか。

「バックアップの中にあるもの」と「戻したものを使うのに要るもの」を、両方で書き出してみてください。

この章のまとめ

  • バックアップを取った時点で分かるのは「戻せるはず」まで。SRE Book は 実際に戻したときだけ分かる と書く。
  • pg_dump は ロールを含まない。実演では psql が 終了コード0 のまま、アプリのロールで接続できない状態に戻った。
  • ON_ERROR_STOP=on は失敗を知らせる(終了コード3)が、戻すのは ロールを先に戻す手順。
  • WAL が1本欠けると、回復目標ありは FATAL、目標なしは欠けた手前で黙って起動。
  • RDS の復元は 新しいインスタンス と既定のパラメータグループ。接続先と設定は利用者側に残る。
  • 復元の確認は、アプリと同じ条件で接続し、件数と最新の行を元と照合する まで。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

SRE Book 第26章が「この章から1つだけ持ち帰るなら」として挙げていることはどれですか。

用語集(4語)
論理ダンプ (pg_dump)
1つのデータベースの中身を SQL 文などで書き出したもの。ロールやテーブル空間のようにクラスタ全体に属するものは含まれない。
グローバルオブジェクト
ロールやテーブル空間など、データベースごとではなくクラスタ全体で共有されるもの。pg_dumpall で書き出す。既定ではロールのパスワードも出力に入る(`--no-role-passwords` で省ける)。
ON_ERROR_STOP
psql の変数。有効にすると SQL のエラーで止まり、終了コード3を返す。既定では無効で、エラー後も続行する。
回復目標 (recovery target)
アーカイブからの復元をどの時点で止めるかの指定。指定があるのに届かなければ、サーバは致命的エラーで止まる。