MySQLのレプリケーション遅延をローカルで再現させる手順 その1

レプリケーションとその問題点

データベースへの負荷を分散させるための手段はいくつかあるのですが、比較的簡単な手段としてMaster-Slave構成によるレプリケーションを使う方法があります。

レプリケーションではMasterサーバーを書き込み専用に、Slaveサーバーを読み取り専用とします。
Masterへの変更がSlaveへ同期されることで同じデータを持つことになります。
その上で、データベースの読み取りは全てSlaveに問い合わせるという方法です。

これによる一番のメリットはSlaveを複数立てることで、データ取得系のリクエストの負荷をSlaveで分散することができるようになることです。また、付加的なメリットとしてはSlaveをバックアップとして機能させたり、Masterが停止したときに代わりのMasterとして使ったりということができる、ということも挙げられます。

もっとも、レプリケーションを使ってもMasterサーバーへの更新は分散させられませんので、書き込みが多いアプリケーションの場合は更なる工夫が必要となります。

今日書きたいのは、レプリケーションを構成したときに考慮すべきレプリケーションの遅延についてです。

Masterへの変更がSlaveへ反映されるまでの流れを、すごく簡単に書くと次のようになります。

  • Masterで実行されたクエリがログに吐かれる
  • ログがSlaveへ転送される
  • Slaveでログを読み取りクエリを実行する

これらの処理は状況によってはタイムラグを感じない程度の速度で実行されるのですが、トランザクションが大きかったりネットワークの状況が悪かったりすることで遅れることがよくあります。上で言えばログの転送やSlaveでのクエリ実行などが遅れることになります。
こうなるとMasterのデータベースの中身とSlaveのデータベースの中身が異なるという事態になります。

MasterとSlaveでデータが異なるといろいろ困ったことが起こるのですが、例えば次のようなケースが考えられます。

  • Slaveの古いデータを元にMasterを上書き処理してしまう
  • Masterにデータを追加したのに、ページをリロードしたらデータが無いように見える

レプリケーションの遅延を起こらなくするというのは不可能に近いので、遅延によりこういった不具合は常に起こりうるという考えの元でサービスを作る必要があります。

ここでは詳しく触れませんが、遅延する前提でサービスを作るにはキャッシュを駆使したりAjaxを使ったりというの方法があります。ですがそういった実装を入れることで遅延に対応したとしても、ローカルの開発環境ではそもそもレプリケーション構成になっていなかったり、レプリケーション構成をつくっても遅延が生じなかったりと、なかなか検証しにくいという問題があります。

幸いローカルマシンでレプリケーション遅延の環境を作ることができるので、その方法を紹介します。

ローカルで複数のMySQLを立てる

mysqld_multi

Macユーザーの場合、homebrewなどでMySQLを入れていることが多いと思います。複数のMySQLインスタンスを動かすためにはmysqld_multiを使います。次のコマンドでmysqld_multiの有無を確認します。

$ which mysqld_multi
/usr/local/bin/mysqld_multi

入っていれば次に進んでOK、ない場合はMySQL homebrewなどでググってインストールする必要があります。

設定ファイル /etc/my.cnf の作成

次に設定ファイルを作ります。
mysqld_multi --example とすると設定ファイルのサンプルを見ることができます。困ったときはそちらを参照するといいでしょう。また、設定ファイルは/etc/my.cnf または ~/.my.cnf のどちらかです。ここでは/etc/my.cnfとします。

[mysqld_multi]
mysqld     = /usr/local/Cellar/mysql/5.6.10/bin/mysqld_safe
mysqladmin = /usr/local/Cellar/mysql/5.6.10/bin/mysqladmin
user       = multi
password   = password
log        = /usr/local/var/log/mysql_multi.log # 任意

[mysqld]

[mysqld1]
server-id = 101
user = root
port = 3306
datadir  = /usr/local/var/mysql
socket   = /tmp/mysql.sock
pid-file = /tmp/mysql.pid

[mysqld2]
server-id = 102
user = root
port = 3307
datadir  = /usr/local/var/mysql2
socket   = /tmp/mysql.sock2
pid-file = /tmp/mysql.pid2

[mysqld1]、[mysqld2]の数字の部分を変えることで任意の数のインスタンスを扱うことができます。また、datadir、socket、pid-fileは任意ですが、各インスタンスで異なるものを指定するようにします。

2つめのインスタンスに関する設定

MySQLのインストール時に行われる設定により[mysqld1]のほうは使えるようになっているのですが、[mysqld2]については初期設定が済んでいないので、それらを実行します。

$ mkdir -p /usr/local/var/mysql2

次にdatadirを初期化します。

$ mysql_install_db --datadir=/usr/local/var/mysql2 --basedir=/usr/local/Cellar/mysql/5.6.10/

basedirにはmysqlがインストールされているディレクトリを指定します。

うまく行っていれば次のコマンドで立ち上がるはずです。

$ mysqld_multi start
$ mysqld_multi stop # 停止するとき

失敗している場合は、/etc/my.cnfの[mysqld_multi]に指定したログに何かしらの出力があるはずですので、そちらを確認してください。

また、次のコマンドで現在の状態を確認することができます。

$ mysqld_multi report

以下のコマンドで接続確認。

$ mysql -u root -p # mysqld1に接続
$ mysql -u root -p --socket=/tmp/mysql.sock2 # mysqld2に接続

また、[mysqld_multi]のuserに指定したユーザーにシャットダウン権限をつける必要があります。これをやっておかないとstopコマンドで停止できず、pidを調べてkillするはめになります。

mysql > GRANT SHUTDOWN ON *.* TO multi@localhost IDENTIFIED BY password';


ここまででローカル環境で複数のMySQLインスタンスを立ち上げることに成功しました。これを元に次の回ではレプリケーションを構築し、意図的に遅延を発生させてみます。