モバオク/モバゲータウンとは
「モバオク」(画面1)は、株式会社モバオクが運営する携帯電話およびPC向けのオークションサービスで、2007年2月末時点での有料会員数84万人、出品数284万品、1日のページビュー数7000〜8000万という規模である。
「モバゲータウン」(画面2)は、株式会社ディー・エヌ・エー(以下、DeNA)が運営している携帯電話サービスで、無料ゲームやSNSを提供している。こちらは2007年3月時点の会員数が400万人で、1日のページビュー数も3億を超えている。
このように、どちらもサービスとしては世界最大級の規模となっている。システムはどちらもDeNAが開発/運用しており、LAMPベースでMySQLのレプリケーションを多用しているという特徴がある。MySQLのスレーブサーバーだけでも、モバオクは約50台、モバゲータウンは約200台を運用している。このような大規模システムであるが、各サービスとも5人程度のメンバーで開発/運用両面を担当している。
以降では、両システムで利用しているオープンソースソフトウェアの開発/運用ノウハウについて紹介する。
MySQLの活用/運用術
レプリケーションの活用
MySQLには、標準でシングルマスタの非同期レプリケーション機能が搭載されている注1(図1)。レプリケーションはDBの複製を用意する仕組みで、一般的にこの複製を多数用意して参照系クエリの負荷分散に利用する。
PostgreSQLでもSlony-Iという非同期レプリケーションの仕組みが用意されている。
シングルマスタの非同期レプリケーション機能では、マスタサーバーが1台に限定され、マスタからスレーブへの複製は非同期で行なわれるため遅延が生じ、短時間のスケールで見ると全スレーブとの同期が保証されない。しかし、その反面スレーブの台数を増加させていってもマスタサーバーの更新負荷は大きくならず、スケーラビリティを維持できるという利点がある。DeNAによる運用実績でも、マスタとスレーブ間の遅延は通常数秒程度以内に収まる。
このレプリケーションを利用する場合、アプリケーション側ではデータ更新時にはマスタサーバーへ接続し、データ参照のみを行なう場合はスレーブサーバーへ接続するように作成する必要がある。
Webや携帯電話向けサービスの場合、小さな規模で始めてユーザー規模、データ規模、ページビュー数を徐々に増加させていくことが多い。小さな規模のためDBの負荷分散が不要な場合でも、マスタサーバー1台、スレーブサーバー1台の構成にしておけば、マスタサーバーに突然障害が起きてもまったく同じデータがスレーブサーバーに常時コピーされるので、スレーブサーバーを新マスタサーバーとして利用できる。
マスタ分割/参照分割をあらかじめ想定する
データ更新の頻度が高く大量な場合、単一のマスタサーバーによる運用だとスレーブ側はマスタから流れてくるレプリケーションデータに基づく更新を行なうだけで手一杯になり、参照要求に十分に応えられなくなる。システム規模の拡大により、このような状態になった場合、「マスタ分割」という作業が必要になってくる。
マスタ分割とは
マスタ分割とは、文字通り複数のマスタサーバーを用意し、読み書きするテーブルによってアプリケーションから接続するDBを使い分ける方法である。
モバオクシステムでの分割の例を図2に示す。
モバオクシステムでは、サービス開始から1年ほど経ったタイミングで、ユーザー系テーブルと出品系テーブルにマスタ分割を行なった。出品系テーブルには、出品情報(商品名、商品説明、開始価格など)のほか、出品物に関するQ&A情報や、入札情報などが含まれる。ユーザー系テーブルには、出品系テーブル以外のデータ、つまり会員情報、落札後の取引情報(落札者/出品者の連絡先情報や支払い/送付方法の選択など)、評価(オークションの取引を通しての感想を落札者/出品者相互に評価したデータ。取引相手が信頼に足るか判断する材料とする)などのデータが含まれる。
出品系テーブルとユーザー系テーブルは将来的にマスタ分割することを想定していたため、開発開始時から1つのSQLでJOINしないような作りにしている。
また、出品数が増えた場合、出品ID(個々の出品を識別するユニークなID)を分割数で割った際の余り値で収容マスタを決定することで分散できるようになっている。例えば、出品系テーブルを2系統にマスタ分割する場合、出品IDを2で割った余り(出品ID % 2)が0であるか1であるかで、INSERTするマスタサーバーを切り替えるような処理が可能である(LIST1)。
sub _getItemHandle {
my $item_id = shift;
if ($item_id % 2 == 0) {
return DA::getHandle($_::ITEM_M1);
} else {
return DA::getHandle($_::ITEM_M2);
}
参照クエリを高速処理する参照分割
さらに、同じマスタに収容されているテーブルでも、参照するテーブルによって接続するスレーブを分ける「参照分割」も有効である。OSのファイルキャッシュも含め、テーブルやインデックスのデータがすべてメモリに載ってしまうとディスクI/Oが発生しないため、参照クエリを高速に処理できる。
モバオクでは、ユーザー系マスタサーバーに収容されている評価系テーブルとそれ以外のユーザー系テーブルとの間で参照分割を行なっている。もし、それぞれで6GBの容量を占めていたとすると、両方をメモリに載せるためには12GB以上必要となる(恐らく実際は16GB程度のメモリが必要だろう)。
しかし、参照分割を行なうのならば、それぞれが6GBのメモリで済むため、8GBのサーバーが2セットあれば事が足り、メモリ購入費用も抑えられる注2(図3)。ただし、この参照分割の場合もアプリケーション側では評価系テーブルとそのほかのユーザー系テーブルとをJOINするようなSQLは利用しないことが前提になる。また、これを前提にしておけば、基本的にマスタ自体を分割することも可能である。
実際にはレプリケーションにより参照範囲以外のデータも更新されるので、ここまでくっきりとは分かれないが、ここでは単純化して説明している。
モバオクでは、マスタ分割は2系統、参照分割は3系統で行なっているので、アプリケーションのロジック側では更新/参照するテーブルを考慮して5つのDBハンドルを使い分けている。ただし、これはどのテーブルがどのハンドルに関連付けられているか、またデータ参照のみか更新を含むかによって判断が付くので、アプリケーションの開発上は特に問題になっていない。