復元したインスタンスだけ本番より遅い
スナップショットやポイントインタイムリカバリ(PITR)から復元したRDSに接続すると、本番と同じクエリなのに応答が遅く、アプリケーションがタイムアウトします。しばらく使っていると改善するため、障害なのか設定ミスなのか、待てば直るのかの判断がつきにくい。
主因はEBSのファーストタッチペナルティ
RDS(非Aurora)のDBインスタンスは、ストレージにEBSボリュームを使います。スナップショットから作ったボリュームはすぐavailableになりますが、ブロックの実体はバックグラウンドで順次取得されます。まだ取得が終わっていないブロックに初めてアクセスすると、その場でS3から取りに行くため、I/O 1件あたりのレイテンシが上がります。
一度アクセスされたブロックは以降EBSボリューム上に存在するので、2回目からは本来のレイテンシで返ります。DBを再起動して共有バッファが空になっても、EBS側の実体化はやり直しになりません。初期化が一度で済むのはこのためです。
ここで、DBのバッファキャッシュのウォームアップとは別の話だと整理しておくと混乱しません。EBSブロックの実体化は解消すれば永続します。一方でPostgreSQLのshared_buffersやInnoDBのbuffer poolはプロセスのメモリ上にあるため、再起動やフェイルオーバーで失われます。Auroraは共有ストレージ層を使うのでファーストタッチペナルティに該当しませんが、バッファキャッシュが空から始まる点は同じです。
対処は1つだけ。本番トラフィックを流す前に、主要なテーブルとインデックスのブロックを一度読んでおいてください。インスタンスクラスのスケールアップは、I/O待ちであってCPU不足ではないため根本解決になりません。
切り分けは3点だけ見る
復元直後のタイミングかどうか、CloudWatchのReadLatencyが通常運用時より高いかどうか、そのレイテンシが時間経過とともに改善しているかどうか。3点が揃えば、ほぼ遅延ロードと判断してウォームアップに進めます。横ばいや悪化なら別の原因を疑ってください。
判断の軸は「レイテンシが高い理由が、件数の多さで説明できるか」です。ReadLatencyが高いのにReadIOPSやReadThroughputがボリュームの上限に達していないなら、1件あたりが遅いということで遅延ロードに整合します。上限に張り付いた結果レイテンシが上がっているなら、スロットリングや容量設計の問題として、gp2のBurstBalanceやgp3の設定値を確認します。絶対値の閾値を決め打ちせず、復元元の本番インスタンスの同一メトリクスと並べるのが確実です。
机上で考えるより、同じクエリを2回流すほうが早い。
-- PostgreSQL の例
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM <テーブル名>;
-- 同じものをもう一度
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM <テーブル名>;
2回目が明確に速くなり、BUFFERSのshared readが減っていればI/O由来です。2回目も同じだけ遅いなら、実行計画やロック待ち、CPU側を見る段階に移ります。なお2回目が速い理由がEBSの実体化なのかDBのバッファキャッシュなのかは、この方法では区別できません。
ウォームアップはDB側のクエリで行う
EC2のEBSならddやfioでボリュームを初期化できますが、RDSはOSにログインできないため使えません。代わりにDBエンジンの上から全データブロックを読むクエリを実行します。PostgreSQLではpg_prewarmが扱いやすい。
CREATE EXTENSION IF NOT EXISTS pg_prewarm;
-- read モード:ブロックを読むが shared_buffers は使わない
SELECT pg_prewarm('<テーブル名>', 'read');
-- buffer モード:shared_buffers にも載せる
SELECT pg_prewarm('<テーブル名>', 'buffer');
EBSの実体化が目的ならreadで十分です。bufferは既存のshared_buffersの内容を追い出すので、重要なテーブルとインデックスに限定して使ってください。pg_prewarmはrelation単位のため、テーブルを指定してもインデックスやTOASTは対象外です。インデックスを読み忘れると、本番トラフィックでインデックス検索が走った時点で同じ遅さが出ます。
MySQL/MariaDB、Oracle、SQL Serverでも目的は変わりません。テーブル本体と全セカンダリインデックスのブロックを一度読みます。
詳しくは
元記事では、テーブルとインデックスとTOASTをpg_classから列挙してまとめて読む例、VACUUM (DISABLE_PAGE_SKIPPING)などpg_prewarmが使えない場合の代替、MySQL/Oracle/SQL Serverそれぞれのヒント付きクエリを掲載しています。パラメータグループや統計情報など「I/O以外」の原因の切り分け、数TB規模でのウォームアップ優先順位とI/Oクレジットへの配慮、復元から本番公開までのAWS CLI手順とRTO見積もりへの組み込み方も扱っています。