MySQLでslave追加時にmasterが全力でbinlogを送って困る時

たまにはしょうもないTIPSでも。MySQLの魅力といえば言わずもがな10年の歴史を誇る「レプリケーション」の仕組みだと思います。これさえあれば1つの筐体で必死にデータ保全しなくてもコピーがいくらでも増やせるし、@nippondanjiさんのスライドにある通り、レプリケーションの妙技を駆使することで様々に柔軟な運用を行うことができます。

slave追加とは?

さてそんなレプリケーションですが、実運用で最も多く行われるオペレーションは「slaveの追加」だと思います。追加の方法は大きく分けると2通りです。(ストレージエンジンはInnoDBを想定。というかInnoDB以外認めません><)

  • 論理バックアップを利用
    • mysqldump等を利用して論理的にデータの静止断面を作る&その時のbinlogのポジションを記録
    • それを新slaveに流しこんで、記録したポジションにchange masterstart slave
  • 物理バックアップを利用
    • xtrabackup等を利用して物理的にデータファイルの静止断面を作る&その時のポジションを記録
    • データファイルを新slaveにコピーして、記録した(ry

どちらでも稼働中のmasterの更新を止めずに静止断面を取れるのがInnoDBの魅力ですね。詳細はこれまた@nippondanjiさんの鍵本に詳しいです。

slave追加の際に気をつけること

とはいえ、僕が業務で触っているような高トランザクション環境でslaveを追加するには簡単にmasterに負荷を与えられないので一工夫必要です。うちではmysqldumpを取るためにサービス参照のないslave(通称backup)を設置して、そこで静止断面とその時のmasterのbinlogのポジションをshow slave statusを使って毎日保存しています。

slaveを追加したい時にはその日のdumpとポジションを使って上記の方法で作るのですが、start slaveしたあとbinlogをキャッチアップする際にmasterで問題が起こることがあります。

  1. binlog転送は全力で行われるのでmasterのoutのtrafficが枯れる
  2. OSのfilecacheにないのでmasterのread IOが出てしまう(参考:GREE Engineers’ Blog)

2についてはグリーさんのブログに詳しいのでそちらを見て頂くとして、ここでは1についてTIPSを。

ネットワークインタフェースを潤沢なモノにしておけばトラフィックが枯れることはあまりないと思いますが、古い環境だとたまに帯域が狭いところがあったりしてstart slaveした瞬間にmasterのトラフィックが頭打ちになり、サービスのレスポンスが遅くなることがあったりします。ってか、しました>< (Fusion-IOとかでたくさん束ねている場合も問題になるかもですね!)

masterのトラフィックを枯渇させないためには?

この問題に対してMySQL側からのアプローチは現状存在しないため、考えられる対応は以下になります。

  1. binlogをscp/rsyncで帯域制御して送って、mysqlbinlogを使ってslaveに適応し、適当なところへchange master
  2. slave側でstop slave/start slaveをこまめに実行して平均トラフィックを下げる
  3. Linux側で帯域制御する(cbq)

1がかっこいい感じなんですが、結構面倒です。まず、binlogのどこから適応させるかを先ほどのshow slave statusの結果を元にmysqlbinlogstart-positionオプションで与えつつ、適応させたいbinlogを全て引数に与えて1コマンドで実行する必要があります。

mysqlbinlog --start-position = 111111 mysqld-bin.000001 mysqld-bin.000002 \
    | mysql -uroot -p database

これが終わったらmysqld-bin.000004の先頭に向けてchange masterすれば多少は最新に近づけてからstart slaveできますね。

3については実現性まで検討してないんですが、ソフトウェア的に帯域制御してしまってmasterのトラフィックを使い過ぎないというアプローチです。ちゃんとしたソースを調べたわけではないですがcbqは自分から送るトラフィックしか制御できないらしいので、本件で利用するのであればmasterでslaveのポートへの通信を制御するしかなさそうです。(新しめのカーネルだと受信側も制御できるという噂もききました)ってかcbqよく分かりません><

なるべく脱力しようぜ!

ただ、今回時間がなかったので、一番お手軽な2の手段を取りました。熟考の末に僕があみ出したスクリプトはこれだ!どうだ!

while : ;
do
    echo "`date` stop slave"; mysql -uroot -pxxxx -e "stop slave io_thread";
    sleep 5;
    echo "`date` start slave"; mysql -uroot -pxxxx -e "start slave io_thread";
    sleep 1;
done

シンプル・イズ・ベストということで。。。

今回はslaveではsql_threadは走らせっぱなしにしておきました。io_threadの起動時間はあまり長くすると思ったよりトラフィックを使ってしまうので、このぐらいが良さそうな感じでした。実際、これでサービスに影響与えることなく無事slave追加できました。

起きてる問題に対して、スマートではないかも知れないけど最小限のリソースで解決するというのも時には重要です。slaveの追加なんてさすがに1日に何十回も起きる作業じゃないので、すごい頑張ってcbq調べたり(僕は)慣れていないmysqlbinlogでのログの適応とかをやらずに、いつもこなれた管理コマンドの組み合わせでも結果としては十分な効果が得られました。