PostgreSQL/MySQLのバックアップ運用 — 論理・物理・PITRと復元テストまで
本記事はアフィリエイト広告(A8.net)を含みます。価格・仕様は2026年6月時点の目安です。最新は各公式サイトでご確認ください。
TL;DR
データベースのバックアップは「ファイルを丸ごとコピーするだけ」では機能しません。稼働中にファイルをコピーすると、書き込みが途中のページが混在して「取れているように見えて復元できないバックアップ」が出来上がります。
DBバックアップで押さえるべきポイントは5つです。①論理バックアップ(pg_dump/mysqldump)で整合性を保って取得する → ②物理バックアップとの使い分けを理解する → ③PITR(WAL/binlog)で任意時点への復元に備える → ④cronまたはsystemd timerで自動化し、オフサイトに退避する → ⑤必ず復元テストを実施する。 最後の復元テストを省略した運用が、最もよくある「バックアップがあったのに復旧できなかった」という障害につながります。
本記事はUbuntu/Debian系VPSでPostgreSQL 16・MySQL 8.0を前提に説明します。
なぜDBバックアップはファイルコピーと違うのか
通常のファイルをバックアップするなら rsync や cp で十分です。しかしデータベースには次の特性があります。
整合性(Consistency)の問題。 DBはメモリ上のバッファ・WAL(Write-Ahead Log)・データファイルが組み合わさって動いています。稼働中にデータファイルだけコピーすると、バッファに残っている変更がディスクに書き出される前の断面を取ってしまい、整合性が壊れます。
稼働中の取得。 本番環境でサービスを止めてバックアップするのは現実的ではありません。論理バックアップツールはDBエンジンを通じてデータを読み出すため、エンジン側がスナップショット的に整合性を保った状態でデータを渡してくれます。
復元先の環境依存。 物理バックアップはエンジンのバージョンやOS・ページサイズが一致している必要があります。論理バックアップはSQLとして出力されるため、別バージョンへの移行やクラウドへの移送に向いています。
論理バックアップ — pg_dump / mysqldump
論理バックアップはDBエンジンを経由してデータをSQL(またはカスタム形式)として取り出す方法です。最も汎用性が高く、VPS上の小中規模DBで最初に選ぶべき手法です。
PostgreSQL — pg_dump
pg_dump はPostgreSQL付属のバックアップコマンドです。デフォルトのプレーンSQL形式と、並列リストア対応のカスタム形式(-Fc)があります。
# プレーンSQL形式(可読・小規模向き)
pg_dump -U postgres -d mydb > /backup/mydb_$(date +%F).sql
# カスタム形式(圧縮・大規模向き・pg_restoreで復元)
pg_dump -U postgres -Fc -d mydb -f /backup/mydb_$(date +%F).dump
# 全データベースまとめて取る場合(pg_dumpallを使う)
pg_dumpall -U postgres > /backup/all_$(date +%F).sql
パスワードが必要な環境では ~/.pgpass にパスワードを記述するか、PGPASSWORD 環境変数を使います。
# ~/.pgpass の形式(chmod 600 が必須)
# hostname:port:database:username:password
localhost:5432:mydb:postgres:mypassword
chmod 600 ~/.pgpass
復元(pg_restore / psql):
# カスタム形式からの復元
pg_restore -U postgres -d mydb /backup/mydb_2026-06-26.dump
# プレーンSQL形式からの復元
psql -U postgres -d mydb < /backup/mydb_2026-06-26.sql
復元先のDBが存在しない場合は先に作成します。
createdb -U postgres mydb
MySQL — mysqldump
mysqldump はMySQL/MariaDB付属のバックアップコマンドです。InnoDB(トランザクション対応エンジン)を使っている場合は --single-transaction が必須です。これを付けることで、ロックをかけずに整合性のとれたスナップショットを取れます。
# InnoDB前提(--single-transactionで整合性を保つ)
mysqldump -u root -p --single-transaction mydb > /backup/mydb_$(date +%F).sql
# 全データベースを一括取得
mysqldump -u root -p --single-transaction --all-databases > /backup/all_$(date +%F).sql
# gzip圧縮しながら出力
mysqldump -u root -p --single-transaction mydb | gzip > /backup/mydb_$(date +%F).sql.gz
--single-transaction はInnoDBのみ有効です。MyISAMテーブルが混在している場合はロックが必要になるため --lock-tables を使います(ただし書き込みが止まります)。
パスワードをコマンドライン引数に書くと ps コマンドで見えてしまいます。~/.my.cnf に記述するのが安全です。
# ~/.my.cnf(chmod 600 が必須)
[client]
user = root
password = mypassword
chmod 600 ~/.my.cnf
これにより -p なしでコマンドを実行できます。
復元:
mysql -u root -p mydb < /backup/mydb_2026-06-26.sql
# gzip圧縮ファイルからの復元
gunzip < /backup/mydb_2026-06-26.sql.gz | mysql -u root -p mydb
物理バックアップとの違い
| 項目 | 論理バックアップ(pg_dump/mysqldump) | 物理バックアップ(pg_basebackup/xtrabackup) |
|---|---|---|
| 出力形式 | SQL / カスタム形式 | データファイルのコピー |
| 整合性 | DBエンジンが保証 | ツールが保証 |
| 速度(大規模DB) | 遅い(全件SELECTと同等) | 速い(ファイルコピー) |
| 移行・バージョン | 柔軟(別バージョン可) | 同一バージョン・同一OSが原則 |
| PITR対応 | 単体では不可(WAL/binlogと組み合わせ) | 対応(WAL/binlogと組み合わせる) |
| 小規模VPS向き | 向いている | 大規模DB・スタンバイ構成向き |
VPS上の数GBまでのDBなら、まず論理バックアップで十分です。数十GB以上になってきたら物理バックアップとPITRの組み合わせを検討します。
PITR(ポイントインタイムリカバリ)の概念
PITRは「任意の時点に復元できる」機能です。「10分前に誰かがテーブルを誤って DELETE した、その直前に戻したい」という場面で力を発揮します。
PostgreSQLのWAL(Write-Ahead Log)
PostgreSQLはすべての変更をWALに記録します。ベースバックアップ(pg_basebackup)を取った後、WALを連続して保管しておけば、ベースバックアップ時点からWALを順番に適用することで任意の時点に復元できます。
# ベースバックアップの取得(pg_basebackupを使う)
pg_basebackup -U postgres -D /backup/base -Fp -Xs -P
WALの連続アーカイブは postgresql.conf で設定します。
# postgresql.conf の該当箇所
archive_mode = on
archive_command = 'cp %p /backup/wal_archive/%f'
wal_level = replica
PITRの詳細な設定手順はPostgreSQL公式ドキュメント(Continuous Archiving and Point-in-Time Recovery)を参照してください。
MySQLのbinlog(バイナリログ)
MySQLのPITRはbinlog(バイナリログ)を使います。フルバックアップ(mysqldump)をベースに、binlogを適用することで任意の時点に復元できます。
/etc/mysql/mysql.conf.d/mysqld.cnf(または /etc/my.cnf)でbinlogを有効化します。
[mysqld]
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
expire_logs_days = 7
設定後はMySQLを再起動します。
sudo systemctl restart mysql
特定の時点までbinlogを適用する場合は mysqlbinlog を使います。
# 特定の時点(--stop-datetime)までのbinlogを適用
mysqlbinlog --stop-datetime="2026-06-26 14:00:00" \
/var/log/mysql/mysql-bin.000001 | mysql -u root -p mydb
定期実行 — cron と systemd timer
バックアップは手動では続きません。自動化が必須です。VPS上での定期実行には cron または systemd timer を使います。両者の使い分けについてはcronとsystemd timerのバックアップ自動化入門を参照してください。
cron の例(PostgreSQL)
crontab -e
毎日午前3時に実行する例です。
# 毎日03:00にPostgreSQLをバックアップ(pg_dumpの出力をgzip圧縮)
0 3 * * * pg_dump -U postgres -Fc mydb > /backup/mydb_$(date +\%F).dump
date +%F を cron から呼ぶ場合、% はバックスラッシュでエスケープします(\%F)。
cron の例(MySQL)
# 毎日02:00にMySQLをバックアップ(--single-transactionで整合性保持・gzip圧縮)
0 2 * * * mysqldump --single-transaction mydb | gzip > /backup/mydb_$(date +\%F).sql.gz
~/.my.cnf にパスワードを記載していれば -u -p 引数は不要です。
古いバックアップの自動削除
バックアップが蓄積し続けるとディスクが溢れます。30日以上前のファイルを削除するエントリを追加します。
# 30日以上前のバックアップを削除
0 4 * * * find /backup -name "*.dump" -mtime +30 -delete
0 4 * * * find /backup -name "*.sql.gz" -mtime +30 -delete
オフサイト保管
バックアップを同じサーバー上にだけ置くのは「サーバーが死んだらバックアップも消える」という状態です。これが後述するよくある失敗の最たる例です。
最低でも別の場所にコピーする仕組みを入れます。
rclone でオブジェクトストレージへ転送
rclone はさまざまなオブジェクトストレージへの転送に対応しています。Cloudflare R2・AWS S3・Backblaze B2・さくらのオブジェクトストレージなど。
sudo apt install rclone -y
rclone config # 対話形式でストレージを設定
設定後、転送コマンドをcronに追加します。
# ローカルの /backup/ をR2(または他のストレージ)に同期
rclone sync /backup/ r2:my-backup-bucket/vps-db/
オフサイト保管の戦略全体については3-2-1バックアップ戦略で整理しています。
最重要 — 復元テストの実施
バックアップ運用で最も重要なのは、定期的に復元テストを行うことです。「ファイルがある」と「復元できる」は別の話です。
なぜ復元テストが必要か
- ダンプが途中で壊れていることがある(書き込みエラー・ディスク容量不足)
- 文字コードの不一致でインポートに失敗することがある
- 復元手順が変わっていて本番障害時に戸惑うことがある
- バックアップスクリプトがサイレントに失敗し続けていることがある
PostgreSQLの復元テスト手順
# 別のDBに復元して確認する(本番DBには触れない)
createdb -U postgres mydb_test
pg_restore -U postgres -d mydb_test /backup/mydb_2026-06-26.dump
# テーブルとレコード数を確認
psql -U postgres -d mydb_test -c "\dt"
psql -U postgres -d mydb_test -c "SELECT COUNT(*) FROM <重要テーブル名>;"
# 確認後は削除
dropdb -U postgres mydb_test
MySQLの復元テスト手順
# テスト用DBを作成して復元
mysql -u root -p -e "CREATE DATABASE mydb_test;"
mysql -u root -p mydb_test < /backup/mydb_2026-06-26.sql
# テーブルとレコード数を確認
mysql -u root -p mydb_test -e "SHOW TABLES;"
mysql -u root -p mydb_test -e "SELECT COUNT(*) FROM <重要テーブル名>;"
# 確認後は削除
mysql -u root -p -e "DROP DATABASE mydb_test;"
復元テストの頻度
| 環境 | 目安 |
|---|---|
| 個人・趣味サーバー | 月1回 |
| 小規模商用サービス | 月2回〜週1回 |
| ミッションクリティカル | より高頻度+自動化 |
よくある失敗
| 失敗パターン | 問題点 | 対策 |
|---|---|---|
| 同じサーバーにのみ保管する | サーバー障害でバックアップも消える | 必ずオフサイト(別ストレージ)に退避 |
| 復元テストをしない | 壊れたバックアップに気づかない | 定期的にテスト用DBに復元して検証 |
--single-transaction を付けない(MySQL) | InnoDBでも書き込み中に不整合が発生 | mysqldumpには必ず --single-transaction |
| 文字コードが不一致 | utf8mb4 の絵文字等が化ける | 取得時と復元時の文字コードを統一する |
| バックアップスクリプトの成否を確認しない | サイレント失敗に気づかない | 終了コードをログに残す・メール通知を入れる |
| ディスクが溢れてバックアップが止まる | 古いバックアップを削除する運用がない | find -mtime +30 -delete を cron に追加 |
| binlog/WALを有効化せずにPITRを試みる | フルバックアップの時点にしか戻れない | DB設定でbinlog/WALアーカイブを最初から有効化 |
まとめ
| 項目 | PostgreSQL | MySQL |
|---|---|---|
| 論理バックアップコマンド | pg_dump -Fc | mysqldump --single-transaction |
| 復元コマンド | pg_restore / psql < | mysql < |
| パスワード安全管理 | ~/.pgpass(chmod 600) | ~/.my.cnf(chmod 600) |
| PITR基盤 | WAL(archive_mode = on) | binlog(log_bin 有効化) |
| 定期実行 | cron / systemd timer | cron / systemd timer |
| オフサイト転送 | rclone / rsync | rclone / rsync |
| 復元テスト | 別DBに pg_restore → 件数確認 | 別DBに mysql < → 件数確認 |
DBバックアップは「取れている」だけでは意味がありません。「復元できる」ことを定期的に確認する運用が、障害時に実際に役立つバックアップになります。同じサーバー上だけへの保管・復元テストの省略・--single-transaction の抜け漏れという3つの失敗を回避するだけで、大半のDBバックアップ障害は防げます。
定期実行の自動化はcronとsystemd timerのバックアップ自動化入門、オフサイト保管の戦略は3-2-1バックアップ戦略で詳しく解説しています。
各サービス公式
バックアップ運用を試す・本番で動かすVPSとしては次の2つが定番です。どちらもスナップショット機能があり、初期設定のコマンドラインバックアップと組み合わせることで多重化できます。
- ConoHa VPS: 時間課金とアプリケーションイメージが豊富。短期の検証環境から本番まで柔軟に使えます。
- Xserver VPS: 高クロックなCPUとシンプルな料金体系。国内データセンターで安定した運用ができます。