MySQL 5.6 でのレプリケーション遅延は危険
MySQL 5.6 の検証中に MySQL 5.5 とは違うタイプのレプリケーション遅延を見つけたので紹介します。
MySQL のレプリケーションのおさらい
MySQL のレプリケーションは次のような仕組みで動作しています。
- マスターの更新トランザクションが binlog を書く
- スレーブの I/O スレッドがマスターに接続し、 binlog を取得し、 relaylog を書く.
- マスター側はスレーブからの接続を受け付けると(dump スレッド)、指定された場所から最新までの binlog を転送する
- binlog が追記されるのを待ってさらにスレーブに送る
- スレーブのSQLスレッドが relaylog を再生する
MySQL 5.5 でよくあったレプリケーション遅延
マスターは並列してトランザクションを処理して、最終的にコミットした順で反映されれば問題ないようになっています。
一方、スレーブはマスターと確実に同じ順序で再生する必要があるため、トランザクションを並列に実行できません。
そのため、スレーブの更新性能はマスターより低く、更新処理が増えるとスレーブの SQL スレッドが間に合わなくなってきます。
SQL スレッドが間に合わなくなると、スレーブに relaylog はあるのに SQL スレッドに実行されてない状態のレプリケーション遅延が発生します。
このレプリケーション遅延は、 SHOW SLAVE STATUS
で Seconds_Behind_Master
を見ることで監視できます。
このレプリケーション遅延では、参照クエリをスレーブに向ける場合はかなり古いデータを見ても大丈夫なように気をつけないといけませんが、 relaylog は大抵マスターの最新から1秒以内の遅延で済んでいるので、突然マスターが死んで RAID の復旧などが不可能だった場合でも失うのはわずかな時間分のコミットで済みます。
MySQL 5.6 で問題になるレプリケーション遅延
MySQL 5.6 でグループコミットが導入されました。
これは、 binlog が有効かつ sync_binlog=1 の場合に、並列する複数のトランザクションのコミットを、トランザクションログではなく binlog をまとめて flush することで永続化します。(binlog が flush されるまでコミットが終わりません)
この binlog が flush される前にスレーブに転送されると、マスターでまだコミットが完了していないトランザクションがスレーブに反映されることになります。
スレーブを参照しているクエリは、まだマスターでSELECTしても見えない未来のデータを参照するかもしれませんし、マスターをクラッシュリカバリした時に flush が終わってない (コミットが終わってない) データが消えると、そのデータを受け取っていたスレーブではレプリケーションが再開できなくなってしまいます。
そのため、 MySQL 5.6.17 で、 binlog の flush が終わるまで LOCK_log というロックを取得しつづけ、 dump スレッドはそのロックを待ってからスレーブに転送する用になりました。
http://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-17.html
Such problems are expected on less durable settings (sync_binlog not equal to 1), but it should not happen when sync_binlog is 1. To fix this issue, a lock (LOCK_log) is now held during synchronization, and is released only after the binary events are actually written to disk. (Bug #17632285, Bug #70669)
この修正の副作用として、更新負荷が増えると dump スレッドが LOCK_log を取得するのに時間がかかり、 binlog がなかなか転送されなくなってしまいます。
この問題は MySQL 5.7.2 で、 dump スレッドが LOCK_log を取得しなくてもどこまで転送していいか分かるようになって改善されたそうです。
(参考: Dump Thread Enhancement On MySQL-5.7.2)
このレプリケーション遅延では、 binlog がスレーブに転送されるのが遅れるので、 マスターが死んで復旧不可能だった場合に、遅延していた時間だけの情報が失われてしまいます。
この問題は SHOW SLAVE STATUS
では監視できないので、 SHOW MASTER STATUS
の File
, Position
と SHOW SLAVE STATUS
の Master_Log_File
, Read_Master_Log_Pos
を比較する必要があるでしょう。
対策
この問題を軽減するには、 LOCK_log を待つ dump スレッドを減らすためにマスターに直接接続するスレーブを1つだけにし、他のスレーブは多段レプリケーションを活用する事ができます。
解決は難しいですが、次のような対応が考えられます。
- MySQL 5.6 をスキップして、 5.7 が GA になるまで 5.5 を使い続ける
- スレーブのフライングを許容し、 5.6.17 の該当部分の変更を revert してしまう。 (フライングの弊害として、マスターがクラッシュリカバリしてからレプリケーションを再開できない可能性があります)
- MySQL 5.6 へ移行する代わりに、グループコミットやレプリケーションが別方式になった MariaDB に移行する。
- RAID を信用して、マスターが死んでも簡単にはスレーブを昇格せずにディスク換装などでの復旧を試みる
- LOCK_log の影響を受けない、ブロックデバイスレベルのレプリケーションを利用する. (RDS の Multi-AZ replication と同じ手法)
- Semi-sync replication を利用し、 binlog が確実に転送されるのを待つ.