クラウド時代のDBバックアップ入門|mysqldump+S3+ライフサイクル

スポンサーリンク

本稿は、プラグイン任せの「とりあえずバックアップ」から一歩進んで、設計→実装→検証→運用までを自分の手で回せるようになるための学習コンテンツです。題材は小〜中規模のMySQL/MariaDB(WordPressなど)を想定し、mysqldumpでの論理バックアップを基軸に、圧縮・暗号化・S3転送・世代管理・復元リハーサルまでを通しで作ります。

現場で本当に役立つのは「取った」事実ではなく確実に戻せることです。そこで本講座では、RPO/RTOを言語化し、失ってよいデータ量と復旧目標時間を前提にバックアップ頻度や保持世代を設計します。実装では--single-transactionを含むダンプ方針、gzip/zstdでの圧縮、SSE-S3やSSE-KMSによる暗号化、IAM最小権限S3ライフサイクルによる保管を、コピー&ペーストで再現できるスクリプトと設定例で示します。さらに、月次の復元リハーサルで整合性を確認し、失敗時の通知と再実行ポリシーまで運用に乗せます。

学習を通じて得られる成果物は、日次取得に耐えるbackup-db.shrestore-db.sh、Prefixを厳密に絞ったIAMポリシー、S3ライフサイクルのJSON、cron/systemd timerの雛形、そして復元チェックのテンプレートです。Linuxの基本操作・Bashの実行・AWS CLIの導入ができる方なら十分に追いかけられる難易度に整えました。まずは検証環境で動かし、「いつでも戻せる」自信を積み上げていきましょう。

  1. この記事で学ぶこと(学習ゴールと前提)
    1. 学習ゴール:RPO/RTOを言語化し、設計→実装→検証まで通しで再現
    2. 前提環境と対象読者(Linux/Bash、MySQL/MariaDB、awscli)
    3. 本講座の流れと成果物(スクリプト/IAM/Lifecycle/手順書)
  2. バックアップ設計の基本(RPO/RTOと戦略)
    1. 取得頻度と世代管理(日次/週次/月次の組み合わせ)
    2. ダンプ方式の選択:–single-transaction/ロック/除外方針
    3. PITRを見据えたbinlog運用と保持期間
  3. 実装準備(資格情報と環境の安全化)
    1. .my.cnfと環境変数の使い分け・権限設定
    2. S3バケット設計:リージョン/プレフィックス/命名規則
    3. IAM最小権限ポリシー(Prefix制限・必要最小のActions)
  4. ダンプスクリプトを作る(再現性と堅牢性)
    1. mysqldumpコマンド設計:主要オプションと注意点
    2. 圧縮・暗号化・チェックサム(gzip/zstd・SSE-S3/KMS・sha256)
    3. ファイル命名とメタ情報(JSON)で整合性を担保
  5. S3転送とライフサイクルの実装
    1. aws cliでの転送(再試行・冪等性・整合チェック)
    2. ライフサイクルルール:IA/Glacier/削除の閾値設計
    3. コスト最適化:リクエスト数・転送料・保管階層のバランス
  6. 自動化と監視(運用に乗せる)
    1. cron / systemd timerの設定例と実行時間帯の決め方
    2. ログ整形と終了コード設計(syslog/JSONライン)
    3. 失敗時の通知(メール/Webhook)と再実行ポリシー
  7. 復元リハーサル(定期検証の型)
    1. 検証用DBでの復元手順と所要時間KPI
    2. 文字コード・トリガ・イベント・権限の再現確認
    3. 合格基準と記録テンプレ(チェックリスト)
  8. 応用:PITRと大規模化の選択肢
    1. binlogリプレイによるPITR最小手順
    2. mydumper/Percona XtraBackupを使う判断軸
    3. マルチDB・マルチリージョン・クロスアカウント戦略
  9. 学びを現場運用へ(定着と継続改善)
    1. よくあるつまずきと対処(サイズ異常・タイムアウト等)
    2. 週次・月次の運用チェックリスト
  10. まとめ
    1. 参考・参照リンク
  11. 関連記事

この記事で学ぶこと(学習ゴールと前提)

学習ゴール:RPO/RTOを言語化し、設計→実装→検証まで通しで再現

バックアップは「取る」より「戻せる」が本質です。失ってよい最大のデータ量(RPO)と、復旧にかけられる時間(RTO)を自分の言葉で定義し、その数字に見合う取得頻度と保持期間を設計します。本稿は、その設計をmysqldumpの論理バックアップで具体化し、圧縮・暗号化・S3保管・復元リハーサルまでを一連の流れで再現できるようにまとめています。

前提環境と対象読者(Linux/Bash、MySQL/MariaDB、awscli)

LinuxにログインしてBashが扱え、MySQL/MariaDBのクライアントが使える方を想定します。S3に触れたことがなくても大丈夫です。awscliの認証さえ通っていれば、本文のコマンドをそのまま写経しながら進められます。

本講座の流れと成果物(スクリプト/IAM/Lifecycle/手順書)

backup-db.shで取得、S3へアップロード、restore-db.shで復元検証、cronまたはsystemd timerで自動化、IAM最小権限とS3ライフサイクルで保管、という構成です。記事末にテンプレートを揃えてあります。

バックアップ設計の基本(RPO/RTOと戦略)

取得頻度と世代管理(日次/週次/月次の組み合わせ)

日次は直近7世代、週次は4世代、月次は6世代、といった層別保持が扱いやすく、コストも読みやすくなります。S3のプレフィックスを「日次」「週次」「月次」で分けるか、日付で自動的に判別するかはスクリプト側で決めます。頻度はRPOに直結します。RPOが24時間なら日次で十分、数時間以内なら日次に加えてbinlog運用の検討が必要です。

ダンプ方式の選択:–single-transaction/ロック/除外方針

InnoDB中心のWordPressなどは--single-transactionでオンライン取得が定番です。変更頻度の高いログ系テーブルはダンプから外すと復元が軽くなります。たとえばwp_actionscheduler_logsや一時テーブルのように再構築可能なものは--ignore-tableで除外します。

mysqldump \
  --single-transaction --routines --triggers --events \
  --default-character-set=utf8mb4 \
  --set-gtid-purged=OFF \
  --ignore-table=mydb.wp_actionscheduler_logs \
  mydb > mydb.sql

PITRを見据えたbinlog運用と保持期間

RPOをさらに短くしたいときは、日次ダンプ+バイナリログ(binlog)でのポイントインタイム復旧(PITR)を組み合わせます。サーバ側でlog_binを有効化し、保持期間をRPOに合わせて設定しておくと、障害直前まで巻き戻せます。

実装準備(資格情報と環境の安全化)

.my.cnfと環境変数の使い分け・権限設定

ダンプに使う資格情報は、コマンドライン引数ではなくホーム直下の.my.cnfか環境変数で安全に扱います。ファイル権限は600に落としておきます。

# ~/.my.cnf (権限 600)

[client]

user=backup_user password=強固なパスワード host=127.0.0.1

chmod 600 ~/.my.cnf

環境変数派なら、MYSQL_PWDを使う方法もありますが、プロセス一覧に出にくい.my.cnf運用の方が実務では好まれます。

S3バケット設計:リージョン/プレフィックス/命名規則

組織名やプロジェクト名を含めたバケットを用意し、ホスト名→DB名→年月日→時刻という階層で保存していくと、人間にも機械にも優しい構造になります。

s3://org-backup/db/{hostname}/{db}/{yyyy}/{mm}/{dd}/{HHmmss}_{db}_{shortsha}.sql.gz

IAM最小権限ポリシー(Prefix制限・必要最小のActions)

バックアップ専用ユーザーに対し、対象プレフィックスに限定したListBucketPutObjectGetObjectDeleteObjectのみ許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PrefixScopedAccess",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::org-backup"],
      "Condition": {
        "StringLike": {
          "s3:prefix": ["db/${aws:username}/*"]
        }
      }
    },
    {
      "Sid": "ObjectsRW",
      "Effect": "Allow",
      "Action": ["s3:PutObject","s3:GetObject","s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::org-backup/db/${aws:username}/*"]
    }
  ]
}

${aws:username}の部分をホスト名等に置き換えて固定化しても構いません。

ダンプスクリプトを作る(再現性と堅牢性)

mysqldumpコマンド設計:主要オプションと注意点

オンライン取得の基本は--single-transaction、ルーチン類は--routines --triggers --events、GTID環境でも移行しやすいよう--set-gtid-purged=OFFを添えます。対象DBを配列で持ち、ループで回すと規模が大きくなっても運用が楽です。

#!/usr/bin/env bash
set -Eeuo pipefail

DBS=("mydb")          # 取得対象DBの配列
HOSTNAME="$(hostname -s)"
DATE="$(date +%Y/%m/%d)"
TIME="$(date +%H%M%S)"
TMP="${TMPDIR:-/tmp}/backup.$$"
S3_BASE="s3://org-backup/db/${HOSTNAME}/${DATE}"
STORAGE_CLASS="STANDARD"  # or STANDARD_IA

mkdir -p "$TMP"

dump_one() {
  local db="$1"
  local sql="${TMP}/${TIME}_${db}.sql"
  mysqldump \
    --single-transaction --routines --triggers --events \
    --default-character-set=utf8mb4 \
    --set-gtid-purged=OFF \
    "$db" > "$sql"

  # 圧縮(zstdがあれば高速・高圧縮)
  if command -v zstd >/dev/null; then
    zstd -19 --format=zstd --quiet "$sql" -o "${sql}.zst"
    rm -f "$sql"
    OUT="${sql}.zst"
    EXT="zst"
  else
    gzip -9 "$sql"
    OUT="${sql}.gz"
    EXT="gz"
  fi

  # チェックサムとメタ情報
  SUM="$(sha256sum "$OUT" | awk '{print $1}')"
  SIZE="$(stat -c%s "$OUT")"
  META="${OUT}.meta.json"
  cat > "$META" <<JSON
{
  "host": "${HOSTNAME}",
  "db": "${db}",
  "ts": "$(date -u +%FT%TZ)",
  "file": "$(basename "$OUT")",
  "bytes": ${SIZE},
  "sha256": "${SUM}",
  "tool": "mysqldump",
  "version": "1.0.0"
}
JSON

  # S3へ転送(冪等に上書き)
  aws s3 cp "$OUT"  "${S3_BASE}/${TIME}_${db}.${EXT}" --storage-class "$STORAGE_CLASS"
  aws s3 cp "$META" "${S3_BASE}/${TIME}_${db}.${EXT}.meta.json" --storage-class "$STORAGE_CLASS"

  echo "OK: ${db} ${SIZE} bytes sha256=${SUM}"
}

for db in "${DBS[@]}"; do
  dump_one "$db"
done

rm -rf "$TMP"

圧縮・暗号化・チェックサム(gzip/zstd・SSE-S3/KMS・sha256)

サーバ側でのファイル暗号化を避けたい場合は、S3のサーバサイド暗号化(SSE-S3)やKMSを使います。aws s3 cpは環境側の設定でSSEを強制できます。

# 常にSSE-S3で暗号化
aws s3 cp file s3://bucket/path --sse AES256

# KMSキー指定
aws s3 cp file s3://bucket/path --sse aws:kms --sse-kms-key-id arn:aws:kms:ap-northeast-1:123456789012:key/...

ファイル命名とメタ情報(JSON)で整合性を担保

人間が見て日付やDBがわかる命名にし、メタJSONにチェックサムとバイト数を必ず記録します。復元時にsha256sumを照合すれば転送中の欠損をすぐに検知できます。

S3転送とライフサイクルの実装

aws cliでの転送(再試行・冪等性・整合チェック)

ネットワークが不安定でもawscliは自動再試行します。より厳密にしたいときは、アップロード後にaws s3 cp --no-sign-requestで匿名ヘッダを確認する方法や、aws s3api head-objectでETagとサイズを照合する方法もあります。

aws s3api head-object --bucket org-backup --key "db/host/2025/09/10/120001_mydb.zst"

ライフサイクルルール:IA/Glacier/削除の閾値設計

一定期間を過ぎた古いバックアップは、アクセス頻度の低いストレージへ自動で移行します。たとえば30日後にSTANDARD_IA、90日後にGLACIER_IR、365日後に削除というルールです。

{
  "Rules": [
    {
      "ID": "db-backup-lifecycle",
      "Status": "Enabled",
      "Filter": { "Prefix": "db/" },
      "Transitions": [
        { "Days": 30, "StorageClass": "STANDARD_IA" },
        { "Days": 90, "StorageClass": "GLACIER_IR" }
      ],
      "Expiration": { "Days": 365 }
    }
  ]
}

適用はaws s3api put-bucket-lifecycle-configurationで行います。

aws s3api put-bucket-lifecycle-configuration \
  --bucket org-backup \
  --lifecycle-configuration file://lifecycle.json

コスト最適化:リクエスト数・転送料・保管階層のバランス

日次のファイルは標準、30日超はIA、四半期を越えたらGlacier系へ、という流れにしておくと、取り出し頻度と費用のバランスが取りやすくなります。サイズが大きいほど効果が出ます。

自動化と監視(運用に乗せる)

cron / systemd timerの設定例と実行時間帯の決め方

メンテ時間に重ならない深夜帯に1回、というのが扱いやすいです。cronはシンプルで、systemd timerはログと状態管理に強いです。

# crontab -e
# 毎日 03:15 に実行(標準出力はsyslogへ)
15 3 * * * /usr/local/bin/backup-db.sh 2>&1 | logger -t db-backup

systemd派なら次の2ファイルで運用できます。

# /etc/systemd/system/db-backup.service
[Unit]
Description=MySQL logical backup to S3

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Daily MySQL backup timer

[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now db-backup.timer

ログ整形と終了コード設計(syslog/JSONライン)

成功・失敗が一目で分かるよう、最後にOK:ERROR:で始まる1行JSONを出すだけでも運用は楽になります。

echo "{\"level\":\"info\",\"event\":\"uploaded\",\"db\":\"${db}\",\"bytes\":${SIZE},\"sha256\":\"${SUM}\"}"

失敗時の通知(メール/Webhook)と再実行ポリシー

シンプルな通知はメールで十分です。Webhook派ならSlackや自前APIへcurlで投げてもOKです。

# msmtp等の設定がある前提
echo "Backup failed on ${HOSTNAME}" | mail -s "DB Backup Failure" ops@example.com

復元リハーサル(定期検証の型)

検証用DBでの復元手順と所要時間KPI

復元は「慣れ」が命です。月に一度、検証用DBへ戻して所要時間を記録しておくと、いざというときに落ち着いて対処できます。

#!/usr/bin/env bash
set -Eeuo pipefail

HOSTNAME="$(hostname -s)"
S3_KEY="s3://org-backup/db/${HOSTNAME}/2025/09/10/120001_mydb.zst"
TMP="$(mktemp -d)"

aws s3 cp "$S3_KEY" "${TMP}/dump.zst"

if [[ "${S3_KEY}" == *.zst ]]; then
  zstd -d "${TMP}/dump.zst" -o "${TMP}/dump.sql"
else
  gunzip -c "${TMP}/dump.gz" > "${TMP}/dump.sql"
fi

mysql -e "DROP DATABASE IF EXISTS mydb_restore; CREATE DATABASE mydb_restore DEFAULT CHARSET utf8mb4;"
mysql mydb_restore < "${TMP}/dump.sql"

# 簡易チェック(テーブル数)
SRC=$(mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='mydb'")
DST=$(mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='mydb_restore'")
[[ "$SRC" -eq "$DST" ]] && echo "OK: table count matched ($SRC)" || { echo "ERROR: mismatch"; exit 1; }

rm -rf "$TMP"

文字コード・トリガ・イベント・権限の再現確認

SHOW VARIABLES LIKE 'character_set_%';や、SHOW TRIGGERS; SHOW EVENTS;で差分がないかを見ます。権限はアプリ接続ユーザーのGRANTをダンプしておき、必要に応じて適用します。

-- 本番側で一度エクスポート
SHOW GRANTS FOR 'appuser'@'localhost'\G

合格基準と記録テンプレ(チェックリスト)

所要時間、テーブル数、主要画面の読み込み可否など、簡潔な合格基準を決めて記録します。小さくても毎月の記録が自信になります。

応用:PITRと大規模化の選択肢

binlogリプレイによるPITR最小手順

障害直前まで戻したいときは、日次ダンプから復元し、障害時刻の手前までbinlogを適用します。

# 例:2025-09-10T12:34:56Z 手前まで適用
mysqlbinlog --start-datetime="2025-09-10 00:00:00" \
            --stop-datetime="2025-09-10 12:34:55" \
            /var/lib/mysql/binlog.000123 | mysql mydb_restore

mydumper/Percona XtraBackupを使う判断軸

数十GBを超えるとmysqldumpが厳しくなります。並列化できるmydumper/myloaderや、物理バックアップのPercona XtraBackupを検討すると、取得時間と復元時間の両方を短縮できます。まずは論理バックアップで型を作り、阜(かさね)て必要になった段階で移行するのが安全です。

マルチDB・マルチリージョン・クロスアカウント戦略

複数DBを扱う場合は配列で列挙、S3プレフィックスにDB名を組み込みます。レジリエンスを高めたいなら、別リージョンや別AWSアカウントへのレプリカ保管も選択肢です。費用と取り出し手間のトレードオフを理解して決めます。

学びを現場運用へ(定着と継続改善)

よくあるつまずきと対処(サイズ異常・タイムアウト等)

ダンプサイズが急に増えたら、一時テーブルやログテーブルの肥大化を疑います。--ignore-tableで除外するか、アプリ側でローテーション設定を行います。mysqldumpが遅いときは、索引の多い巨大テーブルに対するオプションや、--single-transaction有無の影響を見直します。

週次・月次の運用チェックリスト

週次で成功/失敗の件数と所要時間、月次で復元リハーサルの結果とS3コストを見ます。数字が見えるだけで、改善の優先順位が自然と決まっていきます。

まとめ

バックアップは一度整えたら終わりではなく、運用の中で育てていく仕組みです。

RPOとRTOは環境や事業の変化に合わせて見直し、取得頻度や保持世代、ライフサイクルの設定を少しずつ最適化していきましょう。今日つくったスクリプトは、明日の自分が読み返しても迷わないように、命名やログ、メタ情報を丁寧に整えておくと、復元のハードルがぐっと下がります。

大切なのは「戻せる自信」を定期的に積み重ねることです。月に一度の復元リハーサルで、所要時間とチェックサム、テーブル数の一致を記録し、うまくいかなかった点はその日のうちに直します。通知や終了コードの設計がしっかりしていれば、夜間に何かあっても翌朝の判断が早くなります。最初は日次ダンプから始め、必要になったらbinlogでPITR、容量が増えたらmydumperや物理バックアップへ――段階を踏めば無理なく強くできます。

IAMの最小権限やS3のライフサイクルは、コストとセキュリティの土台です。鍵や権限の棚卸し、アカウントやリージョンをまたいだ保管など、できる範囲で堅牢さを上げていきましょう。小さな改善でも、毎週・毎月の記録が残れば成長が可視化されます。

「取れる」から「必ず戻せる」へ。バックアップは安心の土台です。今日の学びをそのまま検証環境で動かし、明日の定常運用へつなげていきましょう。

参考・参照リンク

学びを“実務”へつなぐ最短ルート

独学で積み上げた「わかった」を、仕事で使える「できる」へ。
学習ハブの内容を踏まえて、実務に直結する学習設計・添削・質問環境を整えたい人は、下記のページで具体的な進め方を確認してください。

スポンサーリンク
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする