2、3日前にmysqlnd_ms
をフィリピンのPHPユーザーグループに発表できたことをうれしく思っている。MySQLのマスタ-スレーブ構成での、mysqlndプラグインは、mysqlndエクステンション(訳注: mysqlndはPHP言語に対するPHPライセンスのMySQLネイティブドライバであり、PHP5.3以降には組込となっている)への透過的レイヤである。
これによって、アプリケーションを一切変更することなく、Read-Writeを分離したりスレーブへの読取りを負荷分散させたりできる。
しかし、このプラグインで、高可用の1つのパターンが実現できることをご存知だろうか?
プレゼンテーションでは2種類の方法を紹介した。1つ目はMySQL非同期レプリケーションをマスタ-スレーブ構成かマスタ-マスタ構成で利用したもの、2つ目は全ノードが書込み可能な全プライマリなクラスタとなるものである。
最初の章では、シンプルなHAソリューションを1つ目の方法で達成できたことを示す。最初に述べておくと、ここにあるサンプルコードは私のGithubレポジトリ から取得できる。
mysqlnd_ms
プラグインを使う為には、JSONフォーマットの外部コンフィグが追加で必要となる。
このコンフィグファイルは、マスタとスレーブノード群、フェイルオーバーの設定、およびフィルタ(コネクション選択方法)を定義する。
では、私が使ったmysqlnd_ms
のコンフィグから始めてみよう。 mysqlnd_ms_ms.ini
ファイルは以下の通りである。
{ "primary": { "master": { "master_1": { "host": "127.0.0.1", "port": "33001" } }, "slave": { } }, "standby": { "master": { "master_1": { "host": "127.0.0.1", "port": "33002" } }, "slave": { } } }
ここでは2つアプリケーションを定義した。1つは"primary"、もう1つは"standby"だ。単純化するため、スレーブは定義しなかった。 2つのMySQLインスタンスは、33001, 33002ポートでマスタ-マスタ構成で動作している。
mysqlnd_ms.enable = 1 mysqlnd_ms.disable_rw_split = 1 mysqlnd_ms.force_config_usage = 1 mysqlnd_ms.config_file = /home/revin/git/demo-me/phpugph201407/mysqlnd_ms_ms.ini
これがテスト用に使った master-slave.ini
だ。最初の行は単にプラグインを有効化している。
2行目の、mysqlndms.disablerw_splitは、全てのクエリをマスタに送ることを示しており、このケースではマスタのみを利用するためである。
PHPスクリプトとしては、フルコピーがここにあり、これがどういう仕組みで何をしているかを、幾分長い説明をしたいと思う。
- テストを開始するにあたって、DROPとCREATEクエリでテストテーブルをセットアップする。
- それから、for文のループに突入する。SELECT文のvalidateの後にINSERTを発行し、SELECT文では、新しく挿入された行や、現在のアクティブなサーバIDやコネクションIDといった追加情報をvalidateする。
- ループ内のイテレーションごとに、データベースサーバへの非永続的なコネクションを疑似する為に新しいmysqliオブジェクトが生成される。
- 新しいコネクションを生成する為に、
connect_mysql
関数が呼ばれ、成功すればmysqliオブジェクトが返される。ここで気にとめておかなければいけないのは、mysqlnd_ms
は標準では遅延コネクションを利用しており、すなわちmysqliオブジェクトが生成されただけでは、サーバーにまだ実際に接続されていない。'SELECT 1'
のようなステートメントを手動で発行するか、mysqli::real_connect
を呼ぶ必要がある。mysqli::ping
さえも、前述の行為のあとでなければ動作しなかったので、これに関してはバグ報告をした。 - mysqliオブジェクトが返却されたあと、INSERT文が実際に接続を確立し、ステートメントを実施する為に、
mysqlnd_ms
のトリガを引く。コネクションが確立されていなかったら、query_write_mysql
関数が検知して、connect_mysql
で再接続がされる。connect_mysql
関数では、コネクション関係のエラーコード2002
や2003
で接続に失敗した場合、プライマリへの接続が最低10回リトライされる。10回のリトライで接続できなかったら、アプリケーションは/tmp/PRIMARY_HAS_FAILED
という識別用のファイルを生成し、セカンダリ(スレーブ、またはパッシブなマスタ)に接続を試行する。
下記にプライマリがサーバID 101、スタンバイが102の時の実行例を示す。
[revin@forge phpugph201407]$ php -c master-slave.ini master-slave-ng.php Last value 0001 from server id 101 thread id 7 Last value 0003 from server id 101 thread id 8 37: [2002] Connection refused Connection to host 'primary' failed: [0] Connection refused, retrying (1 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (2 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (3 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (4 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (5 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (6 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (7 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (8 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (9 of 10) in 3 seconds Connection to host 'primary' failed: [0] Connection refused, retrying (10 of 10) in 3 seconds The primary host 'primary' has failed after 30 seconds, failing over to standby! 52: [2002] Connection refused Last value 0004 from server id 102 thread id 635 Last value 0006 from server id 102 thread id 636 Last value 0008 from server id 102 thread id 637 [...]
これは完全なものではなく、たくさんの制約がある。しかし、簡易なHA要件が求められているとき、例えばものすごくクリティカルなアプリケーションを運用しているわけではないが、夜起こされたくなく、どちらかというと朝になってから問題に対応するようなケースには向いているだろう。ここにいくつか注意を記載する。
- マスタ-スレーブ構成の場合、failoverではプライマリ(マスタ)を使えなくするだけなので、翌朝データを再構成する必要がある。
- マスタ-マスタ構成の場合、マスタを起動するだけでオンラインで復旧できる。マスタのレプリケーションが追いつき、
/tmp/PRIMARY_HAS_FAILED
ファイルをアプリケーションのスイッチバック用に削除すればよい。 /tmp/PRIMARY_HAS_FAILED
の使い方は、まだ基礎段階で他の方法もとりうる。マスタを復旧させる為には人手の介入が必要となる為、failover発生時に通知を送る、など考慮すべきである。
同じことは少しコーディングすれば達成できるだろうが、このプラグインを利用すればより手間要らずで恩恵が受けられる。
このプラグインを、複数のMasterが書込みを受け付けられる2番目の活用例としてPercona XtraDB Clusterを使って検証を行っている。いくつか興味深いことが分かったので、しばしお待ちいただきたい。