InnoDBのウォームアップに別サーバでdumpしたib_buffer_poolを使ってみる

MySQLでスレーブを複数台並べている。
負荷増加やサーバ障害で新規スレーブを追加したい。
で、構築したばかりのMySQLスレーブをいきなりサービスに突っ込むとどうなるか。

それなりの規模のサービスであれば、buffer_poolが空っぽのDBだとIOがつまって応答遅延が発生します。

というわけでサービス投入前にbuffer_poolのウォームアップが必要で、方法としてはいくつか考えられます。

基本的にサービス投入前の完璧なウォームアップは無理ゲーなので、
普段は主要テーブルのIndexを読み込んでから、低い振り分け比率でサービス投入、ディスクIO見ながら徐々に振り分け比率を上げていく、ということをしていました。

しかししばらく前にとあるエンジニアから『稼働中DBでdumpしたib_buffer_poolを新規DBでloadする』というかなりびっくりなウォームアップ手順を聞きました。

どういうことかというと、MySQL5.6から使える以下の設定はよく知られていて、

innodb_buffer_pool_load_at_startup = 1
innodb_buffer_pool_dump_at_shutdown = 1
#innodb_buffer_pool_dump_pct  = 100

この設定があるとMySQL停止時にバッファプールの状態をダンプし、起動時にそれを読み込むことでバッファプールの状態を復旧出来ます。
このダンプファイル(ib_buffer_pool)は稼働中のDBでも取得可能なので、それを新規構築したDBで読み込めばバッファプールを本番稼働中の既存DBと同じ状態に出来ると。

これはなかなか怪しい手順です。
ダンプファイルに入っているのは、主キー値などではなくtablespace ID and page IDで、細かいことよくわかってないですが取得したDBとは別のDBでそれを使うのはかなり乱暴な気はします。

ただ今自分が主に使っている環境では、バックアップDBがあって、新規スレーブのデータは全てこのDBのEBSのスナップショットから作っています。
つまり元データは完全に同じもので、その後の更新クエリも同じもの。
なんかいけるような気もする・・・

まぁとりあえず試してみよう、ということでやってみました。

ウォームアップ手順

稼働中のスレーブでdumpを取る。

> SET GLOBAL innodb_buffer_pool_dump_now = 1;

以下のコマンドを実行してcompletedになってれば完了。
これはあっという間に完了して、サービス影響は全く無さそう。
ダンプファイルもすごく小さいです。

> SHOW STATUS LIKE 'innodb_buffer_pool_dump_status';
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status | Buffer pool(s) dump completed at 150723 15:22:44 |
+--------------------------------+--------------------------------------------------+

出力されたダンプファイルを新規DBにコピーする。

$ ls -lh /var/lib/mysql/ib_buffer_pool
-rw-rw---- 1 mysql mysql 28M Jul 23 15:22 /var/lib/mysql/ib_buffer_pool

innodb_buffer_pool_load_at_startup =1が設定されていれば、mysqlを起動するだけでダンプファイルの読み込みが始まります。

# service mysql start

既にmysqlが起動済みであれば以下のコマンドでloadを開始してもOK。
※別のloadが走ってたらinnodb_buffer_pool_load_abort=1で止めてからのほうがいいかも。

> SET GLOBAL innodb_buffer_pool_load_now = 1;

以下のコマンドでcompletedになれば完了。

> SHOW STATUS LIKE 'innodb_buffer_pool_load_status';
+--------------------------------+---------------------------+
| Variable_name                  | Value                     |
+--------------------------------+---------------------------+
| Innodb_buffer_pool_load_status | Loaded 1537/2679699 pages |
+--------------------------------+---------------------------+

> SHOW STATUS LIKE 'innodb_buffer_pool_load_status';
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_load_status | Buffer pool(s) load completed at 150723 18:51:30 |
+--------------------------------+--------------------------------------------------+

検証

とある本番環境でざっくり効果を検証してみました。
どうせ環境依存がデカイので細かいことは置いといて、1回ずつだけウォームアップ時間、サンプルクエリの実行時間を測定しました。

  • r3.2xlarge (EBS最適化オプションつけ忘れてた)
  • MySQL 5.6
  • buffer_pool_size = 45G
  • mysql用EBS : gp2 600G。全部同じスナップショットから作成

サンプルクエリは本番サーバでtcpdumpしてとった50万行のSELECTです。

$ egrep '^SELECT' /tmp/select.sql | wc -l
504316

実行時間は単純に1並列で流し込んで測定しました。

$ time mysql -uxxx -p'xxxx' db_name < /tmp/select.sql > /dev/null

比較として、いつもやってる主要テーブルのIndexの読み込みウォームアップも合わせて実施します。

$ innodb-warmer -u xxx -p 'xxxxx' -d db_name -t comments,articles,...

innodb-warmerコマンドは@songmuさんのMySQL::Warmerを@sgwr_dtsさんがrubyで書きなおしたものです。公開されてたっけな・・・

結果

warmup済みのEBSで検証してみるとこんな感じ。

ウォームアップ サンプルクエリ実行
既存稼働中サーバを振り分け外して - 2m36s
ウォームアップ無し 0 8m42s
innodb-warmerで主要テーブルを温める 66m14s 4m15s
別サーバからbuffer_poolをdumpしてload 33m と少々(dump&scp) 2m38s

別サーバからbuffer_poolをdumpしてloadした場合のウォームアップ時間は30分強で、検証クエリの実行時間は本番稼働中のサーバと同程度に速かったです。
どちらもIndex読み込み方式に比べるとかなり優秀。

wamup無しのEBSだとどうなるか

ウォームアップ サンプルクエリ実行
innodb-warmerで主要テーブルを温める 4h9m 24m41s
別サーバからbuffer_poolをdumpしてload 7h37m 2m49s

ウォームアップの時間がものすごくかかる・・・
この状態のDBを実際にサービスに投入してみます。

レプリケート取りつつほんの少しクエリ流したところから、50%->100%と振り分けに入れていくと、

f:id:mikeda:20150921140239p:plain

読み込みのIOPSはこうなる。

f:id:mikeda:20150921140308p:plain

青がサービス稼働中の既存DB、赤が『innodb-warmerで主要テーブルを温める』のやつ、緑が『別サーバからbuffer_poolをdumpしてload』。
緑のディスク読み込みが短時間で本番DBと同程度になるのに対し、赤はだいぶ時間がかかっています。

んー、こっちの結果はEBSのウォームアップはたいへんだなという意味しかないかな。
EBSのウォームアップ、もうちょっとなんとかなんないかなぁ・・・

まとめ

検証した条件では、ib_buffer_poolを別サーバから持ってきてloadするのはInnnoDBのウォームアップ手順としてなかなか優秀なように見えるので、状況に応じて使っていこうと思います。
ただこの方法が汎用的に使えるのかというと疑問があるので、ツッコミや別の検証結果があればぜひ見たいです。