Aurora/RDS PostgreSQLの違いと性能差の原因
目次
Aurora PostgreSQLとRDS for PostgreSQLは、どちらも中身はPostgreSQLです。それでもRDSからAuroraへ移行した直後に「同じクエリなのに実行計画が変わった」「レプリカ遅延のメトリクスの意味が違う」「月末の請求にI/Oという項目が増えた」といった食い違いが出ます。原因はストレージ層の設計差です。この差がパラメータ既定値とコスト構造に波及します。
構造の違いを押さえたうえで、移行後に性能やレプリカ挙動が想定と違うとき、どのビューとどのパラメータを見れば差分を特定できるかを手順にまとめます。
結論:挙動差の原因はストレージ層・レプリケーション方式・パラメータ既定値の3点に集まる
Auroraとの挙動差を調べるとき、確認すべき順序は次の3点です。
- パラメータ既定値の差、特に
shared_buffers。RDS for PostgreSQLは搭載メモリの1/4程度、Aurora PostgreSQLはメモリの大部分を割り当てる既定式になっています。同じインスタンスサイズでもバッファキャッシュのヒット率が変わり、I/O量・レイテンシ・コストに波及します。 - WALとコミットパスの差。Auroraはストレージ層へredoレコードを送り、ページの再構成はストレージ側が行います。RDSは通常のPostgreSQLと同じく自インスタンスでWALを書き、Multi-AZならEBSレベルの同期ミラーへの書き込み完了を待ちます。コミット待機の待機イベント名が変わるのはこのためです。
- レプリケーション方式の差。Auroraのリーダーは同じストレージボリュームを参照し、RDSのリードレプリカは専用ストレージへストリーミングレプリケーションで追随します。遅延の単位がミリ秒かバイトか、遅延が膨らむ条件も違います。
逆に、ロック競合やSQLの書き方が原因の遅さは、Auroraに移しても解決しません。切り分けの出発点を間違えないようにしてください。
Aurora PostgreSQL と RDS for PostgreSQL は何が違うのか
SQL方言、MVCCとVACUUMの仕組み、オプティマイザ、ロックの種類、拡張機能の多くは共通です。pg_stat_statementsやEXPLAINの読み方も同じ知識で通用します。違うのは「ページとログをどこに置き、どう複製するか」のレイヤーです。
ストレージとWALの扱い:共有分散ストレージへのWAL書き込みとEBS+同期ミラーの差
RDS for PostgreSQLは、EBSボリューム(gp3 / io1 / io2など)を1インスタンスに紐付けた構成です。コミット処理は通常どおりで、WALをローカルのWAL領域にfsyncし、ダーティページはチェックポイントとバックグラウンドライタがデータファイルへ書き出します。Multi-AZ(単一スタンバイ構成)ではストレージをブロックレベルで同期ミラーするため、コミットのレイテンシにAZ間の往復が加算されます。またページ破損対策としてfull_page_writesが有効で、チェックポイント直後はWAL量が増えます。
Aurora PostgreSQLでは、インスタンスはデータファイルを直接書きません。コミット時にredoレコードを3つのAZに分散した共有ストレージへ送り、書き込みクォーラムが成立した時点でコミットが完了します。ページは各ストレージノードがredoを適用して作るため、
- チェックポイントによるダーティバッファの一括フラッシュが性能に与える影響が小さい
- ページ全体の二重書き(full page writes)が構造的に不要になり、ネットワークへ流す量が減る
max_wal_sizeやcheckpoint_completion_target系のチューニングは、RDSと同じ意味を持たない
という違いが生まれます。移行前に時間をかけたWAL/チェックポイントのチューニングがAuroraで空振りするのは、これが理由です。該当パラメータが変更可能かどうかは、自分のDBクラスターパラメータグループでIsModifiableを確認してください。
レプリカとフェイルオーバー:同一ストレージ参照とストリーミングレプリケーションの差
RDSのリードレプリカは、プライマリからWALを受け取って自分のストレージに適用する物理ストリーミングレプリケーションです。遅延はReplicaLag(秒)で見え、主因はWAL適用(recoveryプロセスの単一スレッド適用)とレプリカ側のI/Oです。更新が重いバッチ中にレプリカが数十秒から分単位で遅れるのは、この構造では珍しくありません。
Auroraのリーダーは、ライターと同じストレージボリュームを読みます。WALをストレージに書く作業がないため、ライターから届いたredo情報で自分のバッファキャッシュを更新・無効化するだけで済み、遅延は通常ミリ秒オーダーです。メトリクスもAuroraReplicaLag(ミリ秒)で、単位も意味もRDSと異なります。ただしゼロではなく、ライターの更新スループットが高いとき、リーダーのCPUが飽和しているとき、リーダー側で長時間クエリが走っているときには伸びます。
遅延の正体と監視の組み方は Auroraレプリカラグ監視|AuroraReplicaLag で詳しく整理しています。
実務でもう一つ差が出るのが、Auroraでは外部に自前の物理スタンバイを立てられない点です。pg_basebackup+物理スロットという構成は取れません。クラスター外へデータを流すなら論理レプリケーションかDMSを使います。論理スロットは両者で使えますが、スロットを放置するとWALが溜まってストレージを圧迫する点は共通の注意事項です(PostgreSQL論理レプリケーションのWAL保持とスロット運用)。
フェイルオーバーは、Auroraがストレージを共有しているためリーダーの昇格が主な作業で、一般にRDS Multi-AZより短時間で完了します。ただし接続がどのエンドポイントを掴んでいるか、アプリ側のDNSキャッシュTTL、コネクションプールの再接続設計で体感時間は変わります。切り替え時間の検証は、自分のアプリ構成で行ってください。
症状別:移行後に「遅い・おかしい」ときの切り分け手順
移行後に性能が想定と違うときの実作業です。順番に意味があり、1 → 2 → 3の順で見ると手戻りが少なくなります。
手順1:パラメータグループとマイナーバージョンの差分を洗い出す
最初は環境差分の棚卸しです。両環境で以下を実行して差分を取ります。
-- 既定値から変更されている設定だけを抽出
SELECT name, setting, unit, source, boot_val, reset_val
FROM pg_settings
WHERE source NOT IN ('default', 'client')
ORDER BY name;
-- プランに影響の大きいものをピンポイントで確認
SELECT name, setting, unit
FROM pg_settings
WHERE name IN (
'shared_buffers','effective_cache_size','work_mem','maintenance_work_mem',
'random_page_cost','seq_page_cost','effective_io_concurrency',
'max_parallel_workers_per_gather','jit','default_statistics_target',
'autovacuum_vacuum_cost_limit','autovacuum_naptime'
)
ORDER BY name;
バージョンも必ず突き合わせます。
SELECT version();
SHOW server_version;
SELECT aurora_version(); -- Aurora のみ。Aurora 固有のパッチレベルを確認
代表例がshared_buffersです。RDS for PostgreSQLの既定式は{DBInstanceClassMemory/32768}(8kB単位なのでメモリの約1/4)、Aurora PostgreSQLはSUM({DBInstanceClassMemory/12038})のようにメモリの大部分を割り当てる式になっています。式はバージョンやインスタンスファミリーで変わり得るので、実際の値はパラメータグループとSHOW shared_buffersの両方で確認してください。
つまりAuroraはキャッシュに乗る前提、RDSはストレージI/Oが出る前提でチューニングされています。したがって、
- RDS時代に
effective_cache_sizeやrandom_page_costを手で調整していた場合、その値はAuroraでは過大または過小になり得る - Auroraのまま OSメモリが不足気味なら、
work_mem×同時実行数の積を見直す
という判断になります。jitも要注意です。短時間クエリが大量に走るワークロードでjit = onだと、JITコンパイル時間がレイテンシに乗ることがあります。移行前後で値が変わっていないか確認してください。
手順2:待機イベントでボトルネックの層(CPU・I/O・ロック)を特定する
パラメータ差分を解消したら、次は「どこで待っているか」です。スナップショットでよければ以下で足りますが、継続的に見るならPerformance InsightsのDB Loadを待機イベント別に積み上げます。
SELECT coalesce(wait_event_type, 'CPU') AS type,
coalesce(wait_event, 'CPU') AS event,
count(*)
FROM pg_stat_activity
WHERE state = 'active' AND pid <> pg_backend_pid()
GROUP BY 1, 2
ORDER BY 3 DESC;
読み方の目安は次のとおりです。
| 待機イベント | 示唆 | 対処の方向 |
|---|---|---|
CPU(wait_event が NULL) |
実行計画が悪い、関数が重い、接続過多 | SQL とプランの見直し、プーリング |
IO:DataFileRead |
必要なページがバッファに乗っていない | shared_buffers、インデックス設計、不要な全件走査 |
IO:XactSync(Aurora で頻出) |
コミット待ち。小さなトランザクションの多発 | 一括コミット化、バッチの作り方 |
IO:WALWrite / IO:WALSync(RDS で頻出) |
ローカル WAL の書き込み待ち | ストレージの IOPS/スループット、コミット頻度 |
Lock:transactionid / Lock:relation |
行ロックや DDL のブロック | トランザクション長、lock_timeout の設計 |
LWLock:BufferMapping など |
内部ラッチ競合 | 同時実行数、ホットなページの分散 |
ロック待ちが支配的なら、ストレージの差は関係ありません。Auroraに移しても改善しません。ロック周りの切り分けは PostgreSQL運用トラブルの原因と確認手順|VACUUM・実行計画・ロック の手順がそのまま使えます。
手順3:実行計画と統計情報を比較し、I/Oコスト前提の違いを疑う
待機がCPUかIO:DataFileReadに寄っているなら、実行計画の比較に進みます。プラン本体だけでなくバッファとパラメータを同時に出すのがコツです。
EXPLAIN (ANALYZE, BUFFERS, SETTINGS, VERBOSE)
SELECT ...;
SETTINGS(PostgreSQL 12以降)は、既定値から変更されたプラン関連パラメータを出力します。手順1の差分が本当にプランに効いているかをここで確認できます。BUFFERSではshared hitとshared readの比を両環境で比べます。プラン形状が同じでも、shared_buffersの差でreadの量が変わり、実測時間だけが違うケースを切り分けられます。
プラン形状そのものが変わっている場合は、統計情報を疑います。
SELECT relname, n_live_tup, n_dead_tup, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
SELECT schemaname, tablename, attname, n_distinct, null_frac, correlation
FROM pg_stats
WHERE tablename = 'target_table';
統計情報の扱いは移行方式で変わります。論理的な移行(pg_dump/restore、DMS)では統計が引き継がれないため、移行後に明示的なANALYZEを実行しないと誤ったプランを選びます。スナップショット復元での統計の扱いは Aurora PostgreSQLスナップショット復元と統計情報 で検証ポイントを整理しています。
推定行数と実測行数のズレの読み方は PostgreSQL EXPLAIN ANALYZEの見方 を参照してください。ズレが10倍以上なら統計か相関、ズレが小さいのに遅いならバッファかロックという切り分けになります。
この記事のテーマをさらに学べる本
PostgreSQLの内部構造やレプリケーション、運用監視と性能改善を体系的に扱っており、切り分けの土台になります。
楽天ブックスで詳しく見るコストと運用で見落としやすい違い(I/O課金・バックアップ・拡張機能)
課金モデルは構造が違うため、インスタンス単価の比較だけで判断すると外します。
ストレージとI/Oから見ていくと、RDS for PostgreSQLはプロビジョニングしたボリューム容量に対する課金です。gp3やio1/io2ではIOPSとスループットのプロビジョニング分も費用になり、使っていなくても確保分は課金されます。一方Auroraは実使用量ベースのストレージ課金に加え、標準構成ではI/Oリクエスト数に対する課金があります。shared_buffersにヒットした読み取りは課金対象になりませんが、キャッシュに乗らない範囲を広くスキャンするワークロードではI/O課金が無視できない額になります。I/Oが支配的な場合はI/O課金を含まない構成(Aurora I/O-Optimized)の方が総額で有利になることがあり、CloudWatchのVolumeReadIOPs / VolumeWriteIOPsと請求内訳で実測して比較します。単価や課金単位は改定されるため、金額の判断は公式の料金ページとCost Explorerの実績で行ってください。
レプリカのコスト構造も違います。RDSのリードレプリカは自分のストレージを持つため、インスタンス費用とストレージ費用がレプリカごとに増えます。Auroraはストレージを共有するので、レプリカ追加のコストはインスタンス費用が中心です。参照系をスケールアウトしたいケースでは、これが選定理由になり得ます。
バックアップはどちらも、自動バックアップがデータベースサイズ相当までは追加費用のかからない設計で、保持期間を延ばして総量が超えた分が課金対象です。Auroraにはcopy-on-writeでクラスターを複製するクローンがあり、本番相当データでの検証環境を短時間・低コストで作れます(同一リージョンのAurora間のみ)。検証環境の作り方という運用面では差が大きい部分です。
拡張機能の対応状況はエンジンとバージョンで異なります。移行前に両環境で突き合わせてください。
SELECT name, default_version, installed_version
FROM pg_available_extensions
ORDER BY name;
SHOW shared_preload_libraries;
Auroraにはapg_plan_mgmt(クエリプラン管理)のような固有機能があり、RDS側の機能(RDS Multi-AZ DBクラスターなど)はAuroraの構成と別概念です。pg_stat_statements、pgaudit、pg_cronのように両方で使えるものも、shared_preload_librariesへの登録が必要なものはパラメータグループの設定漏れで動きません。移行チェックリストに入れておいてください。
よくある質問
RDS for PostgreSQL から Aurora に移行すれば性能は上がる?
上がるケースと変わらないケースがはっきり分かれます。改善が期待できるのは、コミットが多くてWAL書き込みやMulti-AZ同期ミラーの待ちが支配的なワークロード、参照をスケールアウトしたいワークロード、shared_buffersの既定差でキャッシュヒット率が上がるケースです。
一方、ロック競合、インデックス不足による全件走査、N+1クエリ、長時間トランザクションによるVACUUM阻害が原因の遅さは、ストレージを変えても解決しません。手順2の待機イベント分析で、ボトルネックがどの層にあるかを先に確定させてください。
Aurora のリードレプリカでも参照遅延は起きる?
起きます。同じストレージを参照するため通常はミリ秒オーダーですが、ライターの更新スループットが高いとき、リーダーのCPUが飽和しているときには伸びます。「書き込み直後にリーダーを読んで最新が見えない」という整合性の問題は遅延がミリ秒でも発生し得るので、更新直後の参照はライターに向けるなどアプリ側の設計で対処します。
また、リーダー側で長時間クエリを走らせていると、リカバリ処理との競合でクエリがキャンセルされることがあります(canceling statement due to conflict with recovery)。関連パラメータの有無と既定値はバージョンで異なるため、自分のパラメータグループで確認してください。
Aurora から RDS for PostgreSQL に戻すことはできる?
スナップショットをAuroraからRDSへ直接復元する形では戻せません。実務上は論理的な移行、つまりpg_dump / pg_restore、論理レプリケーション(Aurora PostgreSQLはパブリッシャーになれます)、DMSのいずれかを使います。ダウンタイムを短くしたいなら、論理レプリケーションで追随させてから切り替える方式が現実的です。移行方向ごとの対応手段は変わることがあるため、着手前に公式ドキュメントで現在の方式を確認してください。
「あとで戻せる」前提での設計は避けてください。戻す作業は実質的に再移行であり、データ量に比例した時間とダウンタイム設計が必要になります。
使っている拡張機能はそのまま移行できる?
多くは移行できますが、事前確認が必要です。確認すべきは3点で、移行先で拡張が提供されているか(pg_available_extensions)、バージョンが同じか(default_versionの差で関数シグネチャや挙動が変わることがあります)、shared_preload_librariesや専用パラメータの設定が必要か、です。特にPostGISのようにバージョン依存が強い拡張は、移行先のバージョン組み合わせを先に固定してから計画を立てます。
OSS-DB Silver 対策テキスト(OSS教科書)
PostgreSQLの仕組みを体系的に学ぶなら、OSS-DB Silverの公式対応テキストが近道です。資格を取らなくても基礎固めに役立ちます。
再発防止:移行・選定時に押さえるチェックポイントとさらに学ぶには
移行や選定の前に以下を成果物として残しておくと、移行後の「おかしい」を切り分ける時間を短縮できます。
- パラメータ差分表。移行元・移行先の
pg_settingsをCSVで出してdiffを取る。特にshared_buffers、effective_cache_size、work_mem、random_page_cost、jit、max_parallel_workers_per_gather、autovacuum系。 - ベースライン性能。移行前に主要SQLの
EXPLAIN (ANALYZE, BUFFERS, SETTINGS)とpg_stat_statementsの上位クエリを保存する。比較対象がないと、遅くなったかどうかの議論ができません。 - 統計情報の再取得計画。移行方式に応じて
ANALYZE(必要ならdefault_statistics_targetを上げてから)を実施する手順を移行手順書に組み込む。 - I/O実測値。CloudWatchの読み書きIOPSと
pg_stat_databaseのblks_read/blks_hitを取得し、AuroraのI/O課金有無を含む構成比較の根拠にする。 - フェイルオーバーの実測。エンドポイント、DNS TTL、コネクションプールの再接続設定を含めた状態で切り替えを試し、アプリから見た復帰時間を把握する。
- 拡張機能の棚卸し。
pg_available_extensionsの差分とshared_preload_librariesの設定要否。 - 待機イベントの常時観測。Performance Insightsを有効にしておく。事後に「あのとき何を待っていたか」を追えるかどうかで原因特定の難易度が変わります。
AuroraとRDSの違いは、PostgreSQLのどの層をAWS側に寄せたかの差です。どちらを選んでもMVCCとVACUUM、実行計画、ロック、待機イベントというPostgreSQL本体の知識がそのまま効きます。マネージドサービス固有の差分は、この基礎があってはじめて正しく評価できます。