2009年8月12日水曜日

InnoDB Plugin 1.0.4 - InnoDB史上極めて重要なリリース

 日本時間の今日、InnoDB Pluginの新バージョン1.0.4がリリースされました。このバージョンでは、「バイナリログを有効にするとグループコミットが効かなくなる問題」が修正されています。ほとんどの環境にとって極めて効果の高い修正です。ほかにもI/Oスレッドの多重化(同様のものがMySQL5.4にも搭載)など効果的な修正が行なわれています。
 InnoDB PluginはまだGA(安定版)ではないので、品質面では標準搭載されているInnoDBよりも落ちます。ただしMySQL Enterpriseサブスクリプションを買っている方であれば追加費用無しでInnoDB Pluginのサポートを受けることができるので、お気軽に試してみて頂ければと思います。
 グループコミット問題修復の効果のほどは、一目瞭然なので図を見た方が分かりやすいでしょう。下図は、mysqlslapで、複数のコネクションから並列でINSERTを行なったときの結果です。
mysqlslap  --concurrency=30 --iterations=1 --engine=innodb \
--auto-generate-sql --auto-generate-sql-load-type=write \
--number-of-queries=100000


my.cnfのパラメータは以下のとおり。H/WはIntel Xeon X5560 Nehalem 2.80GHz * 16cores, 12GB RAM, SAS HDDです。innodb_flush_log_at_trx_commit=1でコミット時同期書き込みを有効にして、バイナリログを有効にして、ストレージではバッテリーバックアップつきライトキャッシュを有効にする、というオーソドックスな設定です。
[mysqld]
basedir=/usr/mysql5137
datadir=/data/mysql5137/data
ignore_builtin_innodb
plugin-load=innodb=ha_innodb.so;innodb_trx=ha_innodb.so;
innodb_locks=ha_innodb.so;innodb_lock_waits=ha_innodb.so;
innodb_cmp=ha_innodb.so;innodb_cmp_reset=ha_innodb.so;
innodb_cmpmem=ha_innodb.so;innodb_cmpmem_reset=ha_innodb.so

innodb_log_files_in_group=2
innodb_buffer_pool_size=2G
innodb_flush_method=O_DIRECT
innodb_log_file_size=512M
innodb_data_file_path=ibdata1:500M:autoextend
innodb_file_per_table
log-bin
table_cache=8192

明らかにInnoDB Plugin 1.0.4の方が高速化しています。通常版のInnoDBでは接続数の増加に対してスケールしていませんが、InnoDB Plugin 1.0.4ではスケールしており、30接続では6.1倍もの性能差が出ていることが分かります(innodb_support_xa=1の場合)。innodb_support_xaを0にすることもできますが、後述の理由で一般的には推奨されません。
以下では、このような性能差が出た理由について述べます。

●グループコミットの修正とは何か
 耐障害性を高めるために、トランザクションがコミットされると、その更新情報がREDOログファイルに同期書き込みされます。このことをコミット時同期書き込みと呼びます。ディスクへの同期書き込みはコストの高い処理で、バッテリーバックアップつきライトキャッシュが搭載されていてもせいぜい毎秒10000回程度しかできません。高速なSELECTなら毎秒数万回くらいは実行できますから、fsyncはパフォーマンスの阻害要因になります。
 そこでInnoDBでは、複数のスレッドから同期書き込みが行なわれた場合は、まとめ上げて少数回数の同期書き込みを行なう機能を搭載しています。これをグループコミットと呼びます。グループコミットは、ほとんどのRDBMSが機能として持っています。
 一方MySQL5.0から、2相コミットの機能が搭載されました。2相コミットは、異なるサーバー間のトランザクションの整合性を保つという(あまり使われない)機能がありますが、MySQLでは異なる(トランザクション対応の)ストレージエンジンおよびバイナリログ間でのトランザクションの整合性を保つという上で、インスタンス1個の環境でも意味があります。ほとんどのケースで「バイナリログとInnoDB」の整合性を保つために使われます。
 たとえば、「バイナリログに書き込んだ後、InnoDBログファイルに書き込む前にクラッシュした」という状況が起きたとします。2相コミットでない場合、リカバリするとバイナリログにだけ書かれていてInnoDBには書かれていないデータが存在することになります。それはスレーブにも転送されるので、「スレーブには存在するけどマスターには無いデータがある」という状態になってしまいます。2相コミットを使うと、「InnoDBログファイルにprepareする→バイナリログに書き込む→InnoDBログファイルにcommitする」という動作になります(2相とはprepare、commitの2フェーズという意味)。「バイナリログに書き込んだ後、InnoDBログファイルに書き込む前にクラッシュした」場合には、事前にprepareで書き込んだデータを用いてリカバリできます。また「バイナリログに書き込む前にクラッシュした」場合には、prepareしたものを無かったことにできます。いずれの場合でも、バイナリログとInnoDB間を整合性のある状態にできます。
 InnoDBでの2相コミットの実装ですが、従来はこうなっていました。
mutex(prepare_commit_mutex)確保
prepareのためにInnoDBログに書き、同期書き込みする
バイナリログに書く
commitのためにInnoDBログに書き、同期書き込みする
mutex解放

 mutex内は同時に1個のスレッドしか動くことができないので、従来型のアプローチだと、ファイルへの同期書き込みがシリアライズされます。つまり100個のスレッドが同時にコミットしたら、prepareのために100回のfsync、commitのために100回のfsyncの計200回が呼ばれます。つまりグループコミットが崩れてしまっていたのです。
 一方、InnoDB Plugin 1.0.4ではこうなりました。
prepareのためにInnoDBログに書き、同期書き込みする
mutex確保
バイナリログに書く
commitのためにInnoDBログに書く
mutex解放
InnoDBログを同期書き込みする

 InnoDB Plugin1.0.4のアプローチでは、同期書き込みをクリティカルセクションの外に出しています。そのため、複数のトランザクションからの同期書き込み命令をまとめあげるグループコミットが機能します。これによって、fsyncの実行回数を大幅に削減できます。以下は、fsyncの実行された回数を示す、Innodb_data_fsyncsの推移を示したものです。

 5.1.37+Builtin(support_xa=1)では、同時接続数に関係なく、1トランザクションあたり2回のfsyncが発生しています。innodb_support_xa=0の場合は1トランザクションあたり1回です。いずれも、グループコミットが効いていないことが分かります。最初のグラフと重ね合わせると、どちらも1秒あたりのfsync回数が10,000前後になっていますが、これは一般的なHDD(+ライトキャッシュ)の限界に近い値で、fsyncがボトルネックになっていると考えられる結果です。一方InnoDB Plugin 1.0.4の場合は、接続数の増加に対して劇的にInnodb_data_fsyncsの増加量が減っています。たとえば30接続(innodb_support_xa=1)のときは、10万トランザクションに対して増加量が200251から26092と、87%も減っています。よってグループコミットが効いていることが分かります。
 また、クリティカルセクションの中でバイナリログとInnoDBログの両方に書いている(同期書き込みはしていない)ので、「InnoDBログファイルへの書き込み順と、バイナリログへの書き込み順が一致しなくなる」という問題が起きることはありません。
 Prepareの順序は保証されないではないか、と言われるかもしれませんが、これは問題ありません。障害のタイミングによっては、prepareされたけれどもcommitされていないトランザクションが存在しえますが、これはそのままではアプリケーションからは見えません。MySQLでは、リカバリ時にはバイナリログをまず読んで、トランザクションID(xid)をピックアップします。次にストレージエンジン(InnoDB)を読んで、prepareされているけれどcommitされていないxidを特定します。そうして特定されたxidを、バイナリログへのコミット順にリカバリします。この結果、最終的にはバイナリログの書き込み順序とInnoDBログファイルへの確定(コミット)順序が一致します。
 というわけで、データ整合性の問題を引き起こさずに、スケーラビリティを上げることができました。バイナリログを同期書き込みする(sync-binlog=1)場合は依然として遅くなってしまいますが、デフォルトの0であれば上図のように非常に高速になります。興味のある方は試してみてください。

2 件のコメント:

まつこ さんのコメント...

いつも参考にさせて頂いております。

> グループコミット問題修復の効果のほどは、一目瞭然なので図を見た方が分かりやすいでしょう。下図は、mysqlslapで、複数のコネクションから並列でINSERTを行なったときの結果です。
> mysqlslap --concurrency=30 --iterations=1 --engine=innodb \
> --auto-generate-sql --auto-generate-sql-load-type=write \
> --number-of-queries=100000

ですが、ドキュメント http://dev.mysql.com/doc/refman/5.1/ja/mysqlslap.html
を確認すると、
--concurrency=N, -c N
SELECTステートメントを発行している際、シミュレートするクライアントの数。
となっております。

--auto-generate-sql-load-type=write の場合は
--concurrency=30 の指定は無効となりそうですが、
いかがでしょうか?

まつこ さんのコメント...

mysqldのlogを有効にして発行されているSQLを確認してみたところ、
concurrencyを設定することで複数コネクションから
並列でINSERTされていることを確認できました。

お騒がせして申し訳ありません。

コメントを投稿