大規模ソーシャルゲーム開発から学んだphp&mysql実践テクニック
DESCRIPTION
OSC 2011 Hokkaidoの発表で使用したスライド資料です。 弊社が「ブラウザ三国志」や「英雄クエスト」といったゲームを、PHP+MySQLで構築してきた上で、身につけたノウハウや、注意すべき箇所、指針などをまとめた資料となっています。TRANSCRIPT
大規模ソーシャルゲーム
開発から学んだ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. ソーシャルゲーム案件の特徴
2. 基本設計とポリシー
3. サーバとアプリの構成
4. なぜPHP+MySQLなのか
5. DBの多重化について
6. アプリ実装のポイント
7. DBのインデックスについて
8. DBのロックについて
9. ロック処理のコツ
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とソフトウェア
・ Linux・ Apache・ PHP・ MySQL・ memcached・ (+ MongoDB)
この構成でほとんどの
ゲームに対応可能
ロードバランサ(Varnish)
Webサーバ(Apache)
Webサーバ(Apache)
Webサーバ(Apache)
DBマスター(MySQL)
DBスレイブ(MySQL)
DBスレイブ(MySQL)
DBスレイブ(MySQL)
DBアクセス
レプリケーション
HTTP振り分け
HTTPアクセス
バッチ処理サーバ
キャッシュサーバ(memcached)
ログサーバ(syslogd)
・ ・ ・
・ ・ ・
負荷負荷負荷負荷にににに応応応応じてじてじてじて
台数台数台数台数をををを調整調整調整調整
サーバとアプリの構成(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」を使っている
・ ロックの挙動は複雑で、しっかり理解することが重要
・ 特に行ロック時に、インデックスが使われなかった場合は、テーブルロックがかかってしまうため注意が必要
プログラマのロックをめぐる一生
1. ロックなんて知らない期→ ロックの存在自体を意識したことがない→ そもそも複数人数が同時に使うアプリを作ったことがない
2. ロック初体験期→ 初めて負荷のかかるアプリを作り、同時更新系バグを出す
→ ここで初めてロックの重要性を知り、恐怖する→ 興奮し、サルのようにロックをしまくる、とにかくロック→ 「Deadlock」、「Lock Wait Timeout」祭りがはじまる
3. ロック青年期
→ ロックしすぎはダメと気づく→ 「SHOW ENGINE INNODB STATUS」の情報を活用→ ロックの内部処理や挙動を意識しだす
DBのロックについて(2)
↓↓↓↓
↓↓↓↓
行ロックの挙動
例1: 存在するレコードを行ロックした場合
DBのロックについて(3)
Gap(Infimum)
1
Gap
2
Gap
5
Gap(supremum)
SELECT * FROM t WHERE c = 2 FOR UPDATESELECT * FROM t WHERE c = 2 FOR UPDATESELECT * FROM t WHERE c = 2 FOR UPDATESELECT * FROM t WHERE c = 2 FOR UPDATE
L
DELETE FROM t WHERE c = 2
INSERT INTO t (c) VALUE(2)
×
×
参考: ・インフィニットループ社内勉強会資料 佐々木 亨基 MySQL InnoDB によるロック処理入門 ※近日公開予定 ・技術評論社 奥野幹也 著 エキスパートのためのMySQL運用+管理トラブルシューティングガイド
※ 例は全てMySQL5.1系で検証
ギャップロックの挙動
例2: 存在しないレコードを行ロックした場合
DBのロックについて(4)
Gap(Infimum)
1
Gap
2
Gap
5
Gap(supremum)
SELECT * FROM t WHERE c = 4 FOR UPDATESELECT * FROM t WHERE c = 4 FOR UPDATESELECT * FROM t WHERE c = 4 FOR UPDATESELECT * FROM t WHERE c = 4 FOR UPDATE
L
SELECT * FROM t WHERE c = 3 SELECT * FROM t WHERE c = 3 SELECT * FROM t WHERE c = 3 SELECT * FROM t WHERE c = 3
FOR UPDATEFOR UPDATEFOR UPDATEFOR UPDATE
INSERT INTO t (c) VALUE(4)INSERT INTO t (c) VALUE(4)INSERT INTO t (c) VALUE(4)INSERT INTO t (c) VALUE(4)
レコードレコードレコードレコードがががが存在存在存在存在しないためしないためしないためしないため、、、、ギャップギャップギャップギャップががががロックロックロックロックされるされるされるされる
INSERT INTO t (c) VALUE(3)INSERT INTO t (c) VALUE(3)INSERT INTO t (c) VALUE(3)INSERT INTO t (c) VALUE(3)××
○○○○
デッドロックの挙動二つ以上のセッションが互いの処理待ちとなり、各セッションの処理が進まなくなった状態
例3: ロックして存在確認後、レコードがなければインサートする
※ t に c < 200 のデータしか無い場合
DBのロックについて(5)
AAAA BBBB
SELECT * FROM t WHERE c = 200 FOR UPDATESELECT * FROM t WHERE c = 200 FOR UPDATESELECT * FROM t WHERE c = 200 FOR UPDATESELECT * FROM t WHERE c = 200 FOR UPDATE
SELECT * FROM t WHERE c = 300 FOR UPDATESELECT * FROM t WHERE c = 300 FOR UPDATESELECT * FROM t WHERE c = 300 FOR UPDATESELECT * FROM t WHERE c = 300 FOR UPDATE
INSERT INTO t (c) VALUE(200)INSERT INTO t (c) VALUE(200)INSERT INTO t (c) VALUE(200)INSERT INTO t (c) VALUE(200)
INSERT INTO t (c) VALUE(300)INSERT INTO t (c) VALUE(300)INSERT INTO t (c) VALUE(300)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からお問い合わせください