innodb_support_xa と binlog の危ない関係
昨日の記事 で innodb_support_xa = 0にすると RDS が速くなることを紹介したのですが、その後 Twitter で innodb_support_xa = 0 にするとクラッシュ時だけでなく通常時も binlog とトランザクションログの一貫性が無くなる(コミットする順序が前後する)可能性があることを指摘していただきました。
innodb_support_xa=0すると、トランザクションがコミットされた順番でバイナリログに載ることが保証できなくなるんだけどいいのかな? DSAS開発者の部屋:AWS RDS の書き込み性能チューニング dsas.blog.klab.org/archives/52108…
— ts. yokuさん (@yoku0825) 2013年4月24日
実際に、 MySQL 5.5 と 5.6 両方で、 innodb_support_xa の説明にそう書かれています。
もともと、 innodb_support_xa = 0 を設定した時は、// innodb_support_xa = 1 のとき 1. prepare (sync) 2. lock 3. write binlog (sync if sync_binlog) 4. commit (sync) 5. unlock // innodb_support_xa = 0 のとき 1. lock 2. write binlog (sync if sync_binlog) 3. commit (sync) 4. unlockとなることを期待していました。こうであれば、 fsync が prepare の分だけ減り、コミットに失敗しない限りは binlog とコミットログの一貫性は保たれるはずです。
しかし、 MySQL 5.5.31 のコードを見ると、実際には次のようになっていました。
// innodb_support_xa = 1 のとき 1. prepare (sync, lock) 2. write binlog (sync if sync_binlog) 3. commit (unlock, sync) // innodb_support_xa = 0 のとき 1. prepare (do nothing) 2. write binlog (sync if sync_binlog) 3. commit (sync)XA を OFF にすると、 prepare 時に取られていたロックが取られなくなり、複数スレッドで実行された時に binlog の書き込みとコミットの順序が守られなくなってしまいます。
しかし、 MySQL 5.5 と 5.6 では大幅に書きなおされているらしいです。
prepare_commit_mutexがMySQL 5.6に見当たらないんです
— SH2さん (@sh2nd) 2013年4月24日
@methane 了解です。ha_innodb.ccのinnobase_xa_prepareでmutex取ってた部分がMySQL 5.6では空になっていて、だいぶリファクタリングされた感じです
— SH2さん (@sh2nd) 2013年4月24日
MySQL 5.6 のソースを呼んでみたところ、 sql/binlog.cc の MYSQL_BIN_LOG::ordered_commit() という関数で、 binlog とトランザクションの順序を守る処理が入っていました。 (というか、 5.5 で呼び出し側がそれを保証しないせいで innodb 側でロックをとると言う設計が無理矢理過ぎですよね)
この関数は binlog の書き込み、 sync, トランザクションのコミットまで順番に行う関数で、複数スレッドで実行されても大丈夫なようにしつつ、 binlog をグループコミットするような実装になっています。
1. トランザクションを flush キューに入れる 2. lock(log) 3. (stage1) flush キュー内の全てのトランザクションを binlog に書き、 sync キューに入れる 4. lock(sync) & unlock(log) 5. (stage2) sync キュー内の全てのトランザクションを取り出し、 必要であれば最後のトランザクションまでのbinlogを sync してから、 commit キューに入れる 6. lock(commit) & unlock(sync) 7. (stage 3) commit キューの中身をすべて取り出し、順番にコミットしていく 8. unlock(commit)
この動作の解説とベンチマークが Binary Log Group Commit in MySQL 5.6 にありました。 XA を OFF にしてもトランザクションが失敗しない限りは binlog とトランザクションの一貫性が崩れないとはいえ、 innodb_support_xa=1 の時の性能が大幅に上がっているので、危険を冒してまで XA を OFF にする必要性は無さそうです。
この改善により、高負荷時は複数のトランザクションの binlog がまとめて書かれ sync されるのですが、残念なのは innodb 側のコミットは順番に1つずつ実行してしまうことです。 せっかく複数のトランザクションをコミット順に並べたのですから、トランザクションログへの書き込みも1回の fsync にまとめられたら性能が上がりそうです。
そういう改善がされていないのか検索してみたところ、 MySQL ではなくて MariaDB の方に見つけました。
[MDEV-232] Remove one fsync() inside engine's commit() method
この MDEV-232 により、各 commit ではトランザクションログを書くだけで fsync せず、最後にまとめて fsync するようになります。 最近いくつかのLinuxディストリビューションで採用されたことで話題の MariaDB ですが、 AWS の Multi-AZ のように fsync が遅い環境では MySQL よりも高い性能を出せそうです。また機会があれば、 MySQL 5.6 と Maria DB のベンチマークを取ってみたいと思います。