グループコミットとは
MySQL 5.6 からは、バイナリログに対するグループコミットができるようになった。
これは、トランザクションを COMMIT すると、その変更が損失するのを防ぐためにログファイルがディスクへ sync される処理に時間がかかるため、複数のトランザクションの COMMIT がある程度まとまったところで、ディスクの同期を行う、というものとなる。
もともと、InnoDB のほうは、MySQL 5.1 + InnoDB Plugin においてグループコミットができるようになっていた。しかし、バイナリログへの同期がグループコミットにはなっておらず、sync_binlog=1 の設定をした場合、トランザクション毎にバイナリログへの同期が行われ、パフォーマンスがなかなか出なかった。そこで MySQL 5.6 ではバイナリログの書き込みについてもグループコミットができるようになった。
グループコミットを使用しない場合とする場合の概念図を以下に示す。
[ グループコミットを使用しない場合 ]
[ グループコミットを使用する場合 ]
MySQL では COMMIT の実行時にバイナリログとストレージエンジンの同期を取る必要があるため、2 相コミットが用いられている。流れとしては、InnoDB の PREPARE -> バイナリログの write -> バイナリログの fsync -> InnoDB の COMMIT となる。MySQL 5.6 より古いバージョンでは、sync_binlog=1 のとき、バイナリログの write と fsync は切り離すことのできない処理であるため、複数のトランザクションがほぼ同時に COMMIT すると、バイナリログの write と fsync がそれぞれお行われていた。
しかし、fsync はディスクからの応答を待つ必要があり、非常に遅い処理となる。この遅い処理をトランザクション毎に毎回行う必要があるため、sync_binlog=1 のときの更新処理は極めて悪かった。
MySQL 5.6 でバイナリログのグループコミットが行えるようになったため、このバイナリログへの fsync の回数を削減でき、sync_binlog=1 のときの更新が効率化した。
なお、InnoDB 内部でも COMMIT はグループコミットとして処理されるが、その動作はバイナリログと連動していないので図においては表現を省略している。
実際に InnoDB のログの fsync 処理は極めて効率的に少ない回数で行われることになる。
なお、グループコミットを安全に使用したい場合、binlog_order_commits オプションをデフォルト (ON) から変更するべきではない。これは、バイナリログと InnoDB で COMMIT の順序を同じにするオプションとなるが、安全性を保った方がトータルコストを安く抑えられる。
MySQL 5.7 におけるグループコミット
MySQL 5.6 におけるグループコミットの改善が行われたことで、sync_binlog=1 を設定してもそれほどパフォーマンスの低下に悩まされなくなり、バイナリログの安全性を求めるコストも低下した。
MySQL 5.7 ではそれがさらに進化し、バイナリログへのグループコミットの性能をチューニングするための 2 つのオプションが追加されている。これらは MTS (マルチスレッドスレーブ) の性能を消える役割をもつようになったため、HDD を利用していなくても依然として重要となる。
- binlog_group_commit_sync_delay: グループコミットの際にさらに追加でトランザクションが COMMIT される機会を待つ、この待ち時間を調整するオプション
- binlog_group_commit_sync_no_delay_count: グループコミットが大きくなりすぎるのを防ぐオプションであり、待機時間が binlog_group_commit_sync_delay に達していなくても、すでに同一のグループで COMMIT しようとしているトランザクションの数が binlog_group_commit_sync_no_delay_count に達して入れば COMMIT を実行する
マスタースレッドにおける Mutex 競合
上記で記載した MySQL 5.6 におけるグループコミットには大きな問題がある。バイナリログは write された時点でスレーブに転送できるようになる。
まだディスクへの fsync が終わっていないバイナリログがあるときに、マスターがクラッシュすると、再起動後にはバイナリログが無いため、ロールバックされることが期待されるが、もしそれらがすでにスレーブに転送されてしまっていると、マスターには存在しないデータをスレーブが持つことになり、不整合が発生する。
この問題は MySQL 5.6.17 において修正されたが、これによりバイナリログのグループコミット全体が LOCK_log という Mutex により守られるようになった。
それまでも LOCK_log によってバイナリログの write は排他処理が行われていたが、write に加えて fsync まで LOCK_log の保護下で行われるようになった。fsync は時間のかかる処理であるため、LOCK_log を必要とする他の処理をブロックしてしまう可能性が高い (LOCK_log はマスタースレッドがバイナリログを読み込む時に使用される)。
結果として、グループコミットとマスタースレッドの間で Mutex の競合が多発することになり、マスタースレッドがスレーブへバイナリログを転送する効率が大幅に低下した。
MySQL 5.7 ではマスタースレッドが LOCK_log の代わりに LOCK_binlog_end_pos という Mutex が導入され、ロックの粒度が細かくなった。この Mutex はマスタースレッドが読み取ることができる最後のバイナリログの位置を管理するものである。MySQL 5.6 ではマスタースレッドは EOF になるまでバイナリログを読み込むようになっていたが、それではまだ fsync が完了していないデータも読んでしまう。そこで MySQL 5.7 ではマスタースレッドが fsync が完了したデータだけ読み込むように、読み込み可能なポジションを管理するようになった。
読み込み可能なポジションは、バイナリログへの fsync が完了した段階で進められ、そうしてはじめてマスタースレッドはその地点までバイナリログを読み進められる。
これにより、スレーブへ送信されるバイナリログは、マスターがすでにディスクへフラッシュしたものだけになり、マスターがクラッシュしてもスレーブへ転送したデータはマスター上に残ることになる。
以上により MySQL 5.7 ではグループコミットの導入により引き起こされた、マスターのクラッシュ時にマスターとスレーブのデータが不整合になる問題を解消し、且つスレーブへのログの転送速度が飛躍的に向上した。