PostgreSQL 初心者ガイド:pg_basebackupで安全なデータベース運用

2025-05-27

pg_basebackupとは

pg_basebackup は、PostgreSQLサーバーの物理的なベースバックアップを作成するためのコマンドラインツールです。これは、PostgreSQLクラスタ全体の完全なコピーを作成し、後でリストアするために使用できます。

主な機能と用途

  • リモート接続
    ローカルサーバーだけでなく、リモートのPostgreSQLサーバーからもバックアップを取得できます。
  • レプリケーションのスナップショット
    新しいスタンバイサーバーを構築する際の初期同期に使用できます。ベースバックアップを取得し、その後のWALログを適用することで、プライマリサーバーと同一の状態のスタンバイサーバーを構築できます。
  • WALログのアーカイブ
    バックアップの開始時点から終了時点までのトランザクションログ(WAL: Write-Ahead Logging)をアーカイブする機能があり、ポイントインタイムリカバリ(PITR)に必要な情報を提供します。
  • 物理バックアップ
    論理バックアップ(例えば pg_dump)とは異なり、データファイルそのものをコピーするため、リストアが高速に行えます。
  • オンラインバックアップ
    データベースが稼働中でもバックアップを取得できるため、システムのダウンタイムを最小限に抑えることができます。
  • 完全バックアップの作成
    稼働中のPostgreSQLサーバーから、データディレクトリ全体のスナップショットを作成します。これにより、データベース全体をある時点の状態に戻すことができます。

基本的な使い方

pg_basebackup コマンドは、通常、PostgreSQLサーバーがインストールされているマシンのコマンドラインインターフェースから実行します。以下は基本的な構文です。

pg_basebackup -h ホスト名 -U ユーザー名 -D 保存先ディレクトリ -P -v -W

各オプションの意味は以下の通りです。

  • -W: パスワードの入力を促します。
  • -v: 詳細な出力を表示します。
  • -P: バックアップの進行状況を表示します。
  • -D 保存先ディレクトリ: 作成されたベースバックアップを保存するローカルのディレクトリを指定します。このディレクトリは空である必要があります。
  • -U ユーザー名: PostgreSQLに接続するためのユーザー名を指定します。通常、レプリケーション権限を持つユーザーを使用します。
  • -h ホスト名: PostgreSQLサーバーが稼働しているホスト名を指定します。ローカルの場合は localhost や IPアドレスを指定します。


ローカルホストで稼働しているPostgreSQLサーバーから、「backup」というユーザーでベースバックアップを /tmp/base_backup ディレクトリに作成する場合:

pg_basebackup -h localhost -U backup -D /tmp/base_backup -P -v -W

このコマンドを実行すると、パスワードの入力が求められ、バックアップ処理が開始されます。完了すると、/tmp/base_backup ディレクトリにPostgreSQLのデータディレクトリの完全なコピーが作成されます。

プログラミングとの関連

直接的にプログラムの中で pg_basebackup を呼び出すことは一般的ではありません。なぜなら、これはシステムレベルの操作であり、通常は運用や管理のスクリプトとして実行されるからです。

しかし、プログラミングの文脈では、以下のような点で pg_basebackup の知識が役立ちます。

  • レプリケーション環境の構築
    アプリケーションが高可用性を求められる場合、レプリケーションの構築が必要になります。pg_basebackup はスタンバイサーバーの初期同期の重要なステップとなるため、その知識が不可欠です。
  • 自動化スクリプトの作成
    Pythonやシェルスクリプトなどを用いて、定期的なベースバックアップの取得を自動化する際に、pg_basebackup コマンドを呼び出す処理を記述することがあります。
  • バックアップ・リカバリ戦略の理解
    アプリケーションを開発・運用する上で、データベースのバックアップとリカバリの仕組みを理解することは重要です。pg_basebackup がどのように動作するかを知ることで、適切なバックアップ戦略を立てることができます。


一般的なエラーとトラブルシューティング

pg_basebackup を実行する際に遭遇する可能性のある一般的なエラーと、その対処法について解説します。

接続関連のエラー

  • 原因と対策

    • PostgreSQLサーバーが起動していない
      まず、PostgreSQLサーバーが正常に起動しているかを確認してください。systemctl status postgresql (systemdの場合) や service postgresql status (SysVinitの場合) などのコマンドで確認できます。
    • ポート番号が間違っている
      -p オプションで正しいポート番号を指定しているか確認してください。デフォルトは 5432 です。
    • ファイアウォール
      PostgreSQLサーバーが稼働しているマシンのファイアウォール設定で、接続元のIPアドレスからのアクセスが許可されているか確認してください。
    • pg_hba.conf の設定
      PostgreSQLの設定ファイル pg_hba.conf に、接続元のホスト名またはIPアドレス、データベース名、ユーザー名に対する適切な認証設定があるか確認してください。必要に応じて設定を修正し、PostgreSQLサーバーを再起動 (pg_ctl reload) してください。
    • 認証情報の誤り
      -U オプションで指定したユーザー名と、-W オプションで入力したパスワードが正しいか確認してください。
    • pg_basebackup: error: could not connect to server: Connection refused (0.0.0.0:5432)
    • pg_basebackup: error: FATAL: no pg_hba.conf entry for host "..."
    • pg_basebackup: error: FATAL: password authentication failed for user "..."

権限関連のエラー

  • 原因と対策

    • レプリケーション権限がない
      -U オプションで指定したユーザーには、REPLICATION 権限が必要です。PostgreSQLに superuser または replication ロールを持つユーザーで接続し、以下のSQLコマンドを実行して権限を付与してください。
      ALTER USER <username> WITH REPLICATION;
      
      <username>-U オプションで指定したユーザー名に置き換えてください。
    • バックアップ先ディレクトリの権限
      -D オプションで指定したバックアップ先のディレクトリに対して、pg_basebackup を実行するユーザーが書き込み権限を持っているか確認してください。必要に応じて chownchmod コマンドで権限を変更してください。
    • PostgreSQLデータディレクトリの権限
      pg_basebackup を実行するユーザーが、PostgreSQLのデータディレクトリ(通常は /var/lib/postgresql/<version>/main/ など)を読み取る権限を持っている必要があります。通常は問題ありませんが、特別な設定をしている場合は確認してください。
  • エラーメッセージの例

    • pg_basebackup: error: FATAL: permission denied for database postgres (または他のデータベース名)
    • pg_basebackup: error: could not open directory "/var/lib/postgresql/14/main": Permission denied (バックアップ先のディレクトリ)

ディスク容量不足

  • 原因と対策

    • バックアップ先のディスク容量不足
      -D オプションで指定したバックアップ先のディスク容量が、PostgreSQLのデータ量に対して不足しています。より大きなディスク容量を持つ場所にバックアップするか、不要なファイルを削除して空き容量を増やしてください。
  • エラーメッセージの例

    • pg_basebackup: error: could not write to file "base/...": No space left on device

WALログ関連のエラー

  • 原因と対策

    • WALアーカイブの設定
      WALアーカイブが適切に設定されていない場合、必要なWALログがすでに削除されている可能性があります。postgresql.conf ファイルの wal_levelarchive_modearchive_command の設定を確認し、必要に応じて修正してください。
    • max_wal_senders の設定
      同時接続数が max_wal_senders の設定値を超えている場合、新しいレプリケーション接続が確立できないことがあります。postgresql.conf ファイルの max_wal_senders の値を増やし、PostgreSQLサーバーを再起動してください。
    • ネットワークの問題
      レプリケーション接続中にネットワークの問題が発生し、WALログの転送が中断されることがあります。ネットワークの接続状況を確認してください。
  • エラーメッセージの例

    • pg_basebackup: error: could not get latest WAL record to start replication
    • pg_basebackup: error: could not send replication command: ERROR: requested WAL segment ... has already been removed
  • 原因と対策

    • バックアップ先ディレクトリが空でない
      -D オプションで指定したディレクトリは空である必要があります。既存のファイルがある場合は、削除するか別のディレクトリを指定してください。
  • エラーメッセージの例

    • pg_basebackup: error: target directory "/tmp/backup" exists but is not empty

トラブルシューティングのヒント

  • ネットワークのテスト
    リモートサーバーからバックアップを取得する場合は、pingtelnet コマンドなどでネットワークの接続性を確認してください。
  • PostgreSQLのバージョン
    使用しているPostgreSQLのバージョンによって、エラーメッセージや挙動が異なる場合があります。公式ドキュメントを参照することも有効です。
  • PostgreSQLサーバーのログの確認
    PostgreSQLサーバーのログファイル(通常は postgresql.log)にも、エラーに関する情報が記録されている場合があります。
  • 詳細なログの確認
    -v オプションを付けて pg_basebackup を実行すると、より詳細な出力が得られます。エラーの原因を特定するのに役立ちます。


例1: Pythonスクリプトによる定期的なベースバックアップの自動化

この例では、Pythonスクリプトを使って定期的に pg_basebackup を実行し、バックアップの世代管理を行う基本的な流れを示します。

import subprocess
import datetime
import os

# 設定
PG_HOST = "localhost"
PG_USER = "backupuser"  # REPLICATION権限を持つユーザー
BACKUP_BASE_DIR = "/var/lib/postgresql_backups"
RETENTION_DAYS = 7

def create_backup():
    """pg_basebackupを実行してバックアップを作成する"""
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_dir = os.path.join(BACKUP_BASE_DIR, timestamp)
    os.makedirs(backup_dir, exist_ok=True)

    command = [
        "pg_basebackup",
        "-h", PG_HOST,
        "-U", PG_USER,
        "-D", backup_dir,
        "-P",
        "-v",
        "-W"  # パスワードは環境変数などで管理することが推奨されます
    ]

    try:
        print(f"バックアップを開始します: {backup_dir}")
        subprocess.run(command, check=True, input="your_password\n".encode())  # パスワードを標準入力で渡す (非推奨)
        print("バックアップが完了しました。")
        return backup_dir
    except subprocess.CalledProcessError as e:
        print(f"バックアップ中にエラーが発生しました: {e}")
        return None

def cleanup_old_backups():
    """古いバックアップを削除する"""
    cutoff_date = datetime.datetime.now() - datetime.timedelta(days=RETENTION_DAYS)
    for backup_dir in os.listdir(BACKUP_BASE_DIR):
        backup_path = os.path.join(BACKUP_BASE_DIR, backup_dir)
        try:
            backup_time_str = backup_dir.split("_")[0]  # YYYYMMDD 部分を抽出
            backup_time = datetime.datetime.strptime(backup_time_str, "%Y%m%d")
            if backup_time < cutoff_date:
                print(f"古いバックアップを削除します: {backup_path}")
                # shutil.rmtree(backup_path) # 実際に削除する場合はコメントアウトを解除
        except (ValueError, IndexError):
            print(f"不正な形式のバックアップディレクトリ: {backup_path}")

if __name__ == "__main__":
    create_backup()
    cleanup_old_backups()

このスクリプトの解説

  1. 設定
    PostgreSQLのホスト名、ユーザー名、バックアップの保存先ディレクトリ、バックアップの保持期間などを変数として定義しています。
  2. create_backup() 関数
    • 現在のタイムスタンプを基にバックアップディレクトリを作成します。
    • subprocess.run() を使用して pg_basebackup コマンドを実行します。
      • -h, -U, -D, -P, -v, -W などのオプションを指定しています。
      • check=True を指定することで、コマンドが失敗した場合に CalledProcessError が発生します。
      • 重要
        パスワードをスクリプトに直接記述したり、標準入力で渡したりする方法はセキュリティ上推奨されません。環境変数や別の安全な方法で管理することを検討してください。
    • バックアップの成否に応じてメッセージを出力します。
  3. cleanup_old_backups() 関数
    • 指定された保持期間よりも古いバックアップディレクトリを特定し、削除します(現在はコメントアウトされています)。
    • バックアップディレクトリ名の形式に基づいて日付を解析しています。
    • 不正な形式のディレクトリはスキップします。
  4. if __name__ == "__main__": ブロック
    • スクリプトが直接実行された場合に、create_backup()cleanup_old_backups() を呼び出します。

例2: シェルスクリプトによるベースバックアップ

よりシンプルな方法として、シェルスクリプトで pg_basebackup を実行することもできます。

#!/bin/bash

PG_HOST="localhost"
PG_USER="backupuser"
BACKUP_DIR="/var/lib/postgresql_backups/$(date +%Y%m%d_%H%M%S)"

echo "ベースバックアップを開始します: ${BACKUP_DIR}"

pg_basebackup -h "${PG_HOST}" -U "${PG_USER}" -D "${BACKUP_DIR}" -P -v -W <<EOF
your_password
EOF

if [ $? -eq 0 ]; then
  echo "ベースバックアップが完了しました。"
else
  echo "ベースバックアップ中にエラーが発生しました。"
fi

# 古いバックアップの削除 (例: 7日より古いものを削除)
find /var/lib/postgresql_backups/ -type d -name "20*" -mtime +7 -exec rm -rf {} \;

このスクリプトの解説

  1. 変数の定義
    ホスト名、ユーザー名、バックアップディレクトリなどを定義しています。バックアップディレクトリ名は date コマンドで生成したタイムスタンプを含みます。
  2. pg_basebackup の実行
    • -h, -U, -D, -P, -v, -W オプションを指定しています。
    • ヒアドキュメント (<<EOF ... EOF) を使用してパスワードを標準入力に渡しています(これもセキュリティ上の注意が必要です)。
  3. 終了ステータスの確認
    pg_basebackup コマンドの終了ステータス ($?) を確認し、成功または失敗のメッセージを出力します。
  4. 古いバックアップの削除
    find コマンドを使用して、7日以上前に作成されたバックアップディレクトリを検索し、削除します。

プログラミングの文脈での注意点

  • 並行処理
    大規模なシステムでは、バックアップ処理が他の処理に影響を与えないように、実行タイミングやリソースの使用量を考慮する必要があります。
  • ログ記録
    バックアップの開始、終了、エラーなどの情報をログファイルに記録することで、トラブルシューティングや監査に役立ちます。
  • 設定の外部化
    ホスト名、ユーザー名、バックアップディレクトリなどの設定は、スクリプトの外部ファイルや環境変数から読み込むようにすると、柔軟性が高まります。
  • エラーハンドリング
    スクリプト内で pg_basebackup の実行が失敗した場合の処理(ログ出力、通知など)を適切に実装することが重要です。
  • セキュリティ
    データベースの認証情報(特にパスワード)をスクリプトに直接埋め込むのは非常に危険です。環境変数、設定ファイル、またはより安全な認証メカニズムを使用することを強く推奨します。

これらの例は、pg_basebackup を自動化するための基本的なスクリプトです。実際の運用環境では、より高度なエラー処理、ログ管理、監視機能などを組み込むことが一般的です。



論理バックアップ (pg_dump, pg_dumpall) の利用

  • 欠点
    • 物理バックアップに比べてリストアに時間がかかる場合がある。
    • データベースのサイズが大きい場合、バックアップに時間がかかり、ディスク容量を消費する可能性がある。
    • ポイントインタイムリカバリ(PITR)には適していない(WALログは含まれない)。
  • 利点
    • 特定のデータベースやテーブルのみをバックアップ・リストアできる。
    • バックアップファイルをテキスト形式で保存できるため、内容の確認や一部修正が容易。
    • 異なるバージョンのPostgreSQL間での移行が比較的容易。
  • プログラミングとの関連
    • 自動化
      Pythonやシェルスクリプトから subprocess モジュールなどを使って pg_dumppg_dumpall を定期的に実行し、バックアップを取得できます。
    • カスタマイズ
      バックアップ対象のデータベースやテーブルを細かく制御できます。
    • リストア
      生成されたSQLスクリプトを psql コマンドで実行したり、アーカイブファイルを pg_restore コマンドでリストアしたりする処理を自動化できます。

例 (Python)

import subprocess
import datetime
import os

DB_NAME = "mydatabase"
PG_USER = "myuser"
BACKUP_DIR = "/var/lib/postgresql_logical_backups"

def create_logical_backup():
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_file = os.path.join(BACKUP_DIR, f"{DB_NAME}_{timestamp}.sql")

    command = [
        "pg_dump",
        "-h", "localhost",
        "-U", PG_USER,
        "-d", DB_NAME,
        "-f", backup_file
    ]

    try:
        print(f"論理バックアップを開始します: {backup_file}")
        subprocess.run(command, check=True)
        print("論理バックアップが完了しました。")
    except subprocess.CalledProcessError as e:
        print(f"論理バックアップ中にエラーが発生しました: {e}")

if __name__ == "__main__":
    create_logical_backup()

ファイルシステムレベルのスナップショットの利用

  • 欠点
    • 特定のファイルシステムに依存する。
    • スナップショット取得時のPostgreSQLの静止処理が必要。
    • ポイントインタイムリカバリにはWALログの別途管理が必要。
  • 利点
    • 高速なバックアップとリストアが可能。
    • 物理バックアップと同様に、クラスタ全体のコピーを作成できる。
  • プログラミングとの関連
    • スクリプトによる制御
      ファイルシステムのスナップショット作成・削除コマンドをスクリプトから実行します。
    • PostgreSQLの静止
      バックアップ前に pg_start_backup() およびバックアップ後に pg_stop_backup() を実行することで、整合性のあるスナップショットを取得できます。

例 (シェルスクリプト、ZFSの場合)

#!/bin/bash

DATA_DIR="/var/lib/postgresql/14/main"
SNAPSHOT_NAME="pgsql_backup_$(date +%Y%m%d_%H%M%S)"
ZPOOL_NAME="your_zpool" # あなたのZFSプール名

PG_CTL="/usr/lib/postgresql/14/bin/pg_ctl" # pg_ctlのパス

# バックアップ開始
sudo -u postgres "$PG_CTL" -D "$DATA_DIR" stop -mf

# スナップショット作成
sudo zfs snapshot "${ZPOOL_NAME}${DATA_DIR}@${SNAPSHOT_NAME}"

# PostgreSQL再起動
sudo -u postgres "$PG_CTL" -D "$DATA_DIR" start

echo "ZFSスナップショットが作成されました: ${ZPOOL_NAME}${DATA_DIR}@${SNAPSHOT_NAME}"

# 古いスナップショットの削除 (例: 7日より古いものを削除)
find / -mountpoint -name "pgsql_backup_*" -mtime +7 -exec sudo zfs destroy "${ZPOOL_NAME}${DATA_DIR}@$(basename {})" \;

クラウドプロバイダーのマネージドバックアップサービス

  • 欠点
    • ベンダーロックインの可能性がある。
    • 柔軟性に制限がある場合がある。
    • コストがかかる場合がある。
  • 利点
    • 設定や管理が容易。
    • 可用性や耐久性が高い。
    • スケーラビリティに優れている。
  • プログラミングとの関連
    • API連携
      各クラウドプロバイダーのAPIやSDKを利用して、バックアップの作成、リストア、設定などをプログラムから制御できます。
    • 監視と通知
      バックアップのステータスを監視し、異常があった場合に通知するシステムを構築できます。

例 (AWS SDK for Python - Boto3)

import boto3
import datetime

REGION_NAME = "us-east-1"
DB_IDENTIFIER = "your-rds-instance-identifier"

rds = boto3.client("rds", region_name=REGION_NAME)

def create_rds_snapshot():
    timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    snapshot_identifier = f"{DB_IDENTIFIER}-snapshot-{timestamp}"

    try:
        response = rds.create_db_snapshot(
            DBSnapshotIdentifier=snapshot_identifier,
            DBInstanceIdentifier=DB_IDENTIFIER
        )
        print(f"RDSスナップショットが開始されました: {snapshot_identifier}")
        return response
    except Exception as e:
        print(f"RDSスナップショットの作成に失敗しました: {e}")
        return None

if __name__ == "__main__":
    create_rds_snapshot()
  • 欠点
    • 導入や設定に手間がかかる場合がある。
    • ツール固有の知識が必要となる。
  • 利点
    • PostgreSQLに特化した高度な機能を利用できる。
    • 効率的なバックアップとリストアが可能。
    • ポイントインタイムリカバリ(PITR)をサポート。
  • プログラミングとの関連
    • APIやCLI連携
      これらのツールが提供するAPIやコマンドラインインターフェースをプログラムから呼び出して、バックアップやリストア処理を自動化できます。
    • 詳細な制御
      増分バックアップ、並列処理、圧縮、暗号化など、高度なバックアップ戦略をプログラムから制御できます。