この記事はMySQL Advent Calendar 2019の9日目の記事です。
binary logとはなにか
binary log(以降binlog)はMySQL serverで実行した更新をイベントという形式で出力したログ。トランザクションのコミット時に出力され、コミットの順番で1トランザクションの変更がシーケンシャルに出力されている。
MySQLではこれを利用することでreplicationやrecoveryを行っている(server crash後のcrash recoveryにはinnodb_log(いわゆるredo log)も使われている)。
レプリケーションに利用される際は、Masterで更新された内容をbinlogとして出力し、この内容をslaveに転送してSlaveのIO threadがrelay_logとして保存する。これをSQL threadが読み出して適用することでMasterとSlaveのデータが同期されるという仕組みになっている。
(参考1より)
MySQLのreplicationは非同期レプリケーションもしくは準同期レプリケーションと呼ばれるもので、Master, Slave間のデータ同期のためによく使われるのは準同期レプリケーションでしょう。 今回はbinary log自体の中身に注目するので、説明は省略します。 (もちろんGroup Replicationを使っている場合はこの限りではないです。)
binary logに関連するファイル
- binlog.index
- 保持しているbinlogを管理するメタデータファイル
- 中身は単純に保持しているbinlogのファイル名が書かれている
- binlog.00000n
binlogのフォーマットに影響を与えるパラメータ
- binlog_format
- statement: 実行したクエリをそのまま文字列としてlogに書く
- row: 変更分を行ごとにイベントとして書く
- mixed: statementとrowのいいところどり
すべてstatementで書いても良いのではないか?と思うかもしれないが、実行時に結果が決まるようなクエリが実行される場合masterとslaveで結果が異なる可能性がある。実はnow()やrand()といった関数はreplicationのために対応されているが、推奨されないformatとなっている。
rowベースでは更新された内容を記述するため、MasterとSlaveのデータが同じならば、replicationによる変更も同じ結果になるが、例えば1つのupdate文で10万行を変更した場合10万行すべての変更を1イベントとしてreplicationするため、実行が遅くなったりreplication遅延の原因になる。
そこで同じ結果になることが保証される場合はstatement形式で送るなどうまくstatementとrow形式を使い分けてくれるのがmixedとなっている。
最新のMySQL 8.0ではrow baseがdefaultになっていて、今後はrowが推奨されるのかもしれない。
- binlog_row_image
- full: すべてのカラムの情報を記録
- minimal: 行の特定に必要なカラム値(PKE: Primary Key Equivalent)と、変更されたカラムの値を記録
- noblob: 更新に関係ないBLOB, TEXTカラムを除いてログに記録
binlog_row_imageは前述のbinlog_format = rowで出力される場合に有効な設定となっている。 PKEとはPriamry Keyと同等なデータのことで、例えばupdate文の結果は変このあった行を1意に特定できるPK相当のデータをbefore imageとして指定し、変更結果をafter imageとして記録することでレプリケーションができるようになっている。
mysqlbinlogでbinlogの中身をみる
binlogの中身はMySQL標準のmysqlbinlog
コマンドを利用することで確認できる。
mysqlbinlogで必要な特定時間の変更分を確認したり、それらをSQLファイルとして抽出してPITRに利用したりする。
例えば、mysqlbinlog --no-defaults --base64-output=DECODE-ROWS -v binlog.000009
と適当なbinlogを指定して中を確認すると以下のような出力を得ることができた。
56行目あたりを見ると、DECODE-ROWS
を指定しているため、コメントアウトされた状態でSQLとして復元されたINSERTクエリを確認することができる。
MySQLを利用している人なら一度は使ったことがあると思うので、省略するが、詳しく知りたい人はドキュメントを参照してください。
binlogの内部構造を見ていく
binlogは先頭の4byteを0xfe 0x62 0x69 0x6e
(0xfe bin
)という文字列から始めて、それ以降はイベント(events)という形式で更新データやメタデータを保持している。
すべてのeventはheaderとdata部からなり、header部は1つのファイル内で固定(基本的に同じMySQL versionなら構造は一定)でheaderに含まれるtypeコードによって指定されるtypeごとにdata部の中身が変化する。
binlogにもversionがあり、有効なversionは1,3,4でMySQL 5.0以降はversion4となっている。 version4として変更がないのはおそらくformatの構造で、新しいeventや使われなくなったeventもあるという状態。
- version 1: 3.23 (で利用されていた)
- version 3: 4.0.2~4.1
- version 4: 5.0以上
現時点で30個以上のeventがあるがここではいくつかのイベントを紹介する。
binlogのフォーマットを規定するevent
binlog自体のフォーマットはbinlogの先頭4byteの次に最初のeventとして出力されるformat_description_event
によって規定される。
つまり、このeventを読むことでそのbinlogファイルのeventをパースすることができるようになる。
おそらくこのformat_description_eventに変更が加わるときにbinlogのversionが変更されるのだと思う。
format_description_event
には以下の5つのデータが記述されている。
- binlog_version
- server_version
- create_timestamp
- header_length
- post_header_length
実際中身をparseするにはformatされた各値を読み取る必要があるのだが、ここでは割愛する。 実際に読んでみたい場合はMySQL Internals Manualとここにかけている情報をsource codeから理解しながら読むしかないです。
(一番右の列はbyte数)
DML実行によるイベント
query_log_event
statement based logging(以降SBL, binlog_formatがstatementとして出力されるログイベント)の場合に実行されたクエリがそのまま出力されるevent. binlog_formatに関わらずDDLはSBLで出力されるので、DDLはこのeventで出力される
実行自体には不要と思えるメタデータもあるが以下のような形式で最後の値として生のsql_statementが入っている。
intvar_event
query_log_event
ではauto_incrementの値やlast_insert_id()を利用している場合にこれらの情報が含まれない、そこで、query_log_event
の前にintvar_event
によってこれらの値を指定する。
option 1
の値- 1の場合、valueがauto_incrementの値
- 2の場合、last_insert_id()の値
これ以外にもに更新がありそうだが、Internals Manualから確認できたのはこの2つの値でした。
write_rows_event
(update_rows_event
, delete_rows_event
)
row based logging(以降RBL, binlog_formatがrowとして出力されるログイベント)の場合に更新分が行単位で出力されるevent。insert, update, deleteがそれぞれ、{write,update,delete}_rows_event
として出力される。
下記の図はupdate_rows_event
で、write,deleteの場合はbefore imageがないので、この部分がなくなる。
table_map_event
RBLの場合に、table情報を適用時にマッピングするためのevent。
上記の{write,update,delete}_rows_event
の前に出力され、このイベントを利用することでmasterとslaveのテーブル定義が異なる場合でもreplicationを行えるようにする役割がある。
schemaのことなるmaster, slave間でのレプリケーションについては(参考2)を参照すると理解が深まると思います。
終わりに
お気づきの方もいるかも知れませんが、このブログ記事はMySQL Casual Talks vol.12で話した、binlogの内部構造調査の内容を改めてまとめたものです。
binlogをうまく活用することでレプリケーションに関連するパフォーマンス問題を解決したり、自前のbinlog serverを実装しようかと思っていましたが、予想以上にbinlogのすべてを理解するのは難しそうだとわかりました。
当時は比較的実装範囲が少なく実験として面白そうなFlash back(binlowを遡ることでロールバックする)を試そうとbinlogのparaserであるmybinlogを作ったりしていました。ですが、Internals Manualだけからbinlogの構造を把握することは難しく、source codeから必要な情報を理解することも大変です。いくつかbinlogをparseしているツールを見つけたり紹介してもらったりしたのでまた戻ってくると思います。
最近はibdファイルやstorage engineの仕組みのほうが気になってしまっていて、放置気味担ってしまっていますが、この内容を読んで興味を持ってくれた方がいたらぜひ声をかけてください。
参考
- 奥野幹也, "エキスパートのためのMySQL 運用+管理トラブルシューティングガイド", p321, 図5-1
- "チョットワカル Row-Based Replication" シリーズ (GREE Engineer Blog) その1 ~ その5