「テガキモンスター」―サーバーサイドの裏側(サーバー/DB編)

こんにちは。
今回はいよいよ「テガキモンスター」のサーバーサイド(特にDB周り)についてお話しします。

使ってるもの

比較的よくあるソーシャルゲームのサーバー構成だと思います。
バージョンはなるべく現段階で出ている安定版の最新のものを選びました。特に、PHPに関してはNamespace(名前空間)などの5.3から入った仕様もバリバリ使っています。

ちなみに余談ですが、弊社の「POMPA」や「斉藤さん」というiPhoneアプリのサーバーサイドも実はほぼ同じ構成*1になっております。

MessagePack

このMessagePackというのは、高速・小サイズのシリアライズ形式のことです。

今後、機能拡張する可能性のあるデータ(たとえばモンスターのスキルデータとか)というのはプログラム側の都合でデータ形式臨機応変に変えられた方が何かと嬉しい*2です。

そんなデータのうち、検索条件としては使わなさそうなカラムについて、テーブルを作る際、該当のカラム型をおもむろにbinary型*3にしてしまいます。そして、DBに入れる際には配列データをシリアライズしバイナリ化、取り出すときには逆にそのバイナリをデシリアライズして元の配列データとして扱えればいいのです。その際に使うシリアライザ・デシリアライザとしてMessagePackを採用しました。

DBについて

メインDBとしてMySQLを採用しているのは他のソーシャルゲームと比べて変わりませんが、MySQLTokyoTyrantMemcachedに入っているデータはそれぞれ完全に独立したデータです。つまり、従来のWebアプリでの使用法である、「マスタデータをMySQLから引っ張ってきて、あとはKVSで参照・更新を行う」というような構成は採用しませんでした。その代わりにHandlerSocketというMySQLプラグインを使って簡単なCRUD処理を高速化しています。

なぜ「マスタ:MySQL・キャッシュ:NoSQL」という構成ではないのか

理由はいくつかありますが・・・

  • データの同期処理が面倒(出来るだけユーザーのデータは一つのソースに保持しておきたい…)
  • Web上でデータを弄れない(自分でそういう管理画面を作ったりすれば別ですが…)
  • 大きなところで利用実績がある*4とはいえデータが急に消えたりしないか心配
  • HandlerSocketは十分速い!(後述)

というのが主な理由です。

確かに、そういった構成が処理時間的には一番速いとは思うのですが、運用コスト(金銭的なというより手間暇的な意味)的なことや精神衛生的なことを考慮すると、結局データソースは一つにしておいた方がよろしいんではないかなーと考えています。それに、MySQLはいい意味で枯れきっている技術ですので、例えば何かあって早急に対処しなければならないというときに、ネット上での情報が一番多いのはMySQLですよねーというのも大きいです。

まあ、細かいことをすっ飛ばして一言で言うと「フロントサイドでもMySQLまだまだ使えるよ!」ってことですね。

じゃあTokyoTyrantMemcachedは何に使っているかというと、TokyoTyrantは「件数制限を設けて、かつ最新順にしか並ばないもの」に使い、Memcachedは「一時データの保存用途」に使っています。

HandlerSocket

HandlerSocketはMobageでおなじみのDeNA社が開発しているMySQL用のプラグインで、ざっくり言うと「Indexさえ張ればSQL文がなくてもMySQLInnoDB)の中にあるデータを読み書きできるようにしてしまうプラグイン」です。詳しくはググった方が早いと思いますが、SQLパースの処理を省いていることや、DBサーバ側でリクエストを集約することでクエリの高速化を図っているようです。なんでも、参照は最大で10倍程度、更新は最大で30倍程度高速になるそうです!開発元のDeNAではキャッシュサーバとMySQLスレイブサーバを停止させてMySQLマスタサーバ一台に集約してしまったほどの威力です。

ただ、そんなHandlerSocketにも弱点はあります。

  • ソートできない!(「1万件のうち更新順で100件だけ取ってくる」とかができないのは結構痛い。基本的にソートはAPサーバー側でやる)
  • JOINできない!(複数テーブルから一気にデータを持ってくることはできますがJOINのようなクエリは困難…)
  • その他のやや複雑なクエリに対応できない!(例えばNOTとかORを使ったクエリは現時点で実装困難です)
  • トランザクションできない!
  • TIMESTAMP型を更新できない!(従って「on update CURRENT_TIMESTAMP」は使えません。更新日時はAPサーバー側で入れる)
  • トリガが実行されない!(行挿入時にregist_dateなどを入れる場合もAPサーバー側で入れる)
  • インデックスを張ってない検索条件では取得できない!

そういうわけで、MySQL本体経由でのデータ参照・更新自体をフロントサイドでは全く使ってない・・・というわけではなく、ユーザーやモンスターデータのInsert時や、HandlerSocketだけでは適切にデータを絞りきれない複雑なクエリ、トランザクションを使うクエリなどは適切にインデックスを張った上で従来通りSQL文を使っています。

ただ、このプラグインのおかげで、従来のLL言語+MySQLの組み合わせのプログラムの作り方が大きく変わる可能性があるような気がします。というより、個人的にはもうかなり変わってます。
この規模感のソーシャルゲームで使っているSQL文が上記のようなクエリで10個程度なのは本来ありえないことだと思います。

TokyoTyrant

TokyoTyrantは具体的に言うと以下のような場面で使われています。

  • バトル画面でのモンスター一覧リスト(各ユーザーのモンスターデータが更新されたときリストに挿入。モンスターのレベルでマッピングしたものをそれぞれ最大で100件程度保持)
  • メッセージデータ(各ユーザーで送受信でそれぞれ20件程度保持)
  • アクティビティデータ(フレンドがバトルに勝った/負けたなどの情報を50件程度保持)
  • バトルログデータ(自分のモンスターが誰かにバトルを仕掛けられ勝利/敗北の結果を20件程度保持)
  • グッジョブ*5データ(誰からグッジョブされたかを各ユーザー20件程度保持)
  • チームバトル結果データ(チームバトルの結果を保持)

他にもあったような気がしないでもないですが、ひとまず上記のような場面でTokyoTyrantが使われています。
前述の通り、古いデータはどんどん消していき、かつユーザーデータ等のように、そこまで(ゲームにとっては)クリティカルなデータではないデータ*6にのみ使うようにしています。

件数制限を設けて古いデータをどんどん消していく、というロジックについては、以下のページにあるロジックを参考にさせて頂きました。このロジック以外でも大変参考になることが多いので、是非一読されることをオススメします!

完全公開!ソーシャルゲーム設計事例:後編

Memcached

「アプリ連携編」でちらっとお話しした通り、このゲーム(に限らずガラケー及びスマートフォンソーシャルゲーム全般)はバトルなどの結果表示の際にちょくちょくクライアント側で演出が入るようになっているわけですが、その部分でいったんWebへのアクセスが途切れるため、一時的にデータを保存しておかないと結果のデータを表示できなくなってしまいます。

例えばテガキモンスターの場合、バトルに関しては以下のような流れになっています。

  1. (サーバー)バトルの処理(ここで結果やモンスターの状態*7を変える)
  2. (サーバー->クライアント)クライアントにアニメ処理を受け渡す*8
  3. (クライアント)アニメーション演出(ここでバトルの勝ち負けも表示)
  4. (サーバ−)バトル結果表示

つまり、1から4に直接飛べれば何も問題なくバトルの結果をWebで表示できるわけですが、クライアント側でのアニメーション演出が挟まることにより「バトル処理後、(一旦ブラウザから離れ)アニメーション演出を表示し、バトル処理で計算した結果を表示」という処理が求められます。このバトル処理の計算結果データを保存・参照するためにMemcachedを使っています。*9

まとめ

  • MySQLは使い方次第でまだまだ使える!
    • マスタはMySQL、フロントはNoSQLっていう構成は処理は速いけどデータの同期処理とかで結局運用コストが掛かっちゃうよ…というお話
  • 簡単なクエリはHandlerSocketを使いましょう!
    • MySQL本体にあるデータをKVS並に高速化して扱えるようになれば面倒なことで悩まなくて済むのでは?というお話
  • 一方で、適材適所でKVSを使えば便利!(ログみたいに古いものは消したいものだったり、セッション代わりに使うのがいいと思います)
    • 速度や扱いやすさの面では分があるのでお手軽に使えばいいんじゃないでしょうか?というお話

さて、次回は「じゃあ具体的にどういう風に動かしているのか?」ということを掘り下げてお話していきたいと思います。

*1:ただしMemcachedとMessagePackは(確か)入れてません

*2:要するにテーブルの形式を意識せずにばんばんデータを変更できた方がいいという意

*3:データ容量が見当付かない場合は各種blob型でも可

*4:TokyoTyrantは元mixi(現Google)のエンジニアの方が開発したもので、(今はどうか分かりませんが)mixiの最終ログインタイムの保持に使われているそうです

*5:ソーシャルゲームでよくあるメッセージを必要としない「あいさつ機能」的なもの

*6:最悪消えてしまってもユーザーさんがゲームをする上では支障が出ないようなデータ

*7:HP変化とか経験値付与とか

*8:「アプリ連携編」を参照

*9:本来なら、PHPに備え付けのセッション処理を使ってもいいのですが、データ保持時間を比較的簡単に指定できることや、出来るだけ高速に処理にするため、そして同じブラウザでのデバッグを簡単にするためにMemcachedを採用しました