ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2014-12-25

MySQLレプリケーションの運用が劇的変化!!GTIDについて仕組みから理解する

メリークリスマス!!やあ、良い子のみんな!!サンタクロース・・・ではなく、ヒゲモジャギークからのクリスマスプレゼントだよ!!

というわけで、MySQL Casual Advent Calendarの25日目である。今朝Advent Calendarを覗いてみると、本日分のエントリーが無かったので、急遽書くことにした。Advent Calendar最後の日、クリスマスを飾る記事のテーマはGTIDだ。

前回の投稿では、MySQL 5.6の目玉機能として、レプリケーションがクラッシュセーフになったことを挙げた。レプリケーションまわりで言えば、もうひとつ外せない目玉機能がある。それがGTID(Global Transaction ID)である。

GTIDは良くも悪くもレプリケーションの運用を変化させる。GTIDを使うことによって得られる最大のメリットは、CHANGE MASTER TOでバイナリログポジションをいちいち指定しなくても良いということだ。スレーブが要求するGTIDから、自動的にどのポジションからバイナリログを転送すれば良いのかを判断してくれる。マスターに障害が生じたなどの理由でスレーブを昇格させるとき、最も進んだスレーブを選択しさえすれば、後は勝手に差分を調整してくれるのである。何て便利な機能なんだ!と思わざるを得ない。

その一方、GTIDを使う上では運用者のマインドにも変化が求められる。運用方法がガラリ変わってしまうからだ。スレーブの構築方法や、日々のバックアップ等の運用にも変化が求められる。今日は、便利だけど注意が必要なGTIDについて、その仕組みから解説しようと思う。

GTIDとは

まず、GTIDが何であるかということについて説明しよう。それを理解しなければ話が始まらないからだ。GTIDはその名の通り、個々のトランザクションに対してつけられたユニークな(世界でただひとつの)IDである。GTIDは、MySQLが持つ論理的なトランザクションのログであるバイナリログに記録される。

GTIDは次のような書式を持っている。

hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh:n[-n]

hhh...の部分はUUIDであり、16進数である。このUUIDはサーバーごとに個別のものであり、データディレクトリ上のauto.cnfというファイル内に記録される。auto.cnfは初回起動時に自動的に作成される。もし他のサーバーへauto.cnfをコピーしてしまうと、UUIDが重複することになり、その結果GTIDも重複することになるため、注意が必要である。n[-n]の部分は数値である。この数値は連番になっており、それぞれのサーバーにおいて、トランザクションがコミットされるたびに新しいIDが割り振られる。連番は、連続している部分を1-100というように範囲として表現することができる。すべての数値を列挙すると、GTIDは膨大な大きさになってしまうが、このように範囲で表せば、欠番がない限り2つの数値を記述すれば事足りる。

このような書式を用いることで、個々のサーバーにおいて、何番までトランザクションが実行されたかということが分かるわけである。

GTIDの管理方法

mysqld実行中にはメモリ上にGTIDが保持されるが、バイナリログ以外のどこにもGTIDは記録されない。GTIDをコンセプト通りに機能させるには、過去に実行したすべてのGTIDについての情報が必要である。

GTIDがバイナリログにしか存在しないならば、全てのバイナリログを保存しておく必要があるのか(消してはいけないのか)?という疑問が生じるかも知れないが、心配には及ばない。それぞれのバイナリログの先頭には、Previous GTID Setというイベントが記録されており、そのバイナリログが作成される前に実行されたすべてのGTIDが記録されている。バイナリログやデータに損傷がなければ、基本的には最も新しいバイナリログを見れば、過去に実行したGTIDをすべて調べることができる。ただし、最も新しいバイナリログだけは、mysqldが起動時に中身を全てスキャンして、含まれるGTIDを調べなければならない。

レプリケーションスレーブのGTID

GTIDを用いる以上、スレーブもバイナリログをONにしておく必要がある。でなければ、マスターの変更をどこまで実行したかということが分からないからだ。

スレーブのバイナリログには、マスターのGTIDがそのまま反映される。マスターの変更は、マスターのGTIDのまま、スレーブのバイナリログに記録される。これにより、スレーブはどこまでレプリケーションを実行したかということを判断することができるのである。

もちろん、スレーブ上で独自に実行したトランザクションは、スレーブのUUIDを用いてGTIDが割り当てられる。スレーブを昇格した場合にも、昇格後のトランザクションには、その昇格したスレーブのUUIDを用いたGTIDがつけられることになる。

マスター接続時の処理

GTIDを使用している場合、スレーブがマスターへ接続したときには、COM_BINLOG_DUMP_GTIDというコマンドが送信される。そのコマンドと共に、最後に受信したGTIDがマスターへ送信される。

マスターはスレーブから受け取ったGTIDから、送信すべきバイナリログのポジションを見つけ出す。ところが、この作業が少々厄介だったりする。マスターはそれぞれのGTIDごとに、対応するバイナリログポジションに関する情報を、メモリ上に保持しているわけではないからだ。バイナリログには非常に多くのイベントが含まれるので、GTIDとバイナリログポジションを対応させるデータを持とうとすると、膨大なサイズになってしまう。そのため、GTIDでは、マスターは開始すべきGTIDを含んだバイナリログをスキャンして、ログポジションを発見するという手法を選択している。正確には、スレーブが要求するGTIDよりも古いGTIDを持つイベントは、単に読み飛ばされるだけなのだが。

バイナリログをスキャンするのはI/Oの負荷が高い操作になる。特にログサイズが大きければ大きいほど、その負荷は高くなる。スレーブ接続時のI/Oの負荷を大きくしたくなければ、バイナリログの最大サイズはあまり大きくし過ぎないほうが良い。max_binlog_sizeはそこそこのサイズにしておくべきだろう。

余談だが、バイナリログのサフィックスとなっている連番は6桁だが、内部的にはunsigned longなので、6桁以上のケースにも対応可能である。max_binlog_sizeを小さくするとバイナリログのローテートが増えてしまうが、100万個以上のファイルも問題なく作れるので、ローテートを過剰に恐れる必要はないだろう。

MySQL 5.6シリーズの最初のバージョンである、MySQL 5.6.10にはこの処理に問題があった。なんと、スレーブが接続したときに、マスター上ではバイナリログを若い方から全てスキャンしていたのだ。スレーブが接続、あるいは再接続する度にバイナリログを全てスキャンしていたため、大変なI/Oの負荷があった。流石にこの問題は致命的だったので、その次のバージョンであるMySQL 5.6.11で修正された。詳細はリリースノートを参照されたい。

どのバイナリログを最初にスキャンすれば良いかは、バイナリログの先頭にあるPREVIOUS GTID SETを見れば判定が可能である。MySQL 5.6.11では、バイナリログを新しいほう(番号が大きいほう)から順に検査し、対象のGTIDとPREVIOUS GTID SETを比較することで、どのファイルからレプリケーションを始めれば良いかを判別するようになっている。MySQL 5.6は登場してからかなり経っているので、古いバージョンではなく、最新のものを使うようにして欲しい。

オプション

GTIDを用いたレプリケーションを運用するには、次の3つのオプションを記述する必要がある。
  • gtid_mode=ON
  • enforce_gtid_consistency=1
  • log_slave_updates=1
log_slave_updatesはマスターには関係ないが、有効化しても無害なので書いておいても問題はない。

スレーブへのデータのコピー

新たにスレーブを追加する場合には、例のごとくmysqldumpを使うのが最も簡単である。

マスター上で既にGTIDが有効になっている場合、そのマスターからmysqldumpでバックアップを取ると、ダンプ中にSET @@global.gtid_purged='...'が含まれるようになる。(これはmysqldumpの--set-gtid-purgedオプションの、デフォルトの挙動である。)ここで指定されるGTIDは、その時のマスターのGTIDと同期しており、このダンプをスレーブへリストアすることで、自動的にGTIDが設定される仕組みである。SET @@global.gtid_purged='...'を実行すると、バイナリログのローテーションが行われるので、新しいバイナリログの先頭に、PREVIOUS GTID SETとしてgtid_purgedの値が記録されるようになっている。そのため、再起動してもgtid_purgedの設定が消えることはない。

mysqldumpを用いてデータをリストアするときの注意点としては、事前にGTIDを含んだバイナリログが存在してはイケナイという点である。既にGTIDを含んだバイナリログが存在している場合、gtid_executedがセットされるのだが、gtid_purgedはgtid_executedが空のときしか変更することができないのである。つまり、新しいサーバーにしかリストアはできない。

コールドバックアップを使う場合には、mysqlbinlogコマンドを使って、最新のバイナリログからGTID_NEXTあるいはPREVIOUS GTID SETを見つけて、gtid_purgedを自分で設定すると良いだろう。LVMのスナップショットを用いる場合には、スナップショットを取る瞬間にFLUSH TABLES WITH READ LOCKを実行するはずだが、そのときSHOW MASTER STATUSを実行して、GTIDを取得すると良い。

CHANGE MASTER TO

今回の記事では、GTIDが有効になっている場合、どのようにマスターが適切なバイナリログポジションを探しだすかということを説明した。このような仕組みになっているため、スレーブは明示的にバイナリログファイル名とポジションを指定する必要がない。従って、CHANGE MASTER TOコマンドでは、ファイル名とポジションの代わりに、MASTER_AUTO_POSITION=1を指定する。
CHANGE MASTER TO
  MASTER_HOST='ホスト名',
  MASTER_PORT=3306,
  MASTER_AUTO_POSITION=1
ちなみに、MySQL 5.6ではMASTER_USERとMASTER_PASSWORDをCHANGE MASTER TOで指定すると警告がでる。これは、できるだけパスワードをディスクに記録することがないようにするための、つまりセキュリティを強化するための措置だ。ユーザー名とパスワードは、CHANGE MASTER TOの代わりに、START SLAVEで指定する。この点については、yokuさんが解説してくれているので、そちらを参照して欲しい。

スレーブの昇格

マスターがダウンした場合、できるだけ早くサービスを再開したい場合には、スレーブのひとつをマスターに昇格させるという運用がポピュラーである。この際、必ず最も進んだスレーブを選択しなければならない。もし仮に、新しいマスターより進んだスレーブが存在すると、新しいマスターのバイナリログに存在しないGTIDが指定されると、エラーになってしまうからだ。

MySQL Utilitiesに付属しているmysqlfailoverコマンドを使うと、マスターの障害を検知し、最も進んだスレーブを割り出し、自動的に昇格するところまでやってくれる。もちろん、mysqlfailoverコマンドは、GTIDによるAUTO_POSITIONを使うことで、自動的な昇格を実現しているのである。皆さんがオレオレツールを作成されるときも、ぜひGTIDを活用して頂きたい。

なお、当然ながら、新しいマスター上には、他のスレーブが送信したGTIDに対応するバイナリログが存在していなければならない。たとえスレーブ上であっても、バイナリログをパージする際には、細心の注意が必要である。

レプリケーションルールとの併用

スレーブ側で--replicate-do-*や--replicate-ignore-*などのルールを使ってフィルタリングをすると、GTIDに欠番ができて、連番が連続しなくなるため、SHOW SLAVE STATUSの出力が大変なことになってしまう。GTIDを用いるときは、フィルタリングしないのほうが無難である。

まとめ

今日はGTIDの仕組みと、基本的な使い方について説明した。レプリケーションの運用を楽にするという点では、かなり役立つのは疑いようがない。もしそのようなニーズがあればぜひ利用を検討して欲しい。

本記事のまとめとして、GTIDを使う上での注意点をリストアップしておく。
  • GTIDはバイナリログに記録されるので、バイナリログの取り扱いは注意が必要
  • log_slave_updatesが必須
  • 接続時にバイナリログをスキャンするため、ログファイルのサイズは大きくし過ぎない方が良い
  • マスターからデータをコピーする場合には、GTIDの情報が必要
  • フィルタリングはできるだけ使わない
  • MySQL 5.6シリーズで最新のバージョンを使う
もう少し書き足りない部分もあるが、今日のところはこの辺で。

0 コメント:

コメントを投稿