チョットワカル Row-Based Replication・その2

こんにちわ。せじまです。今回も replication の話をします。

はじめに

第二回です。

今回の主なお題は、 THD::decide_logging_format() という関数になります。 THD::decide_logging_format() の仕様がわかると、binlog_format が原因で replication 止まる理由が、(そこそこ)わかるようになるでしょう。

また、Row-Based Replication に移行したとき、 THD::decide_logging_format() 以外で replication が停止してしまうケースなどについても、軽くメモ程度に書いておきます。

THD::decide_logging_format() について

(MySQL Internals は微妙に内容が古かったりするんですが)、参考までに、まずは 19.4.1 Determining the Logging Format を読んでみましょう。

At parse time, it is detected if the statement is unsafe to log in statement format (that is, requires row format). If this is the case, the THD::Lex::set_stmt_unsafe() function is called. This must be done prior to the call to THD::decide_logging_format() (that is, prior to lock_tables). As a special case, some types of unsafeness are detected inside THD::decide_logging_format(), before the logging format is decided. Note that statements shall be marked unsafe even if binlog_format!=mixed.

次は、具体的に、 SQLCOM_UPDATE 受けたところから、 THD::decide_logging_format() を見ていきましょう。今回も MySQL 8.0.12 のソースコードをベースに読んでいきます。

こんなかんじで statement を実行する前にいったん lock_tables() を実行しておりまして

lock_tables() の中で THD::decide_logging_format() が呼ばれています。

https://github.com/mysql/mysql-server/blob/mysql-8.0.12/sql/sql_base.cc#L6436

THD::decide_logging_format() という関数についてですが、コメントがけっこう充実しています。

ざっくりまとめはコメントのとおりで

Row Injection というのは、 binlog_format=ROW にしたとき binlog に出力される、UPDATE_ROWS_EVENT などのことでしょうね。それらを SQL_Thread などで実行する場合が Statement Type = I になります。

では、順を追って見ていきましょう。

Row capable でも Stmt capable でもない storage engine の例の一つは、 performance schema です。

https://github.com/mysql/mysql-server/blob/mysql-8.0.12/storage/perfschema/ha_perfschema.h#L73-L97

「間違っても replication できると思うなよ?」ということでしょう。

Stmt capable であり Row capable でない storage engine ですが、ソースコード上ではテストのために 存在しているようです。 なので、これはいったん忘れても良いでしょう。

https://github.com/mysql/mysql-server/blob/mysql-8.0.12/storage/example/ha_example.h#L100

Row capable であり Stmt capable でない storage engine ですが、身近なところでいうと、 READ COMMITTED or READ UNCOMMITTED な InnoDB が該当します。InnoDB が Stmt capable であるのは、 Isolation level が REPEATABLE READ か SERIALIZABLE のときに限られるわけです。

といったことを踏まえると、想定すべき現実的な Decision table for logging format は、次のようなものでしょう。

  • InnoDB で READ COMMITTED or READ UNCOMMITTED なとき
Row capable Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Statement capable N N N N N N N N N Y Y Y Y Y Y Y Y Y
Statement type S S S U U U I I I S S S U U U I I I
binlog_format S M R S M R S M R S M R S M R S M R
Logged format - R R - R R - R R S R R S R R - R R
Warning/Error 5 - - 5 - - 6 - - - - - 7 - - 6 - -
  • InnoDB で READ COMMITTED や READ UNCOMMITTED を使用しないとき、あるいは MyISAM や ARCHIVE ENGINE など使うとき
Row capable Y Y Y Y Y Y Y Y Y
Statement capable Y Y Y Y Y Y Y Y Y
Statement type S S S U U U I I I
binlog_format S M R S M R S M R
Logged format S R R S R R - R R
Warning/Error - - - 7 - - 6 - -

まぁ、THD::decide_logging_format() の中では THD::is_dml_gtid_compatible() も呼んでいて、 GTID との互換性を確認していたりもするので、将来 GTID 対応にしたいなら、InnoDB 以外は考えないほうがいいでしょうね。

ちなみに decide_logging_format() の中で HA_HAS_OWN_BINLOGGING というフラグが出てきて own_binlogging ってなんだよって話になりますが、 ndbcluster、いわゆる MySQL NDB Cluster のことですね。

Decision table に出てこない次の error についてですが

8 は own_binlogging な ndbcluster の話ですね

18.6.3 MySQL Cluster レプリケーションの既知の問題

バイナリロギングを実行していないスレーブのストレージエンジンへの NDB の複製
独自のバイナリロギングを処理しないストレージエンジンを使用するスレーブに MySQL Cluster から複製を試みると、レプリケーションプロセスは次のエラーにより停止します: Binary logging not possible ... Statement cannot be written atomically since more than one engine involved and at least one engine is self-logging (エラー 1595)。次の方法のいずれかで、この問題を回避できます。

9 はここでしょうね

https://github.com/mysql/mysql-server/blob/mysql-8.0.12/sql/binlog.cc#L9911-L9920

https://github.com/mysql/mysql-server/blob/mysql-8.0.12/sql/rpl_gtid_persist.h#L192-L231

補足

Storage Engine の レイヤーで Row capable かどうか判断する必要があるという話は、この bug report が発端のようです。

WorkLog 3303 によって、 Row capable や Statement capable という概念がもたらされたようですが

WorkLog 3303 の SETTINGS FOR CURRENT ENGINES と、現状の仕様は異なっているようです。このあたり、実際にコード書いてたら仕様変えたほうが良いとわかったってことかもしれませんね。

THD::decide_logging_format() 以外で Replication が停止してしまうケースなどについて

いちおう軽く触れておきます。すごい雑にまとめると

  • MySQL 5.6 で SBR から RBR に移行する場合は、割と注意したほうがいい。特に、 SET GLOBAL で binlog_format 変更するのは、よほどその環境を理解していない限り、止めておいた方が無難なのでは。 master と slave で型が違ったり、 TIMESTAMP型がmicrosecondsサポートしてるかしていないかでも、 replication に影響する。
  • MySQL 5.7 で SBR から RBR に移行する場合、だいぶいろいろと改善されている。ただ、 binlog_rows_query_log_events と、MySQL 5.7 で追加された Multi-Source Replication を組み合わせると、メモリリークするなどのバグが有った。マイナーバージョンが古めの MySQL5.7を使っている場合、 SBR から RBR への移行は、注意が必要。
  • MySQL 8.0 ではこのあたりのバグなどだいぶ直っているらしい。 MySQL8.0 において、RBRへの移行のハードルはだいぶ下がっている感がある。(ただ、 MySQL 8.0 を使ってる時点で、 SBR はもうやめている気もするし、8.0 に移行すること自体の方が、RBRへの移行よりハードル高い気がする)

時刻型や slave_type_conversions

MySQL5.6 から DATETIME や TIMESTAMP が microseconds に対応しました。

同じTIMESTAMP型でも、5.5以前と5.6以降では内部的に異なるデータ型となった場合、replication止まるケースがありました。

5.7.5 or 5.6.20 以降でなおったそうです。

Replication: Replication of tables that contained temporal type fields (such as TIMESTAMP, DATETIME, and TIME) from different MySQL versions failed due to incompatible TIMESTAMP types. The fractional TIMESTAMP format added in MySQL 5.6.4 was not being correctly converted. You can now replicate a TIMESTAMP in either format correctly according to the slave_type_conversions variable. (Bug #70124, Bug #17532932)

Replication: Replication of tables that contained temporal type fields (such as TIMESTAMP, DATETIME, and TIME) from different MySQL versions failed due to incompatible TIMESTAMP types. The fractional TIMESTAMP format added in MySQL 5.6.4 was not being correctly converted. You can now replicate a TIMESTAMP in either format correctly according to the slave_type_conversions variable. (Bug #70124, Bug #17532932)

具体的にはこの commitですね。

さらに、さいきんのMySQLは型変換するためのパラメータが追加されて、 5.7.2 以降で設定できる値が増えています。

このへんも参考までに

rbr_exec_mode

mysqlbinlog用に、 5.7 から追加されました。詳しくはリファレンスマニュアルを参照してください。

Bug #85371 Memory leak in multi-source replication when binlog_rows_query_log_events=1

Multi-Source Replication とbinlog_rows_query_log_events=1 を組み合わせた場合、メモリリークすることがあったそうで、MySQL 5.7.21 と 8.0.4 以降でなおったそうです。

今日はこれまで

decide_logging_format() の Warning/Error は、昔の MySQL より最近の MySQL の方が、 error code が増えていたりします。さいきんの THD::decide_logging_format() を前提に考えた方が、より厳格で安全かもしれません。興味のある方は、むかしの MySQL とさいきんの MySQL で、decide_logging_format() を読み比べてもらうと良いでしょう。

MySQL の replication は、 storage engine と協調動作しながら、その整合性を保つよう、改善され続けてきました。このあたりの経緯などに興味のある方は、今回取り上げた関数やWorkLogなどをいろいろ読んでもらうと、理解が深まるのではないでしょうか。

Row-Based Replication は、 Statement-Based Replicaiton で発生していた不具合を改善するものとして導入された部分もあるのですが、 SBR で動いていたものを RBR に移行するとき、なんらかの副作用が発生する可能性があります。 SET GLOBAL で binlog_format 変更することがリファレンスマニュアルで推奨されていないのは、そのあたりの副作用だったり振る舞いを把握するのが、なかなか難しいという問題があるのでしょう。

次回以降も RBR の話は続きます。