chobi_e

MySQLユーザーのためのMySQLプロトコル入門 #4

なんとなく気分で始めたMySQLプロトコル入門ですが今回は少し趣向をかえてbinlog formatについて書いてみたいと思います。

MySQLでのレプリケーションのメッセージ単位として使われているbinlogですが、そもそもbinlog(バイナリーログ)とはどういったものなのでしょうか?実際問題よくわからなくてもちょちょっと設定すれば素敵に動いてくれるのでわからなくても大丈夫っちゃー、大丈夫なんですがせっかく興味が湧いてしまった事ですし調べていきましょう。

http://dev.mysql.com/doc/internals/en/replication-protocol.html より引用してみます。

Replication uses binlogs to ship changes done on the master to the slave and can be written to Binlog File and sent over the network as Binlog Network Stream.

ええっと、要するにレプリケーションという仕組みはbinlogというものを使ってmasterでの変更をslaveに伝えられるもので、Binlog Network Streamというものでnetwork上を流れるモノのようです。

ううん、なんだか分かるような、わからないような。ちょっとややこしいですね。
今日のところはひとまずBinlog Fileというものがどういったものなのかについて見て行きましょう。

Binlog File

http://dev.mysql.com/doc/internals/en/binlog-file.html
BinlogファイルはBinlog File Headerで始まり、Binlog Eventが連続したものである。と書かれています。

そのまんますぎますが、図で書いてみるとこんな感じでしょうか。
Screen Shot 2014-11-17 at 1.28.48 PM

Binlog File Headerとはどういうものか、というと
http://dev.mysql.com/doc/internals/en/binlog-file-header.html

0xfe, 0x62, 0x69, 0x6e (0xfe bin)の4byteから始まるファイルとかいてありますが単なるMagicですね。あまり重要ではないので次に進みましょう

Binlog Event Header

Binlog Eventが続いたもの……と書いてあるぐらいなのでここが一番重要なポイントっぽいですね。Binlog EventはMySQL Packetと同じようにHeader + Payload形式となっています。

http://dev.mysql.com/doc/internals/en/binlog-event-header.html から定義を引用します。

binlogのversionによって定義が分岐しますが、現状のMySQLではlog pos, flagsは必ずつくようになっています。今までのMySQLプロトコルをParseしていれば簡単にParse出来そうですね。

それでは早速Binlog Event Headerをparseするプログラムを書いてみましょう。

毎度の通り、PlayGroundで試せます。実行結果は下記の通りです。
http://play.golang.org/p/2QL3ZD7b29

Parse出来てそうですね!これで中身はともかく、Binlogが読み進められるようになりました。

Binlog Event Type

ひとまずBinlogのメッセージをParseできるようになりましたが、先ほどParseした0x0f(15)のEvent Typeとはなんだったのでしょうか?

全部のEventを解説していく結構な量になってしまうので
http://dev.mysql.com/doc/internals/en/binlog-event-type.html から私が興味有る部分だけ抜粋してみます。

と、これぐらいですかね。せっかくBinlogをparseするのですし自力でクエリが見れるようになりたいですよね(mysqlbinlogでqueryみれますが、まーそれはおいといて)。あとはFORMAT DESCRIPTION EVENTとROTATE EVENTぐらいがわかればとりあえず肝は分かりそうです。

それではFormat Description Eventを見ていきます

Format Description Event

http://dev.mysql.com/doc/internals/en/format-description-event.html Format Description Eventはbinlog version 4において最初のイベントで、binlog formatのバージョンや, binlogを作成したMySQL Serverのバージョン情報などが入っています。

細かい話は置いといて、まずはParseしてみましょう。

event type header lengthsの項目だけが解釈がしづらい上に解説が少ないですね。

こういった説明が少ないのはMySQLに限らずよくあるパターンなんですが、いくつか実際のバイナリを眺めていればなんとなく理解しやすいものです。

ざっくりと説明するとこの項目はuint8の配列で各種Event Headerの大きさを明記だけのものです(配列のOffsetはEventType – 1という仮定になっています)
例えば先ほどの0x0f format descriptionの大きさがどれくらいか、という場合はEvent Type – 1のOffsetを見れば大きさが書いてある、ということになります。

Binlog HeaderをParseするプログラムにFormat Description EventをParseする機能を追加してみましょう。

Format Description EventをParseすることで各イベントのサイズがわかるようになりました。
http://play.golang.org/p/m8cFKfsLIG

そいでは次すすみます。

Rotate Event

Replicationをしているとbinlogがrotationしていることに気がつくと思います。このRotate Eventでは主にbinlogのrotationを行う時にどうしたらよいか、ということが記されています。
http://dev.mysql.com/doc/internals/en/rotate-event.html から抜粋すると

binlog version 1は現状のversionではないので省いておきました。

uint64のpositionと次のbinlog fileの名前が入っているだけなのでparse簡単ですね。適当に書くとこんな感じでしょうか

実際Rotate EventはRotation以外の用途にも使われたりするのですが、現状はとりあえずParseしてみる事が目的なのでまた後日にでも解説しようと思います

Query Event

さぁ、ようやくおまちかねのQuery Eventです。
http://dev.mysql.com/doc/internals/en/query-event.html

ここらへんまでやってきた皆さんであればもうサクサクとParseできるはずです。決して解説が面倒になってきたわけではありませんヨ!?

一箇所status-varsの部分だけ分かりづらいので補足しておきます。
status-varsは連続したbyteで表したkey valueでkeyが1byte, value sizeはkeyによって事前に定義された長さを使います。

例えばstatus-varsのoffset 0が0x00の場合はKeyがQ_FLAGS2_CODEとなり、Valueの長さは4byteとなります。

まー、ぶっちゃけこれらのstatus-varsの情報は自前でReplicationをしようとかいった場合以外には不要な情報ですし実装するのが結構面倒だったりするのでスルーしておきましょう。

終わりに

Binlog FileのParseを通してEventについて学ぶことが出来ました。

実際はmysqlbinlogコマンドを使うだけで十分なんですが、ファイルフォーマットまで詳しくなっているとbinlogが壊れた時もバイナリを眺めればどこが悪いのか想像がつくようになるので覚えておいて損はないですね。

今日説明した内容のサンプルを ここ においておきますので興味がある人は書いてみて答え合わせしてみたりしてください。

そうそう、こういった新しい事とか知らない事についてやらない理由とかわからない理由こねくり回すのはそれはそれで楽しいんですが、ぼくたち創造的な開発者ですし、ごたごたいってねーでコード書こうって姿勢って大事だったりします。最初は難しいかもしれないけどちょっとトライしたら絶対できるから。
こういった事は「やるかやったか」(やるかやらないか、じゃないよ)なのでどんどん書いて引き出し増やしていきましょ。

そんなことはさておき、次回はBinlog Eventの中でもRow Based Replication部分に焦点を当てて解説して行きたいと思います。年末進行&私がAdvent Calendarに参加しすぎてしまい次の記事まで期間があいてしまうかもしれませんが、いましばらくお待ちいただければと思います。

Treasure Data Advent Calendarへのお誘い

今年はhttp://qiita.com/advent-calendar/2014/td というのを主催しておりますので、みなさんも是非是非ご参加いただければと思います。私もGREEで実際の現場で使われている中からちょろちょろと事例をご紹介できるように調整すすめています。

TD使い始めの方へのtipsや実際の事例などの共有ができれば幸いです。

それでは

sashima

GREEのUserAgent比率を公開します(2014/11)

開発企画室の佐島です。
今週末に開催されるGREE (Internal) Tech Summitという社内イベントの準備で猫の手も借りたいほど忙しいのですが、今月もGREEを利用して頂いているクライアントのUA比率を公開したいと思います。
イベントの模様は、たぶんこのblogで紹介できるんじゃないかと思ってます。

グラフは以下のデータを元に作成しています。

  • 本データはGREEのSNSサービスのブラウザに基くデータです。
  • 本データの内容の正確性・信頼性については保証いたしかねます。
  • データはスマートフォンに限ったブラウザ比率を元に集計しています。
tep

2014/11 Mini Tech Talk(グリー社内勉強会)

こんにちは、開発企画室の三木です。
グリーの社内勉強会「Mini Tech Talk」、今月のラインナップをお届けします。

今月のラインナップ

  • 毎月1日にその月のラインナップを公開させていただきます(今月はもはや12日ですが・・・)
  • 基本的にグリー社員が発表する勉強会ですが、他社からゲストスピーカーをお招きしてお話し頂く事もあります。
  • 発表終了後に資料を公開いたします。ただし【社内限定】となっている発表については資料の公開はございませんのでご了承ください。
【社内限定】2014/11/06

「MySQL Central 2014 レポート」

瀬島 貴則

MySQL Central @ Oracle Open World 2014 で話された、 MySQL5.7 や他社事例について

  • キーワード:MySQL5.7, Facebook, Twitter
  • 難易度:初心者向け
【社内限定】2014/11/14

「アカマイのプラットフォームとワールドカップ事例紹介」

アカマイ・テクノロジーズ合同会社 岡本智史さん/土屋貴史さん

今、アカマイがインターネットの全トラフィックの15%~30%を配信する企業まで成長したのは、高い技術力とそれに裏付けられた信頼性の高いプラットフォーム基盤です。今年開催したブラジルワールドカップでは、1イベントにおける配信量の記録を更新した。インターネットが社会基盤となり、それを支えるアカマイのプラットフォームについてご紹介致します。

  • キーワード:アカマイ、CDN、大規模配信、ワールドカップ、クラウド、インターネット、高速
  • 難易度:アカマイの社名を知っていて、CDNベンダーであることは知っているが、あまり知らない方、アカマイがどのような会社か興味がある方
2014/11/28

「サービスの効果を高めるグリー内製ツールの技術と紹介」

堀口 真司

お客様に、よりよいサービスを届けるために開発されたツールの紹介をします。またそれらの需要変化や規模、技術、今後の展開などをお見せします。
SAPPORO CEDEC 2014(11/21-22)での発表内容を社内向けに発表

  • キーワード:グリーを支えてきたツール
  • 難易度:初心者

Pickupレポート

上記のラインナップの中から、社内でも反響が大きかった発表をピックアップして簡単にレポートさせて頂きます(後日掲載予定です)

chobi_e

MySQLユーザのためのMySQLプロトコル入門#3

みなさん連休はどうでしたか?私はというものずーっと家に引きこもってcloudbitでsshアクセスできるようにしたりして遊んでいました。
今日の記事ではMySQL Serverに対してコマンドを発行して結果を取得していきます。

基本的にコマンドの実行の流れを最小で追ってみよう、という趣旨なので細かい分岐やエラーハンドリングなどは省いていますのであしからず。

Select Queryを投げてみよう

Queryの実行はMySQLサーバーに対してCOM_QUERYコマンドを送信すると結果が帰ってきます。

大まかなシーケンスとしてはdev.mysql.comに記載の通りとなっています。図でいうと一番左の分岐です。Select Queryを実行するだけですので、ProtocolText::Resultsetが帰ってくることが期待できそうですね。

簡単な図にするとこんな感じの順番でデータがやってきます。

Screen Shot 2014-11-06 at 4.28.13 PM

説明はほどほどにしておいて、まずはMySQLに適当なデータセットをつっこんでおきます。

ngrepでパケットを確認しつつ、テーブルの中身をしてみましょう。

selectを実行してみます

ngrepの結果は全て乗せると長くなってしまうので今回の興味がある部分だけ抜粋してみます。

そのままでもなんとなく読めちゃいそうな結果ですね。

COM_QUERY

それではまずはClientから投げるMySQL Packetを分解してみます。

先頭4byteはMySQL Packetのheaderなのでpayloadと分割すると下記のようになります。

dev.mysql.comのCOM_QUERYの解説によるとCOM_QUERYは
1byteの0x03が識別子で残りはstring[EOF]のqueryとなります。string[EOF]はpayloadの残り全部が文字列であるという意味になります。

com_query

それでは早速COM_QUERYのMySQL Packetをつくる関数を作ってみましょう。

このコードはPlayGroundで試せます、があまりにもシンプル過ぎるので特にいうことないですね。

それでは、引き続き結果セットをParseしていきましょう

COM_QUERY Response

COM_QUERYのResponseでは結果として取りうる値が数パターンあります。

MySQL Packetの1byte目によって下記の通りに分岐します。SELECTの場合はLength Encoded Integerの値が帰ります。

はて、Length Encoded Integerとは何でしょうか?

Length Encoded Integer

説明していませんでしたがMySQL protocolでの値表現はLittle Endianを使っています。
固定長の値表現だけでは時に効率的ではない事もありますので、効率性をあげたり、表現力を上げる為にMySQL ProtocolではLength Encoded Integerという表現がよく使われます。

Length Encoded integerの1から9byteに可変し、1byte目の値に応じでbyte, int8 int16, int24, int64の表現ができます。
ざっくり言うと250以下の値は1byteだけで表現でき、それ以上の場合は値の範囲に応じたサイズ分のデータがくっつく、というシンプルな構造です。

[1byte prefix]([n bytes if larger > 250])?

興味深い点としては1byte目をうまくerror packetに合わせてるところですね。それではlength encoded integer(lenenc int)を読み込む関数を作ってみましょう。

基本的にはprefix + 実データ形式となるのでそのままparseできます。GoのbinaryパッケージにはUint24というのはないので
そこだけ自前で読み込んでいますがparseは簡単ですね。

ついでに、文字列を表現するLength Encoded Stringというのもあるのでこの関数も作っときます。

見て分かる通りデータ部分のprefixにLength Encoded Integerが付いているだけです。

これらの関数はPlayGroundで確認できるようにしてありますのでいじってみると理解しやすいかと思います。

Length Encoded Integerの解説はこれくらいにしてクエリ結果の続きの解説です。

COM_QUERY Responseの続き

Select Queryの結果の場合はだいたいLength Encoded Integerでカラム数が帰ってくる、というところまで説明しました。
今度はカラム定義から結果セット部分をparseしていきます。

これを手で分割していくと下記の通りとなります。少し長いですが、カラム定義と結果セットの返却とEOFパケットの3パターンだけなのでたんたんといけます

それではカラム定義部分から解説していきましょう。

結果セットのカラム定義

カラム定義のパケットはColumnDefinitionといって、データ定義部分を引用すると:

とあるようにLength Encoded Stringでテーブル名やカラム名と型定義、そして型の詳細についてが入っています。

CharacterSetColumnTypeの定義はこのリンクを確認して下さい。

EOF Packetが来たらColumn Definition部分は終了です。続いて結果セットに行きましょう。

結果セット

結果セットのパケットはResultsetRowといい、結果セットのカラムの数だけLength Encoded Stringが帰ってきます。

今回、2つのカラムがあるというのがCOM_QUERYの最初のResponseで帰ってきているので1行には2つのカラムが入っています。
とりあえず1行だけ手でparseしてみましょう。

Length Encoded Stringがカラム数分連続しているだけなのでParseは単純ですね。文字列の1とpetrucciという値が送らてきていました。
ResultsetRowもColmunDefinitionと同様にEOF Packetがかえってくるまで繰り返して終了です。

通しでCOM_QUERYをプログラムから実行してみる

前回の記事のプログラムに今日の部分を追加してみたのがこんな感じです。
MySQL Packetは読み飛ばしやすいのでクライアントの実装にトライアンドエラーでやれると思います。

ちょっと雑ですがSelect Queryの実行から結果セットの取得までひと通り出来ましたね!

次回に向けて

この三回の連載でMySQLプロトコルの基礎的な部分は終わったのであとは興味があれば自分で実装ができると思います。

MySQLプロトコルを理解していればMySQLのネットワーク的な振る舞いが想像しやすくなりますし、色々なhackもできるので食わず嫌いせずに是非一度試してみてください。

次回のMySQLプロトコル入門の記事ではプロトコルから一旦離れBinlog Formatの解説に入ります。
それでは Happy Hacking