2010/11/22

mysqlのテーブルの「のれん分け」


二週連続でこんにちは。夜は炭水化物は控え、朝はカレーが食べたいhiroshiです。

DECOLOGもそうでしたが、サービス開始するときに、たいがいのWEBサービスは大規模になることを前提としたインフラ構成は採りません。
たとえば、1000人も会員がいない状態でDBにシャーディングが採り入れられてたら、それはちょっとした事件です。

なのでだいたいは1台のサーバにWEBサーバもDBサーバも兼任させるような状態から始まるわけです。

だんだん人気が出てきて負荷がキツイ!となれば、まずはWEBサーバとDBサーバを分けるあたりが定石なのでしょうか。
その状態でも1つのDBサーバには、サービスで利用する全テーブルが入っている状態も珍しくないかと思います。

そして、さらに人気が加速していけば、いずれはテーブルごとにDBを分ける、という局面にぶち当たるわけです。
この「DBを分ける」作業のことををうちでは「のれん分け」と呼んでいますが、この手順を紹介したいと思います。


のれん分けイメージ

大まかな手順は以下です。

  1. 三段構成にする。
  2. slaveを切り替える
  3. masterを切り替える
  4. 余分なテーブルを落とす

の4ステップです。



1.三段構成にする。


これはつまりこんな状態にすることをいいます。

三段構成イメージ

この作業は基本的には単純なslave増設と新規レプリケーション構成を組むのの組み合わせでできるので、前回のエントリ「replicationしてるMySQLのslave増設手順」を参考にしてください。

通常のレプリケーション構築との違う、ポイントとしては
  • テーブル構成は最初は丸ままコピーすること
  • 「New master」my.cnfの設定にlog-slave-updatesとreplicate-do-tableでノレン分けしたいテーブルを設定しておくこと
です。


テーブル構成を丸ままコピーするのは、そうしないとレプリケーションが失敗するからです。replicate-ignore-dbやreplicate-do-tableなどのレプリケーション設定は、「指定のDBまたはテーブルのデータだけを受け付ける(無視する)」、のではなく、「いったん全部のデータを受け付けるけどreplicate-do-table等の設定に合わないものは破棄する」という意味合いの設定です。

もう1つは特に説明は要らないですね。具体的な設定例としてはこんな感じです。

[mysqld]
log-slave-updates
replicate-do-table=db.entry

この構成が作れてNewも含めた全slaveの同期がとれたことが確認できれば次のステップです。同期がとれたかどうかの確認は件数をとるとかそういうので確認します。



2.スレーブを切り替える


これまでは、blog、user、entryというすべてのテーブルへの接続先は同じでしたが、これからはentryだけ別にしたいわけなので、そのための準備をします。
まず、LVSに別のバーチャルIPを割り当て、そこ宛てのパケットは「New slave」に割り振られるようにしておきます。

そして次にアプリケーション対応です。今まではアプリケーション中のDBの接続先の設定はMASTERかSLAVEか、というところだけの切り分けでした。今回の場合、entryテーブルのみ別のDBグループに切り分けることになるので、接続先の設定にentryのslave(New slave)に割り振るようにします。

イメージ的にはこんな感じです。

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
  $conn = DBConnection::get(MASTER);
 } else {
  $conn = DBConnection::get(SLAVE);
 }

 parent::__construct($conn);
}

だったのを・・・

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
  $conn = DBConnection::get(MASTER);
 } else {
  $conn = DBConnection::get(ENTRY_SLAVE);
 }

 parent::__construct($conn);
}
 

こんな感じに。

この時点ではまだentryのmaster(New master)への割り振りはしません。
WEBサーバが冗長構成をとっている場合は特にそうなのですが、アプリケーションをデプロイしている間にどうしてもmasterとnew Masterの両方をentryのマスターとして捉えられてしまうタイミングができてしまいます。(DECOLOGの場合)その結果、データの整合性が取れなくなってしまうため、まずはスレーブのみ切り替えを行います。これはidにauto incrementを使っている場面を想像するとわかりやすいと思います。



一方でスレーブの場合はslaveでもNew slaveでもどちらを参照されてもデータの整合性的に問題ないので、サービスを止めずに切り替えることができます。

そのため、DECOLOGでは、まずはslaveのみ切り替え、後にメンテナンス時にサービスを停止して、DBアクセスのない状態にしてからマスタを切り替えるという運用をしています。

slave切り替え後はこんな感じのイメージです。


blog,userテーブルのreadはslaveへ。entryテーブルのreadはNew slaveへ




3.マスタを切り替える


前述のとおり、サービスを停止しマスタへのアクセスがなくなった状態になってから、マスタのアクセス先を切り替えたプログラムをデプロイします。
※ちなみにマスタのアクセスがなくなった状態の確認は、show master statusでpositionが動かなくなるのを確認しています。

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
   $conn = DBConnection::get(MASTER); ←ココ
 } else {
   $conn = DBConnection::get(ENTRY_SLAVE);
 }

   parent::__construct($conn);
}

マスタの切り替えが上手く入ったのを確認したら、このステップは終了です。



4.余分なテーブルを落とす


これで、アプリケーションからのentryテーブルへのアクセスは、完全に新しい構成のみとなりましたので、最後の仕上げにかかります。

最初にmaster←new masterのレプリケーションを切ります。

mysql> stop slave;
mysql> change master to master_host='';



mysql> show slave status;
Empty set (0.02 sec)
となったらOKです。

次にmasterからentryテーブルを落とします。
mysql> drop table entry;
slaveのはレプリケーションしているので勝手にentry tableはドロップされます。

最後にnew masterからblogテーブルとuserテーブルを落とします。
mysql> drop table blog,user;
ここのnew slaveからもレプリケーションしているので勝手にドロップされます。

ということで、これでのれん分けの完了です。

これは例によって「DECOLOGのやり方」ということでもっと良いやり方があるかもしれません。
よくはてブのコメントとかで「こうしたらいい」というコメントをいただいたりしますが、まさしくそういう要素を多分に含んでおりますゆえ。。。

では、また!

次回は弊社シェフによる第1回クッキング講座を予定しています。