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インスタンスを立ち上げることに成功しました。これを元に次の回ではレプリケーションを構築し、意図的に遅延を発生させてみます。