<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>PITR on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/pitr/</link>
    <description>Recent content in PITR on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Fri, 06 Mar 2026 09:02:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/pitr/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>PostgreSQL PITR復旧訓練ガイド: バックアップがあるのに戻せないを防ぐ実践手順</title>
      <link>https://www.ai2core.com/posts/2026-03-06-postgresql-pitr-drill-production-guide/</link>
      <pubDate>Fri, 06 Mar 2026 09:02:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-03-06-postgresql-pitr-drill-production-guide/</guid>
      <description>PostgreSQLのPoint-in-Time Recoveryを本番運用で成立させるために、バックアップ設計・復旧手順・訓練設計を具体例で解説。</description>
      <content:encoded><![CDATA[<h1 id="postgresql-pitr復旧訓練ガイド-バックアップがあるのに戻せないを防ぐ実践手順">PostgreSQL PITR復旧訓練ガイド: バックアップがあるのに戻せないを防ぐ実践手順</h1>
<p>PostgreSQL運用で最も危険なのは「バックアップがある」という安心感です。実際の障害では、バックアップ自体より <strong>復旧手順の不整合</strong> で時間を失います。たとえば、WAL保管期間が足りず目標時刻に戻せない、暗号鍵が見つからず復号できない、復旧後の整合性確認が曖昧で再開判断ができない、といった問題です。</p>
<p>本記事では、PostgreSQLの Point-in-Time Recovery（PITR）を、机上ではなく本番レベルで回すための実装手順を解説します。<code>pgBackRest</code> を例にしていますが、考え方は他ツールでも共通です。</p>
<h2 id="1-pitrの前提-3つ揃わないと復旧できない">1. PITRの前提: 3つ揃わないと復旧できない</h2>
<p>PITRは次の3要素で成立します。</p>
<ol>
<li><strong>ベースバックアップ</strong>（フルまたは差分）</li>
<li><strong>WALアーカイブ</strong>（継続的）</li>
<li><strong>目標時刻情報</strong>（いつまで戻すか）</li>
</ol>
<p>どれか1つでも欠けると成立しません。特に本番で多いのは「WALが途中で消えていた」ケースです。S3保存していても、ライフサイクル設定や権限変更で欠落することがあります。</p>
<h2 id="2-まず決めるべきrtorpo">2. まず決めるべきRTO/RPO</h2>
<p>技術論の前に、業務要件を決めます。</p>
<ul>
<li><strong>RTO</strong>（復旧に許容される時間）: 例 60分</li>
<li><strong>RPO</strong>（失ってよいデータ時間）: 例 5分</li>
</ul>
<p>この2つで設計が変わります。</p>
<ul>
<li>RPO 5分以内ならWALアーカイブ遅延監視が必須</li>
<li>RTO 60分以内なら復旧訓練を定期実施し、手順を自動化する必要あり</li>
</ul>
<p>要件不明のまま「毎日バックアップ」だけ実施しても、障害時に役立たないことが多いです。</p>
<h2 id="3-推奨アーキテクチャ単一リージョンの最小構成">3. 推奨アーキテクチャ（単一リージョンの最小構成）</h2>
<ul>
<li>DBサーバ: PostgreSQL 15/16</li>
<li>バックアップツール: pgBackRest</li>
<li>保存先: S3互換ストレージ（バージョニングON）</li>
<li>監視: Prometheus + Alertmanager</li>
<li>復旧先: 別ホスト（本番と同一ネットワーク）</li>
</ul>
<p>重要なのは、<strong>本番DBと別ホストで実際に復旧できること</strong> を定期検証する点です。</p>
<h2 id="4-実装手順pgbackrest">4. 実装手順（pgBackRest）</h2>
<h3 id="41-postgresql設定">4.1 PostgreSQL設定</h3>
<p><code>postgresql.conf</code> 例:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">wal_level = replica
archive_mode = on
archive_command = &#39;pgbackrest --stanza=main archive-push %p&#39;
max_wal_senders = 10
wal_compression = on
</code></pre><p><code>archive_command</code> は失敗時に非0を返す必要があります。ここが曖昧だとWAL欠落に気づけません。</p>
<h3 id="42-pgbackrest設定">4.2 pgBackRest設定</h3>
<p><code>/etc/pgbackrest/pgbackrest.conf</code> 例:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">[global]
repo1-type=s3
repo1-path=/pgbackrest
repo1-s3-bucket=prod-db-backup
repo1-s3-endpoint=s3.ap-northeast-1.amazonaws.com
repo1-s3-region=ap-northeast-1
repo1-retention-full=14
start-fast=y
process-max=4
compress-type=zst

[main]
pg1-path=/var/lib/postgresql/16/main
</code></pre><p>初期化:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pgbackrest --stanza<span style="color:#f92672">=</span>main stanza-create
</span></span><span style="display:flex;"><span>pgbackrest --stanza<span style="color:#f92672">=</span>main --type<span style="color:#f92672">=</span>full backup
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="43-wal到達の監視">4.3 WAL到達の監視</h3>
<ul>
<li><code>pg_stat_archiver</code> の <code>failed_count</code></li>
<li>最終成功時刻と現在時刻の差分</li>
<li>repoの最新WALタイムスタンプ</li>
</ul>
<p>例: 10分以上WALが更新されない場合にCriticalアラート。</p>
<h2 id="5-復旧訓練drillを標準化する">5. 復旧訓練（Drill）を標準化する</h2>
<p>運用で差が出るのはここです。訓練の目的は「復旧できること」ではなく、<strong>予測可能な時間で安全に復旧できること</strong> です。</p>
<h3 id="51-月次ドリル手順テンプレート">5.1 月次ドリル手順（テンプレート）</h3>
<ol>
<li>目標時刻を決める（例: 当日 08:35:00 JST）</li>
<li>復旧専用ホストを初期化</li>
<li>バックアップ + WALからリストア</li>
<li>DB起動後、整合性チェックを実行</li>
<li>アプリのスモークテストを実行</li>
<li>RTO実測値と課題を記録</li>
</ol>
<h3 id="52-実コマンド例">5.2 実コマンド例</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 復旧先でデータディレクトリを準備</span>
</span></span><span style="display:flex;"><span>systemctl stop postgresql
</span></span><span style="display:flex;"><span>rm -rf /var/lib/postgresql/16/main/*
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 指定時刻までリストア</span>
</span></span><span style="display:flex;"><span>pgbackrest --stanza<span style="color:#f92672">=</span>main <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --type<span style="color:#f92672">=</span>time <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --target<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;2026-03-06 08:35:00+09&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --delta <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  restore
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 起動</span>
</span></span><span style="display:flex;"><span>systemctl start postgresql
</span></span></code></pre></td></tr></table>
</div>
</div><p>PostgreSQL 12+では <code>recovery.conf</code> ではなく <code>postgresql.auto.conf</code> + <code>standby.signal</code> 形式に変わっている点に注意してください。</p>
<h2 id="6-復旧後の整合性チェック項目">6. 復旧後の整合性チェック項目</h2>
<p>「起動したからOK」は危険です。最低限、次を確認します。</p>
<ul>
<li>主要テーブル件数（基準値との差分）</li>
<li>直近トランザクション時刻</li>
<li>重要集計値（売上、注文、在庫など）</li>
<li>アプリの read/write スモークテスト</li>
<li>レプリカ再構築可否</li>
</ul>
<p>SQL例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> now();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#66d9ef">max</span>(created_at) <span style="color:#66d9ef">FROM</span> orders;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#66d9ef">count</span>(<span style="color:#f92672">*</span>) <span style="color:#66d9ef">FROM</span> users;
</span></span></code></pre></td></tr></table>
</div>
</div><p>件数だけでなく「業務上重要な指標」を入れるのが実務的です。</p>
<h2 id="7-失敗しがちなポイントと対策">7. 失敗しがちなポイントと対策</h2>
<h3 id="71-wal保持期間が短すぎる">7.1 WAL保持期間が短すぎる</h3>
<ul>
<li>対策: RPO/RTOに基づき、最低でも7〜14日保持</li>
<li>削除はバックアップ整合性確認後に実行</li>
</ul>
<h3 id="72-暗号鍵認証情報の保管ミス">7.2 暗号鍵・認証情報の保管ミス</h3>
<ul>
<li>対策: KMS/Secret Managerに分離保管</li>
<li>緊急時アクセス手順をRunbook化</li>
</ul>
<h3 id="73-手順が個人依存">7.3 手順が個人依存</h3>
<ul>
<li>対策: RunbookをGit管理</li>
<li>ドリル時に“当番以外”が実行して再現性を検証</li>
</ul>
<h3 id="74-復旧はできるが遅すぎる">7.4 復旧はできるが遅すぎる</h3>
<ul>
<li>対策: 差分バックアップ頻度見直し</li>
<li>復旧先マシンスペックの最低保証</li>
<li>リストア並列度（<code>process-max</code>）調整</li>
</ul>
<h2 id="8-自動化の実装例ciでドリルを回す">8. 自動化の実装例（CIでドリルを回す）</h2>
<p>本番同等データを使えない場合は、匿名化済みスナップショットで定期検証します。</p>
<ul>
<li>毎週日曜 03:00 に復旧ジョブ起動</li>
<li>復旧後にSQLチェック + APIスモーク</li>
<li>結果をSlack/Discordへ通知</li>
<li>失敗時は翌営業日のSRE定例でレビュー</li>
</ul>
<p>この「半自動ドリル」を導入すると、障害時の初動が劇的に安定します。</p>
<h2 id="9-監査対応のための証跡">9. 監査対応のための証跡</h2>
<p>監査や顧客説明で必要になるのは、次の証跡です。</p>
<ul>
<li>バックアップ成功ログ（日次）</li>
<li>WAL連続性の証跡</li>
<li>復旧ドリルの実行記録（日時、担当、RTO実測）</li>
<li>改善アクション履歴</li>
</ul>
<p>「やっています」ではなく「この月にこの結果でした」と示せる状態を作っておきましょう。</p>
<h2 id="10-現場向けチェックリスト">10. 現場向けチェックリスト</h2>
<ul>
<li><input disabled="" type="checkbox"> フルバックアップ成功率 99%以上</li>
<li><input disabled="" type="checkbox"> WAL遅延アラートが有効</li>
<li><input disabled="" type="checkbox"> 目標時刻指定の復旧手順がRunbook化</li>
<li><input disabled="" type="checkbox"> 月次ドリル実施済み</li>
<li><input disabled="" type="checkbox"> 復旧後整合性チェックSQLが整備済み</li>
<li><input disabled="" type="checkbox"> KMS/鍵管理手順が文書化済み</li>
</ul>
<p>このチェックリストを満たして初めて「PITR対応」と言えます。</p>
<h2 id="まとめ">まとめ</h2>
<p>PITRは設定項目の話ではなく、<strong>復旧可能性を継続的に検証する運用</strong> です。</p>
<ul>
<li>RTO/RPOを先に決める</li>
<li>バックアップ + WAL + 監視をセットで設計する</li>
<li>月次ドリルで実測し、Runbookを改善する</li>
<li>復旧後整合性チェックまで標準化する</li>
</ul>
<p>バックアップがあることはゴールではありません。障害時に「何分で、どこまで戻せるか」を言える状態こそが、プロダクション品質です。</p>
<h2 id="付録-実運用で使えるpitrドリル手順90分版">付録: 実運用で使えるPITRドリル手順（90分版）</h2>
<p>以下は、現場でそのまま回せる最小ドリル手順です。月次で固定化すると、障害対応の心理的負荷を下げられます。</p>
<ol>
<li><strong>開始宣言（5分）</strong>
<ul>
<li>担当者、開始時刻、目標RTO/RPOをチケットに記録</li>
</ul>
</li>
<li><strong>復旧環境準備（15分）</strong>
<ul>
<li>復旧先PostgreSQLを起動</li>
<li>バックアップ世代と目標復旧時刻（例: 10:32:00 JST）を確定</li>
</ul>
</li>
<li><strong>リストア実行（25分）</strong>
<ul>
<li><code>pg_restore</code> もしくはベースバックアップ展開</li>
<li>WAL適用完了ログを保存</li>
</ul>
</li>
<li><strong>整合性検証（20分）</strong>
<ul>
<li>件数チェックSQL、外部キー整合性、直近注文IDの連続性を確認</li>
<li>APIスモーク（ログイン/一覧/作成）を実施</li>
</ul>
</li>
<li><strong>振り返り（25分）</strong>
<ul>
<li>実測RTO/RPOを記録</li>
<li>失敗ポイントをRunbookに即反映</li>
</ul>
</li>
</ol>
<p>重要なのは「成功したか」だけではなく、<strong>どこで何分消費したか</strong>を毎回残すことです。これを3回続けるだけで、復旧手順のボトルネックがかなり明確になります。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>PostgreSQL</category>
      <category>Backup</category>
      <category>PITR</category>
      <category>SRE</category>
      <category>Disaster Recovery</category>
    </item>
  </channel>
</rss>
