SlideShare a Scribd company logo
大規模ソーシャルゲーム
    開発から学んだ
PHP&MySQL実践テクニック
   株式会社インフィニットループ
      松井 健太郎
       2011/06/11
自己紹介(1)
松井 健太郎
・ 株式会社インフィニットループ 代表取締役
・ LOCAL PHP部 部長
・ ke-tai.org 管理人
・ コーラとバイクが好き
自己紹介(2)




http://ke-tai.org/
自己紹介(3)


         →



                 ↓
先日、自販機の営業さんが来て、現在導入予定
無料設定なので、ボタンを押すだけで、
コーラが飲める環境に!!
会社紹介
株式会社インフィニットループ
所在地: 札幌市中央区
設立:  2007年6月
人員:  27名(契約・アルバイト含む)
業務内容:ソーシャルゲーム開発
• もともと数人規模のよくあるウェブアプリケーション開発会社だったが、ブ
  ラウザゲーム開発をきっかけに、メイン事業を「ゲーム開発」「高負荷サ
  ーバサイド開発」にシフト

• 絶賛人材募集中!!
  このスライドを見て興味を持たれた方は是非声を掛けてください
開発実績
[ブラウザ三国志]
 ブラウザ三国志]
     三国志
 運営(株)AQインタラクティブ
 開発ONE-UP(株)
 プログラム開発を(株)インフィニットループが担当
 2009年7月正式サービス開始
 公式のほか、mixi、Yahoo!モバゲー、ハンゲーム、
 ニコニコアプリなど多くのプラットフォーム・チャネリングに対応、
 海外にも展開中


[英雄クエスト]
 英雄クエスト]
   クエスト
 運営・開発ONE-UP(株)
 サーバ側プログラム開発を(株)インフィニットループが担当
 2010年10月正式サービス開始
 Yahoo!モバゲーにて運用中
本日の内容
1. ソーシャルゲーム案件の特徴    6. アプリ実装のポイント

2. 基本設計とポリシー        7. DBのインデックスについて

3. サーバとアプリの構成       8. DBのロックについて

4. なぜPHP+MySQLなのか   9. ロック処理のコツ

5. DBの多重化について       10. KVSの利用
ソーシャルゲーム案件の特徴
・ 基本動作は、通常のウェブアプリケーション案件と同じ
→ HTMLとCSSで作られた画面の遷移
→ FlashやJavaScriptによる処理
→ DBへの登録・修正・削除
→ 培った業務スキルは、そのまま流用出来る

・ 通常案件との違い
→ アクセス数や負荷が桁違いに大きいため、負荷対策が必要
→ 多ユーザの同時操作に耐えるため、ロック等はしっかりと
→ 開発規模が大きく、スピードが求められる
  ・ 個人戦での限界、チーム開発は必須
  ・ 開発だけではなくマネジメント的な要素も求められる
  ・ 仕様書ありきの仕事ではない、コミュニケーションスキルが必要
  ・ リリースしてからが本当の戦い
   (感覚的にはリリース時点で、全体の60%くらい)
基本設計とポリシー(1)
破綻をきたさない設計が重要
 【よくある失敗パターン】

  1. とりあえず仕様を満たすように開発を進める
  2. テストもパスしてオープンした
  3. 利用者が増え、負荷が大きくなる
  4. サーバ台数を増やすなどして対処するが、設計上の問題からボトルネック
    発生し、ある一定数以上の接続数はどうしても捌くことができない
  5. 慢性的に重い状態となり破綻

※テストの段階で、本番と同等の負荷をかけるのはかなり難しい
※ゲームという性質上、利用人数の増加が読めない

→ サーバ台数を増やすことで、
   処理能力を上げられる設計が重要
基本設計とポリシー(2)
負荷対策を考慮した設計が重要
・ 負荷を「さばく」よりも、負荷を「かわす」方法を考える

・ データはとにかく溜めないこと
 → データには保存期限を設ける
 → DELETE文は遅いので、バッチ削除とかは無理が出てきやすい
 → データを追加するときに、最も古いデータを消すなどの工夫が必要

・ 厳密に扱う必要がないところは、どんどんルーズに
 → 重要度が低く、変更が頻繁なデータは、DBに保存しない
 → 例えばメールの新着チェックは、毎回行わずにn回に1回とする

・ ゲーム企画チームとのすり合わせがすごく大事
 → 負荷との戦いは、ゲーム仕様を決めるところから始まっている
 → 仕様書が来てから仕事開始ではない
基本設計とポリシー(3)
仕様はなるべく簡単に
 → 面白さに影響が出ないのであれば、仕様は簡単な方が良い
 → 仕様を限定しないと、のちのちサーバ負荷に限界が出てくる


          難しい仕様の例                       簡単な仕様の例
 全プレイヤーが1つのワールドでプレイ            ワールドで分けられる
 (分割は不可)                       (ワールド単位でのサーバ分割が可能)

 他者との関わりが多い                    他者との関わりが少ない
 (連携プレイが主のため、ユーザ単位での分割が難しい)    (ユーザID単位での分割が可能、排他ロックも不要)

 二窓でのプレイを許可する                  二窓でのプレイができない/許可しない
 (同時操作系のバグが起こりやすい)             (同時操作系のバグが起こりにくい ※)

 アイテムに個数制限がある 例:武器Aは世界に10個まで   個数制限はない
 (厳密なロック処理が必要)                 (ロック処理は比較的ルーズでも大丈夫 ※)

 処理結果が公開され、多くの人の目に触れる          処理結果が1度しか表示されず、1人にしか見えない
 (処理に間違いが許されない)                (処理に多少の間違いがあっても気づかれない ※)


                                          ※バグを容認しているわけではないです
                                           バグを容認しているわけではないです
サーバとアプリの構成(1)
使用している主なOSとソフトウェア                                     HTTPアクセス




・ Linux
・ Apache                                     ロードバランサ
                                              (Varnish)
                                                           HTTP振り分け


・ PHP
・ MySQL         バッチ処理サーバ
                               Webサーバ         Webサーバ             Webサーバ
                                                                              ・ ・ ・

・ memcached                     (Apache)

                                    DBアクセス
                                               (Apache)           (Apache)




・ (+ MongoDB)   キャッシュサーバ
                 (memcached)
                                                                             負荷に応じて
                                                                             負荷に
                                                                             台数を
                                                                             台数を調整
                                              DBマスター
                                               (MySQL)
                  ログサーバ                                    レプリケーション
                   (syslogd)


この構成でほとんどの                                                                    ・ ・ ・
ゲームに対応可能                       DBスレイブ
                                (MySQL)
                                              DBスレイブ
                                               (MySQL)
                                                                 DBスレイブ
                                                                  (MySQL)
サーバとアプリの構成(2)
サーバ環境にはクラウドサービスを利用している。

ヒットするかどうかで、利用者数の変化の激しいゲーム案件は、
クラウドサービスとの相性が良い。
(可能ならDBマスターなど、要所にリアルサーバを併用できるとなお良い)

・ Amazon EC2
 → 他と比べると安価な料金
 → コントロールパネルやコマンドから全て操作可
 → オートスケーリングが利用可能
 → 2011年春には、東京リージョンができ、通信レスポンスも早くなった

・ 国内クラウドサービス
 → 料金ちょっと高め
 → 日本語による手厚いサービス
 → 業者によっては、インスタンスを足すのに、
   営業に連絡して数日かかるなど、スピード感に欠ける
なぜPHP+MySQLなのか
・ 高負荷での運用に、数多くの実績がある鉄板構成

・ ネット上の情報も多く、実務に反映しやすい

・ チームマネジメント上もメリットが多い
 → 人材確保が容易、教育も容易
 → 開発スピードの向上に繋がる

・ PHPのダメなところはコーディング規約でカバー

・ APCは必ず導入
 → 試した範囲では、他の高速化エンジンよりもかなり早い

・ MySQLはなるべく新しいバージョンを
DBの多重化について
・ MySQL標準のレプリケーション機能を使っての、
 マスター/スレイブ構成が基本
・ スレイブは数を増やせば良い、マスターの負荷軽減が課題となる
・ マスター分割は、ゲーム上の仕様も絡んできて、難易度も高い
・ レプリケーション遅延を防ぐ工夫をする
→ サイズがメモリに収まる範囲なら、スレイブはtmpfs上に展開
→ 参照率をアプリ側で制御できるようにする

・ マスター/スレイブの使い分けには主に2つのアプローチがある
→ 最初マスターしか見ないように作って、
   安全な部分から徐々にスレイブやKVSを見るように切り替えていく
→ 普段はスレイブ、トランザクション開始時にマスターに接続を切り替える

・ 「MySQL Cluster」、「XAトランザクション」ってどうなんだろう
アプリ実装のポイント
・ フレームワークは使わないもしくは自作
 → 既存フレームワークを使っても結局改造するはめに
 → 機能よりも速度重視
 → Flashなどのクライアントアプリとの連携部は作り込んだ方がよい

・ O/Rマッパーは使用していない
 → 管理できないSQLは発行されないようにする
 → 自作のクエリビルダー的なものを作って利用している

・ アプリを書くときは処理スピードをそれほど意識しなくても大丈夫
 → がんばって書いても、それほど負荷は下がらない
 → それよりもDBの負荷を下げるほうが効果的
 → ただし共通処理(毎アクセス処理される箇所)は、気合を入れる
 → 大量の配列処理は遅いので注意
 → Apacheログから重いページランキングを生成し、上から順に潰す
DBのインデックスについて(1)
ここが負荷対策の最大のキモ!!
・ インデックスとは
 → 本でいう索引のようなもの
 → 適切に利用すると、何十倍・何百倍も速度が変わることも

・ むやみに張ればいいというものではない
 → 索引を増やすと本が厚くなる(サイズが大きくなる)
 → INSERT, UPDATE, DELETE時にインデックスを
    作り直すので、速度が低下する
 → インデックスサイズがメモリに収まるサイズを超えると、
    その瞬間から大幅に速度が低下する

・ インデックスを適切に使うよう、SQLをチューニングしていく
 → インデックスを張っても、SQLの書き方によっては使われない
 → EXPLAINで確認しながら、チューニングしていく
DBのインデックスについて(2)
プログラマのインデックスをめぐる一生
1. インデックスなんて知らない期
 → インデックスの存在自体を意識したことがない
 → そもそも利用者が少ないアプリしか作ったことがない

2. インデックス初体験期                     ↓
 → 初めて負荷のかかるアプリを作り、インデックスの存在を知る
 → インデックスを張ったところ、それだけで負荷が数分の1に
 → 興奮し、サルのようにインデックスを張りまくる

3. インデックスわかってきた期                  ↓
 → インデックスは張りすぎてもダメとようやく気づく
 → 複合インデックスを活用しはじめる
 → インデックスサイズを意識しだす
DBのインデックスについて(3)
インデックスを使う上での主な注意点
・ 「!=」、「<>」はインデックスが使用できない

・ LIKE検索時、前方一致以外では、インデックスは使用できない

・ ORや範囲検索(不等号)はインデックスが使用できないことがある

・ ORDER BYは、インデックスを使用できないパターンが多数ある

・ とにかくEXPLAINが大事、MySQLのスロークエリログも参考になる

→ プロジェクト内の全てのプログラマに周知徹底を!
      参考: インフィニットループ技術ブログ ソーシャルゲーム開発者なら知っておきたい MySQL INDEX + EXPLAIN入門
           http://www.infiniteloop.co.jp/blog/2011/03/mysql-index-explain/
DBのロックについて(1)
これをしっかりやらないと、リリース後バグだらけに!!
・ ロックはとても大事
→ ロックをしっかりかけていないと、連打や同時操作でバグが起こる
→ テスト環境では、再現しないことも多いのでやっかい

・ 行ロックを使う、テーブルロックは使っていない
・ ストレージエンジンには「InnoDB」、
  分離レベルには、「REPEATABLE READ」を使っている
・ ロックの挙動は複雑で、しっかり理解することが重要
・ 特に行ロック時に、インデックスが使われなかった場合は、
  テーブルロックがかかってしまうため注意が必要
DBのロックについて(2)
プログラマのロックをめぐる一生
1. ロックなんて知らない期
  → ロックの存在自体を意識したことがない
  → そもそも複数人数が同時に使うアプリを作ったことがない

2. ロック初体験期                                  ↓
  → 初めて負荷のかかるアプリを作り、同時更新系バグを出す
  → ここで初めてロックの重要性を知り、恐怖する
  → 興奮し、サルのようにロックをしまくる、とにかくロック
  → 「Deadlock」、「Lock Wait Timeout」祭りがはじまる
                                            ↓
3. ロック青年期
  → ロックしすぎはダメと気づく
  → 「SHOW ENGINE INNODB STATUS」の情報を活用
  → ロックの内部処理や挙動を意識しだす
DBのロックについて(3)
行ロックの挙動                                       ※ 例は全てMySQL5.1系で検証


例1: 存在するレコードを行ロックした場合

SELECT * FROM t WHERE c = 2 FOR UPDATE
    Gap(Infimum)
                                    DELETE FROM t WHERE c = 2
         1
        Gap
                            ×
         2            L
        Gap
         5
                            ×       INSERT INTO t (c) VALUE(2)
   Gap(supremum)
          参考: ・インフィニットループ社内勉強会資料 佐々木 亨基 MySQL InnoDB によるロック処理入門  ※近日公開予定
               ・技術評論社  奥野幹也 著 エキスパートのためのMySQL運用+管理トラブルシューティングガイド
DBのロックについて(4)
ギャップロックの挙動
例2: 存在しないレコードを行ロックした場合

SELECT * FROM t WHERE c = 4 FOR UPDATE
    Gap(Infimum)
                             SELECT * FROM t WHERE c = 3
         1
                             FOR UPDATE
        Gap
                         ○
         2
                        ×    INSERT INTO t (c) VALUE(3)

                        ×
        Gap        L
         5                   INSERT INTO t (c) VALUE(4)
   Gap(supremum)
                       レコードが存在しないため、ギャップがロックされる
                       レコードが存在しないため、ギャップがロックされる
                              しないため
DBのロックについて(5)
デッドロックの挙動
二つ以上のセッションが互いの処理待ちとなり、各セッションの処理が進まなくなった状態

例3: ロックして存在確認後、レコードがなければインサートする
※ t に c < 200 のデータしか無い場合
                        A                                          B
   SELECT * FROM t WHERE c = 200 FOR UPDATE

                                              SELECT * FROM t WHERE c = 300 FOR UPDATE

   INSERT INTO t (c) VALUE(200)

                                              INSERT INTO t (c) VALUE(300)




これがデッドロックになる
ロック処理のコツ
・ MySQLのロック処理は複雑で、新米プログラマを含めた全員が、
  完全に理解するのは難しい(INSERTが絡むと特に複雑)

・ 基本指針を示し、難しい箇所はリードエンジニアがレビューで対応

・ 基本は、「トランザクション開始後に、すぐ更新対象をロック」

・ ロック時にインデックスが使用されていることをEXPLAINで確認

・ 値を加算/減算をするときは、なるべくSQLの中で行う
  → UPDATE example_tbl SET a = a + 1 WHERE foo = ‘bar’;
                                                          とても書き切
・ ロックで取ったもの以外、値が正しいことを信じない                                れないので、
  → スレイブ遅延やファントムリードに気をつける                                 近日中に弊社
                                                          技術ブログで
・ デッドロックは、ロック範囲の見直しと、リトライで対処する                             別途資料を
                                                           公開予定
・ MySQLの気持ちになって考えるのが大事
KVS(キーバリューストア)の利用
・ KVS(memcachedなど)は、一時期積極的に利用したが、
  今は一部での利用に留めている
・ 開発やテストのコストがかかる
 → キャッシュのクリア漏れによるバグを生む
 → トランザクション中にKVSのデータを扱うのは難しい

・ システム全体のトータルコストで考える
 → KVSから値を取得し、それをアプリ側で結合するくらいなら、
    MySQLのJOINの方が早い

・ 使うところにはもちろん使う
 → PHPのセッション管理
 → 変更が入らないマスタテーブル類のキャッシュ
 → 読み込み回数が著しく多い箇所
まとめ
・ ゲーム案件であっても、特に変わったことはしておらず、
  基本が大事、基礎に忠実に淡々と
・ なるべく負荷をかけないゲーム仕様になるようすり合わせを

・ SQLチューニングとインデックスの見直しが
  負荷対策の中核
・ ロックは大事、しっかりとした理解が必要

・ キーバリューストアは、全体のコストを考えて
  使いどころを検討して使う
・ MySQL(というかデータベース)は本当に難しい!!
  → オレたちは、このMySQL坂を登りはじめたばかり
最後に
・ ゲームの仕事は、大変なこともあるが面白い
  仕事を楽しんでやることが重要
・ 参考ゲームや自分の作ったゲームを、きちんとプレイすること
  が大事、ゲームを理解せずにゲームは作れない
・ 皆に愛されるものを作れたときは、本当に幸せ
・ プレイヤーやパートナーに対し、
  いつも感謝の心を忘れずに
おまけ(1)
  スタッフ募集のお知らせ
株式会社インフィニットループでは、一緒に楽しんで
ゲームを作ってくれるスタッフを募集しています
・ PHPプログラマ、Rubyプログラマ
・ ActionScriptプログラマ
・ データベースエンジニア
・ サーバエンジニア
・ ゲーム企画・運営経験者

興味のある方は、直接声を掛けていただくか、
HPからお問い合わせください
おまけ(2)
  アルバイト募集のお知らせ
株式会社インフィニットループでは、
アルバイトも募集しています。
・ プログラマ見習い
・ テスター(プログラムのテスト、バグ検証)
・ その他雑務

せっかくプログラムを書けるスキルを持っているのに、
コンビニ等でバイトするのは勿体ない!!

学生歓迎、ノートPC貸与
興味のある方は、直接声を掛けていただくか、
HPからお問い合わせください

More Related Content

大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック