l'essentiel est invisible pour les yeux

Monday, March 06, 2006

[Vol.2] RailsとMySQLによる大規模サイト構築実験

vol2でも引き続き、負荷分散・高可用性を備えるDBとWEBアプリケーションフレームワーク(以下AF)のセットアップを目指します。



デュアルマスタ構成に複数台のスレーブがぶら下がっています。
もう少し、詳しく今考えているアーキテクトを見ていきたいと思います。

最小構成: マスタ2台, スレーブ1台

最低3台のDBを用意します。
なぜ3台か?スレーブを追加する際には、マスタのスナップショット取得のためにマスタへの更新を停止する必要があります。データ量が増えると、マスタを停止してスナップショットを取得するには、長時間を要するため、これは現実的な解ではない。

そこで、スレーブをマスタのスナップショットとして考え、スレーブへのレプリケーションを一時停止し、スレーブのスナップショットを新規に用意したスレーブにコピーしレプリケーションを設定する。

スレーブが1台しか存在しない場合でのスレーブ停止時は、参照系のクエリは、全てマスタに向けられるようにActive Record Clusterを実装しています。

また、ロードバランサー自体も冗長化され2台のマスタ上にActive-Passive構成で配置されています。

デュアルマスタ構成構築に当たっての注意点
デュアルマスタは、すべての問題を万事解決する銀の弾丸では無いということを理解しておかなければなりません。
具体的には、次のような問題を念頭に入れておくべきです。

・更新がリアルタイムで反映されるわけではない

つまり、レプリケーション遅延がクリティカルになるようなサービスには向かないということです。でも、現状のBtoCサービスを見る限りおそらくこれは、クリティカルな問題ではないと思ってます。

レプリケーション遅延時間の許容値
レプリケーション遅延時間の許容値を設定するのは非常に難しいです。
また、レプリケーションの遅れは定期的に監視しておくべきです。

・レプリケーション遅延時間の監視

最新のタイムスタンプのみを格納するテーブルを一つ用意し、このテーブルに対して一定の間隔でマスタがレコードを挿入するようにする。そして監視プロセスが一定間隔置きでこのレコードを読み出し→タイムスタンプを調べる。

→Nagios経由で監視するのがベター。

レプリケーション遅延が大きくなってきたときの解決策。
レプリケーション遅延が許容できなくなるぐらい大きくなったときは、もう一台クラスタを用意してテーブルを分割→負荷分散する。

[vol.1]で、次のような考察点が上がりました。
考察1 - 参照系クエリもLBで振り分けるようにするか
考察2 - マスタ間でのレプリケーション遅延により発生する、
      AUTO_INCREMENTシーケンスの衝突問題を回避するための対策
考察3 - テーブル設計の工夫

以上の考察点をもう少しブラッシュアップ。


考察1
参照系のクエリをAFで振り分けるのか?LBで振り分けるのか?

LBをセットアップするのが面倒。
AF側でリクエストの負荷分散を管理する方がよさそうである。


考察2
デュアルマスタのアーキテクトを選択することにより、AUTO_INCREMENTカラムのシーケンスの衝突が発生する問題である。レプリケーションが完了する前に各マスタがINSERTクエリを受け付けシーケンスを割り当ててしまい、レプリケーション時にキーが衝突し一意制約に反するため、レプリケーションに失敗し同期できなくなる。

現在の解決方法は二つである。
・ID値として採用しているAUTO_INCREMENT属性をやめ、MD5ハッシュを使用する。
→レプリケーション遅延によるキーの衝突はなくなりそう。

・外部のライブラリを使用し、グローバルな固有識別子(GUID)を使用する。

しかし、どちらも現実的な解ではなさげ。

もう少し、ハックなアプローチを紹介。
1. マルチパートキー
2. AUTO_INCREMENTフィールドの拡張

マルチパートキー
一つのプライマリキーを用いるのではなく、AUTO_INCREMENTなプライマリキーとMySQLで定義するserver_idのマルチパートキーをプライマリキーとして使用する。

では、INSERTのケースの場合どのようにserver-idを知ることが出来るだろうか?
MySQLでは、SERVER_ID()関数を使って自身のサーバIDを取得することが出来る。
例えば、

INSERT INTO drecoms values (SERVER_ID(), 'naito', 'naito@drecom.co.jp');

しかし、この方法には大きな問題点がある。。
→マルチパート自動増分キーはMyISAMしか使えない。
→外部キーの設定が難しくなる。

マスタはInnoDBをストレージエンジンとして使用するだろうから、これは却下。
また、AF側で決めうちでサーバIDを指定する方法がありますが、LBを通しているため、どこのマスタにデータが格納されるかわからないので、実は決めうちでserver-idを指定できない罠。

→よって却下

AUTO_INCREMENTカラムの拡張
IDカラムをINTEGER型ではなく、BIGINTを使用し、64bitに拡張。
上位32bitにサーバIDを格納し、下位32bitにIDを格納。
マルチパートキーを一つのキーで実現します。

このアプローチが一番現実的。
AR側に拡張が必要になるかもしれない。

問題点
→レコード件数が膨大になったときに、DISK容量が爆発する。
→ID値が広く分散する。


考察3
異なるクラスタ間でのテーブルのJOINには(現状)対応できないため、JOINが発生しないようにテーブルを分散できるよう設計する必要がある。


現状の進捗
現在、デュアルマスタ&スレーブ一台のクラスタの構築を完了済み。
Active Record Cluster経由でクエリが正常に分散されることも確認済み。

障害時の動作検証
Heartbeat障害&ホスト障害→Activeに発生した場合はフォールバックされることを確認。
MySQLプロセス障害→LVSハッシュテーブル・Ldirectordともに正常動作しているように見受けられるが、なぜかリクエストがルーティングされない。

→デバッグ中。

TODO
・デュアルマスタ構成はシンプルなマスタスレーブ構成に比べて少し複雑。
障害別対応をきちっと決めておく必要がある。

・MySQLの設定をデフォルトで使用しているケースが見受けれる。
例えば、スレーブが一台死んだだけで、リソースはあまっているのにMaxClientの制限に引っ掛かって接続できないようでは意味が無い。パラメータの設定基準を明確にしドキュメント化する。