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でのログの適応とかをやらずに、いつもこなれた管理コマンドの組み合わせでも結果としては十分な効果が得られました。