Skip to content

MySQL 9 Binlog: What's Actually Different

MygramDBのMySQL 9.x対応をやった。MygramDBはGTIDベースのbinlogレプリケーションでMySQLからデータを取り込むインメモリ全文検索エンジンで、binlogのバイナリフォーマットを自前でパースしている。VECTORカラムを持つテーブルでINSERTやUPDATEが走れば、未知のカラム型でパーサーが壊れる。対応が必要だった。

MySQL 8.4.8と9.6.0のソースコードを並べて、binlog関連のコードを全部比較した。イベントヘッダ、イベントタイプ、GTIDフォーマット、ROWSイベント、TABLE_MAPのメタデータ——片っ端から差分を取った。

binlogは20年以上かけて後方互換を積み重ねてきたフォーマットだ。イベントヘッダにイベント長を持たせて未知のイベントをスキップ可能にし、TLVで拡張可能なメタデータを備えている。この設計のおかげで、フォーマットの骨格は9.xでも頑丈なままだった。

ただし、新しいデータ型はどうしようもない。ROWSイベントの行データをパースするとき、カラム型ごとにバイト列の読み方が決まっている。未知のカラム型が来たら、何バイト読めばいいかわからない。スキップすらできない。

変わらなかったもの

まず安心材料から。

領域変わったか
イベントヘッダ(19バイト固定)同一
チェックサム(CRC32、4バイト)同一
binlogバージョン4のまま
イベントタイプ番号(0〜42)同一
GTID / Tagged GTIDワイヤフォーマット同一
Transaction Payload圧縮(type 40)同一
DECIMALバイナリエンコーディング同一
Packed Integer同一

Tagged GTIDイベント(type 42)は8.4で既に導入済みで、9.xでの変更はない。binlogストリームの骨格は完全に同一だ。既存のbinlogパーサーの大部分は、何も変えずに9.xのストリームを読める。

変わったもの

変更は3つ。うち、binlogコンシューマが対応を迫られるのは1つだけだ。

1. MYSQL_TYPE_VECTOR = 242——唯一の破壊的変更

MySQL 9.0でVECTOR型が追加された。include/field_types.hMYSQL_TYPE_VECTOR = 242が新設されている。8.4.8の同ファイルでは242は欠番だった。

背景にはRAGの爆発的な普及がある。LLMが実用化されて以降、テキストや画像を埋め込みベクトルに変換してDBに格納し、類似検索する需要が急増した。PostgreSQLはpgvector(2021年〜)で早くからこの需要を取り込み、ベクトル検索のデファクトになりつつあった。MySQL 9.0(2024年7月、Innovation Release)はネイティブのVECTOR型とHNSWインデックスを追加して追いかけた形だ。LTSである8.4系列とは別のリリースラインだが、9.xのbinlogストリームを読むツールは対応を避けられない。

このDB間競争の副産物として、binlogに新しいカラム型が入った。ただしVECTOR型のbinlog上のエンコーディングはBLOBと同じだ。

MySQLのソースコード(sql/field.h)を見ると、Field_vectorField_blobを継承している。packlengthは4固定(4バイト長さプレフィクス)。データ本体はIEEE 754 float32値のバイナリ列で、次元数 × 4バイトの固定長になる。

TABLE_MAPイベントでのメタデータも、BLOB系と同じ1バイト(長さプレフィクスのバイト数を示す)。ROWSイベントでのデータエンコーディングも、長さプレフィクス + 可変長バイナリ。

実装上は、フィールドサイズ計算でtype 242をBLOB(252)と同じ分岐に入れ、TABLE_MAPのメタデータパースとROWSのデコードでBLOB系の処理にVECTORを追加するだけでいい。MygramDBでの対応は、3ファイルにcase文を数行足す作業だった。

mysqlbinlogコマンドでのVECTOR表示

MySQLのmysqlbinlogコマンドはVECTOR型をfloat4get()でデコードして[1.00000e+00,2.00000e+00,3.00000e+00]のように表示する(log_event.cc 2040〜2051行)。binlogコンシューマが同じことをする必要はない。バイナリデータとしてそのまま渡せば十分なケースが多い。MygramDBはhex文字列に変換している。

2. VECTOR_DIMENSIONALITY——TABLE_MAPの新メタデータフィールド

TABLE_MAPイベントのオプショナルメタデータに、TLVフィールドVECTOR_DIMENSIONALITYが追加された(rows_event.h)。VECTORカラムの次元数を格納する。

これは「無視しても壊れない」変更だ。TABLE_MAPのオプショナルメタデータはTLV(Type-Length-Value)形式で、未知のTypeはLength分スキップすればいい。8.4以前からこの仕組みは同じで、未知フィールドのスキップを正しく実装していれば何もする必要がない。

次元数が必要なユースケース(VECTORカラムのバリデーション等)がある場合だけ、パースを追加すればいい。

3. USE_SQL_FOREIGN_KEY_F——ROWSイベントの新フラグ

ROWSイベントのフラグにビット4(1U << 4)が追加された(rows_event.h)。SQL標準の外部キー制約処理を有効にするかどうかを示すフラグだ。

ビット名前意味
0STMT_END_Fトランザクション末尾
1NO_FOREIGN_KEY_CHECKS_F外部キーチェック無効
2RELAXED_UNIQUE_CHECKS_Fユニーク制約チェック緩和
3COMPLETE_ROWS_F全カラムを含む
4USE_SQL_FOREIGN_KEY_FSQL標準の外部キー処理(9.xで追加)
5〜15予約済み

これも「無視しても壊れない」変更だ。未知のフラグビットを無視する実装なら、対応不要。ROWSイベントのフラグを個別に解釈してレプリケーション制御に使っているコンシューマだけが気にすればいい。

binlogの外で変わったこと

binlogフォーマット自体ではないが、binlogレプリケーションを実装しているツールが影響を受ける変更が2つある。

binlog_format=ROWの非推奨化

MySQL 9.xでは--binlog-formatオプションが非推奨になった。Row-basedが唯一のフォーマットだ。8.xの時点で実運用でROW以外を使う理由はほぼなかったから、実質的な影響は小さい。ただ、Docker Composeやmy.cnfで--binlog-format=ROWを明示していると、起動時に警告が出る。消しておいたほうがいい。

mysql_native_passwordの削除

9.xではサーバー側のmysql_native_passwordプラグインが削除された。caching_sha2_passwordがデフォルトかつ唯一の選択肢になる。

SSL接続ならそのまま動く。SSL無しで接続している場合は、クライアント側でMYSQL_OPT_GET_SERVER_PUBLIC_KEYを設定してRSA公開鍵を取得する必要がある。MygramDBではlibmysqlclientのmysql_options()でこのオプションを有効にした。

mysql_native_passwordはクライアント側には残っている

紛らわしいが、libmysqlclient(クライアントライブラリ)にはmysql_native_passwordの実装がまだ残っている。9.6.0のソースではlibmysql/authentication_native_password/に移動しているだけだ。削除されたのはサーバー側のプラグインで、クライアントから8.x以前のサーバーに接続する場合は引き続き使える。

まとめ

MySQL 8.4と9.xのbinlogフォーマットを全比較した結果を整理する。

変更対応が必要か理由
MYSQL_TYPE_VECTOR (242)必要未知のカラム型はパースを壊す
VECTOR_DIMENSIONALITY メタデータ不要TLVスキップで安全
USE_SQL_FOREIGN_KEY_F フラグ不要未知ビット無視で安全
binlog_format=ROW 非推奨設定削除のみ警告が出るだけ
mysql_native_password 削除接続設定の変更binlogフォーマットとは無関係

binlogの後方互換設計は、9.xでも期待どおりに機能している。未知のイベントはスキップできる。未知のメタデータフィールドもスキップできる。未知のフラグビットも無視できる。

ただし、未知のカラム型だけはスキップできない。ROWSイベントの行データはカラム型ごとに読み方が決まっていて、未知の型が来たら何バイト進めればいいかわからない。binlogの設計者がここだけは拡張可能にしなかったのか、それとも型の追加が稀すぎて問題にならなかったのかはわからない。いずれにせよ、VECTOR型のハンドリングを足す必要がある。エンコーディングがBLOBと同じなのは救いだ。