RDSは何を守ってくれないのか|Multi-AZ・レプリカ・バックアップの分担

RDSは何を守ってくれないのか|Multi-AZ・レプリカ・バックアップの分担

RDSはマネージドサービスですが、「放っておいても大丈夫な部分」と「自分で設計しないと事故る部分」がはっきり分かれています。バックアップの取得やパッチ適用はAWSがやってくれますが、失敗の仕方を知らないまま運用に入ると、障害のときに初めて設計の穴に気づくことになります。

この記事では、PostgreSQLをRDSで本番運用するときに、先に決めておかないと後から効いてくる項目を整理します。

目次

Multi-AZ とリードレプリカを混同しない

Multi-AZ配置は同期レプリケーションで可用性を、リードレプリカは非同期レプリケーションで読み取り負荷分散を担うことを比較した図
名前が似ているうえに「もう1台ある」点も同じだが、守っているものが違う

どちらも「もう1台ある」構成ですが、目的が違います。

Multi-AZ 配置リードレプリカ
目的可用性読み取りの負荷分散
レプリケーション同期非同期(遅延あり)
接続スタンバイには接続できない専用エンドポイントで読み取り可
障害時自動フェイルオーバー自動では昇格しない
エンドポイント変わらないプライマリとは別

「Multi-AZ にしたからリードレプリカは要らない」も「レプリカがあるから可用性は大丈夫」も、どちらも成立しません。負荷分散と可用性は別の問題なので、必要ならどちらも入れます。

そして重要なのは、この2つはどちらも「間違って消したデータ」を守らないことです。同期レプリケーションは誤ったDELETEも忠実にコピーしますし、リードレプリカにも遅れて反映されます。データを戻せるのはバックアップだけです。

料金は何が増えるのか

Multi-AZ はスタンバイをもう1台持つ構成なので、インスタンスもストレージも2台分かかります。東京リージョンのPostgreSQLの単価で並べると、きれいに2倍です。

項目シングルAZMulti-AZ
db.m6g.large1時間 0.221ドル1時間 0.441ドル
gp3 ストレージ1GB 月 0.138ドル1GB 月 0.276ドル
gp3 の追加IOPS1 IOPS 月 0.024ドル1 IOPS 月 0.048ドル

ただし、プライマリとスタンバイの間のレプリケーションで発生するデータ転送に料金はかかりません(AWSのFAQに明記されています)。「Multi-AZ にすると転送量でも取られるのでは」という心配は要りません。増えるのは台数分の固定費です。ただしこれはRDSの中のレプリケーションの話で、アプリからDBへの通信がAZをまたぐ分は通常どおり課金されます(東京リージョンで1GBあたり0.01ドル)。

リードレプリカは、1台増やすごとにインスタンスとストレージがもう1セット増えます。加えて別リージョンに置くと、ソースのリージョンから出ていくデータに転送料金がかかります。作成時のスナップショットの転送と、その後の変更分の転送の両方が対象です。

フェイルオーバーでアプリ側に起きること

Multi-AZ のフェイルオーバーは、エンドポイント名はそのままで、DNSの向き先がスタンバイのIPに切り替わるという動きをします。ここにアプリ側の落とし穴があります。

  • DNSをキャッシュし続ける
    JVMのように、名前解決の結果を長時間(設定によっては無期限に)キャッシュする実行環境では、切り替わったあとも古いIPにつなぎ続けます。TTLを尊重する設定にしておく必要があります
  • 接続プールが古い接続を持ち続ける
    プール内のコネクションは切断されて初めて作り直されます。切断検知と、接続の最大生存時間の設定が効きます
  • 切り替え中の書き込みは失敗する
    数十秒のあいだ書き込みができない時間が発生します。リトライ処理がないと、その間のリクエストがそのままエラーになります

つまり「Multi-AZ にしたから大丈夫」はアプリ側の実装まで含めて初めて成立します。RDSにはフェイルオーバーを手動で起こす機能があるので、本番投入前に一度実際に発生させて、アプリがどう振る舞うか確認しておくのが確実です。

パラメータグループは適用タイミングに注意

デフォルトのパラメータグループは編集できないので、カスタムのものを作って適用します。最低限、次のあたりは最初に設定しておきます。

resource "aws_db_parameter_group" "postgres" {
  family = "postgres16"
  name   = "production-postgres16"

  # 1秒以上かかったクエリをログに出す
  parameter {
    name  = "log_min_duration_statement"
    value = "1000"
  }

  # 実行計画の統計を取る(Performance Insights と併用する)
  parameter {
    name  = "shared_preload_libraries"
    value = "pg_stat_statements"
    apply_method = "pending-reboot"   # static パラメータ
  }

  # ロック待ちを検知する
  parameter {
    name  = "log_lock_waits"
    value = "1"
  }
}

ここで詰まりやすいのが static と dynamic の区別です。

  • dynamic
    変更するとすぐ反映される(log_min_duration_statement など)
  • static
    インスタンスを再起動しないと反映されない(shared_preload_libraries、shared_buffers など)

staticなパラメータを変更しただけで「設定した」と思い込むと、実際には効いていない状態で運用が始まります。変更後にパラメータグループのステータスが pending-reboot になっていないかを必ず確認します。

# 反映待ちのパラメータが残っていないか確認する
aws rds describe-db-instances \
  --db-instance-identifier my-db \
  --query 'DBInstances[0].DBParameterGroups'

メモリ系のパラメータは、{DBInstanceClassMemory/4} のような式で書けます。インスタンスクラスを変えたときに追従してくれるので、固定値よりこちらが安全です。

コンクリートの部屋に半開きになった金庫の扉。太い閂が出ている写真
頑丈な扉が守る範囲は、扉の内側だけ

運用中に見るクエリ(PostgreSQL)

マネージドコンソールだけでは分からないことがあります。手元に置いておくクエリをいくつか挙げます。ここから先は RDS for PostgreSQL を前提にしたクエリです。MySQL なら SHOW PROCESSLIST や information_schema.innodb_trx が同じ役割になります。

長時間実行中のクエリを探す

SELECT pid, now() - query_start AS duration, state, left(query, 80)
FROM pg_stat_activity
WHERE state <> 'idle'
  AND now() - query_start > interval '30 seconds'
ORDER BY duration DESC;

pg_stat_activity は、いま接続しているセッションの一覧です。このクエリはそこから実行中(state が idle 以外)で、開始から30秒以上たっているものを、長い順に並べています。duration は now() - query_start なので「そのクエリが何秒走り続けているか」、left(query, 80) はクエリ本文が長くなりがちなので先頭だけを出しています。

30秒はしきい値の例です。バッチが動く時間帯なら長めに、オンライン処理だけのデータベースなら5秒でも構いません。障害のときに初めて実行するのではなく、平常時にも流して「いつもの並び」を知っておくと、異常かどうかの判断が速くなります。

止めるときは、出てきた pid を使います。

-- クエリだけ止める(接続は残る)
SELECT pg_cancel_backend(12345);

-- それでも止まらないときは、セッションごと切る
SELECT pg_terminate_backend(12345);

pg_cancel_backend は実行中のクエリをキャンセルし、pg_terminate_backend はセッションそのものを終了します。どちらもアプリ側にはエラーとして返るので、リトライやトランザクションの扱いを確認してから実行します。他人のセッションに対して実行するには pg_signal_backend の権限が必要ですが、RDS のマスターユーザーは rds_superuser なので、どの接続でも止められます。

放置されたトランザクションを探す

SELECT pid, now() - xact_start AS xact_age, state, left(query, 80)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY xact_age DESC;

idle in transaction はとくに厄介です。アプリがトランザクションを開いたまま放置している状態で、その間 VACUUM が不要行を回収できません。ディスク使用量がじわじわ増え、テーブルが肥大化し、やがて性能が落ちます。CPU使用率もコネクション数も正常に見えるので、原因にたどり着くのが遅れがちです。

対策として idle_in_transaction_session_timeout を設定し、一定時間で強制的に切断するようにしておきます。

VACUUM の状況を見る

SELECT relname,
       n_live_tup, n_dead_tup,
       round(n_dead_tup * 100.0 / nullif(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
       last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY n_dead_tup DESC;

更新の多いテーブルで dead_pct が下がらず last_autovacuum も古い場合、autovacuum が追いついていません。テーブル単位で autovacuum_vacuum_scale_factor を下げて、実行頻度を上げる調整が要ります。

バックアップは「戻せること」まで確認する

自動バックアップの保持期間は最大35日です。ここで押さえておきたい性質があります。

  • ポイントインタイムリカバリの復元先は、必ず新しいインスタンスになる
    既存のインスタンスを巻き戻すことはできません
  • したがって復元後は、アプリの接続先を切り替える作業が必ず発生します
    パラメータグループ・セキュリティグループ・サブネットグループの指定も引き継ぎが必要です
  • インスタンスを削除すると自動バックアップも消えます
    残したい場合は削除前に手動スナップショットを取ります
  • 保持期間を0にすると自動バックアップが無効になり、ポイントインタイムリカバリも使えません

いちばん大事なのは、復元を一度実際にやってみることです。手順書だけ用意して試していないと、本番で焦ることになります。復元先が新インスタンスになる以上、切り替えの所要時間は事前に測っておかないと分かりません。

バックアップの料金

バックアップの保存先はS3で、料金はリージョン単位の合計(自動バックアップ+手動スナップショット)で決まります。そのリージョンでプロビジョニングしているDBストレージの合計までは無料で、超えた分だけが課金されます。東京リージョンの超過分は1GBあたり月0.095ドルです。

つまり請求に出てくるのは、保持期間を長くしたときと、手動スナップショットを消さずに貯めたときです。保持期間を35日にすれば、それだけバックアップの総量も増えます。スナップショットをS3へエクスポートする場合は、別途1GBあたり0.012ドルがかかります。

単価はAWSが公開している料金データ(東京リージョン、2026年9月時点)から取りました。

メンテナンスウィンドウを放置しない

マイナーバージョンアップやOSパッチはメンテナンスウィンドウ中に適用されます。作成時に指定しないと自動で割り当てられるため、知らないうちに業務時間帯に設定されていることがあります。

  • アクセスの少ない時間帯に、バックアップウィンドウと重ならないよう設定する
  • Multi-AZ ならフェイルオーバーで済むが、それでも切り替えの瞬間に接続断は起きる
  • 単一AZ構成の場合は、適用中そのまま停止する
    本番で単一AZを選ぶ理由はほぼない

まとめ

  • Multi-AZ は可用性、リードレプリカは負荷分散
    どちらも誤削除は守らない
  • フェイルオーバーはDNS切り替え
    アプリ側のDNSキャッシュと接続プールとリトライまで含めて設計する
  • staticパラメータは再起動しないと効かない
    設定したつもりを作らない
  • idle in transaction を放置しない
    VACUUMが止まり、静かに肥大化する
  • 復元は新インスタンスに作られる
    切り替え手順を用意し、一度実際に試す

マネージドサービスが引き受けてくれるのは作業であって、設計と確認ではありません。分担がどこで切れているかを把握しておくのが、RDS運用の実際のところだと思います。

次に読む記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

わどこんのアバター わどこん

実務12年のバックエンド・インフラエンジニア。バックエンド開発からクラウド・インフラの設計・構築・運用まで担当しています。主要言語は Java・Kotlin・PHP・Python。運用の現場で拾った知見を、再現できる手順に落として残すのがこのブログのテーマです。

目次