無気力生活 (ノ ´ω`)ノ ~゜

脱力系エンジニア。てきとーに生きてます。

第二回ゲームサーバー勉強会に参加してきた

7/19(土)に開催された、ゲームサーバーに特化した勉強会についてのメモです。

広くてきれいな場を提供していただいたGREEさん、ありがとうございました。 そういえばGREEさんに入るのは、今回が初めてですね( ・ω・)

今回の勉強会では、以下のようなお話を頂きました。

  • データベースの再入門
    (@nsega様 )
  • 分割と整合性をがんばる話 ソーシャルゲームの整合性対策
    (株式会社gumi 小林様)
  • MMOのサーバーについて「剣と魔法のログレス 〜いにしえの女神〜」の実装例 (株式会社Aiming 山藤様)

スライド資料はまだ公開されてない模様でしたが、一部共有をして頂けるとの事だったので、公開され次第リンク貼っておきます。
今回、書いているメモは、あくまで私個人のメモから抜粋しているので、正確でない部分があるかもしれません。その部分はご指摘いただければと思います。( ・`ω・´)

結構、分量が多くなってしまったことを反省…その分濃い勉強会でした。

データベースの再入門

初心者しかいないと仮定した説明をします、との前置きがありました。内容もDBを使うにあたっての基本的なお話が中心でした。

nsegaさんは、MongoDBの人で有名な方です サーバーサイドの開発、直近だとネイティブもやってるそうです。 DBはPosgtre、Mysqlをよく使うそうです。

2014/08/27
資料が公開されている旨いただきましたので、貼っつけておきます。

ここからメモ

データベースについて

データベースを扱うためには、データの定義を明確にする必要がある。
論理的、物理的問わず、最初に定義を固めておく。

DBMSと、問い合わせするためのSQLには、以下のような種類があり、目的が異なる。

  • DDL (Create等の)データ定義言語
  • DML (Insert等の)データ操作言語
  • DCL (Rollback等の) データ制御言語

複数人で同時に操作する、壊れたら大変なデータ、を扱うときはDB。
DBを使うケース = 検索性重視、障害時のリカバリを迅速に行う、整合性を保証したい場合

必要なデータを探すときは、インデックスがパフォーマンスにかなり影響する。DBで使われるインデックスの特性を理解すること

  • B+Treeインデックス
  • ハッシュインデックス
トランザクションを重視する理由
  • データ更新の並列性を保つ
  • データの整合性を保障する
レプリケーション
  • DBのデータを複製できる。障害が発生しても、ある程度、データが元に戻せる。
  • マスタ-スレーブ構成。Update等の更新は、マスタに対して行う。
    マスタの変更は随時、スレーブに配信する、アプリからのReadはスレーブから。

RDBMSとNoSQLについて

  • RDBMS

    • データの一貫性保つことが得意。SELECTでの複数条件を使った柔軟な検索ができる。
    • 苦手
      • たくさんの通信があるとつらい。
      • インデックス作成などの、構成変更で時間がかかる
      • カラムのパターンが、固定しづらいケースだと苦手
      • 汎用的に使おうとすると、予備カラムが増殖してやばい
      • 結果を早く返すこと苦手(トランザクション効くとどうしても)
  • NoSQL

    • 何種類かある(KV型、ドキュメントベース型、カラムファミリ型)
      • データ分散しやすい。大量データもOK。
      • スケールアウトさせやすい(分割が楽)
      • 安いサーバーでも十分
    • 苦手

お互いが不得意なことを、無理に置き換える必要はない 。適材適所が大事。DBかKVSの選択は、要件を解決するための手札でしかない。

KVSの使いどころは、タイムライン表示とか、リアルタイムランキング。 セッションステートとしても良い感じ 。

データベースを使った使用事例

データモデリング大事。あとで矛盾があっても、手戻りは基本できない。
これから勉強する人は、IPAデータベーススペシャリストの勉強が、おすすめできる。

  • データの種類と管理方法

    • マスタ系データ
      アイテムとかモンスターそのもののデータ。頻繁にかわることはあまりない
      • M_xxxのようなプレフィックスつけて管理してる
    • トランザクション系データ
      ユーザーデータ。頻繁に更新追加が行われるもの
      • T_xxxのようなプレフィックスをつけて管理してる
  • ER図自動生成
    ジークレストではEclipse使っているので、ERMaster使って、ER図からクエリ自動生成してる

Tips

  • ER図レビュー、ソースレビュー、クエリレビューやる

    • 実行計画を見る
    • リクエスト回数が多すぎないか(ORM使っているとある)
    • 事前に事故防止ができる
    • わかってるエンジニアのアサインが必須になる
  • レプリケーション設計

    • 最初からDB負荷を分散できる
    • レプリケーション遅延を見越して設計する必要ある。マスタ反映後スレーブ反映しているので、若干遅延が出てしまう。タイミングによって、変更が反映されていない情報が取れる問題が。
    • そのため、時にはせっかくスレーブに向けてたSelectクエリを、Master側に向ける必要も出てくる
  • シャーディング

    • 水平分割: レコード単位で別DBに向ける
    • 垂直分割: 1テーブル内の、更新が多い項目だけを別テーブルに切り出す。
      シャーディングすることによって、DB性能、管理しやすさ、可用性に優しくなる。ただ、テーブルJoinが難しく…設計で考慮することが、増える。
  • 開発

    • フロントとサーバーサイド分けずに、一人でやったほうが効率いい
    • API定義書とかつらい。全部自分で作ることで、コミュニケーションレスでできる。楽に。
    • ただし、両方できる人は貴重…

分割と整合性をがんばる話 ソーシャルゲームの整合性対策

gumiの清水さんのお話でした。ソーシャルゲームの運用を通して、いろいろ工夫されたことを中心に、話されてました。

gumiさんは、python使ってることで有名な、SAPですね。 最近、大口の資金調達で話題になっていたりもしました。

ここからメモ

負荷対策

新規サービスが大ヒットして、負荷が限界に…
当時の、AWS最高インスタンスでもダメだったので、単純なスケールアップでは対応できなかった。サービス側で色々改修した。

  • Player系データを、単独のDBサーバーに。
  • 機能単位で、DB先を変更していたものを、Master、Trade、Guild、Friendをそれぞれ別サーバーに分割する方法に変更(垂直分割)
    • 性能限界にぶち当たるたびに試行錯誤してた。
    • 外部キーを外して、別DBに移す作業をひらすら実施。
  • 一つの機能に負荷が集中して、対処不可能に
    • KVSにも、じゃんじゃん流す

複数ユーザーが、同時に使う機能は、分割することが難しい - Player情報系は負荷が多く、分割難しい - ロックの粒度で負荷変わらない... なので、Player毎に、接続先DBを変更する、水平分割を行って解決。
ユーザー増えたらサーバー増やす→ 性能限界の問題は解消できた。

しかし、複数DBをまたがせると、

  • 多発する不具合
  • 消えた更新、消えたカード
    トレード機能で、カードを他のユーザー所有に変更するとき、カードから、ユーザーのヒモ付を消すようにしていた。
    → このユーザーが、分割キーだったので、分割キーを消すことで、このカードはどのDBに置くべきか、判断つかなくなった。
    結果、他のユーザーの資産が影響受ける問題に。
    → 水平分割した時は、分割キーは消しちゃいけない

トランザクション

  • 不整合と戦う
    一から抜本的に対応

    • 負荷は水平分割で対処
    • XA Transactionによる一貫性担保
    • ロックによる排他制御
  • 水平分割を前提とした構成にする

    • プレイヤーデータはPlayer用のShardにまとめる。
    • ただし、ギルドテーブルだけは、全ユーザに対する網羅的なアクセスが発生するので、これは1DBサーバーにまとめるようにした。
    • マスタデータはJsonにして、デプロイ時にメモリ上に展開するような方法に。
  • DBのみで実装する

    • プレイヤーのデータはすべてDBに
    • 自動回復系ステータスもDB
       いままではKVSに格納→Redisを水平分割してた→ 人が動くたびにデータが生成されるので、サーバーを都度増やす自体に、つらい。
       DBだけ更新、KVSだけ更新が発生していて、どちらかが、漏れる事故があった。
    • 何か問題が起こると、ユーザーに特になる場合は裏技として2chで祭られ、ユーザーが損する場合は、CSが爆発。
    • なので、トランザクションを重要視してた。
    • 正規化を徹底

XA Transaction

分散トランザクション

  • XAに参加するいずれかのDBで問題起これば、ロールバック可能
  • 複数DBをまたいだトランザクションいける
  • フレームワーク側が対応していなかったので自社開発
  • Preapared後のデータコミット時に、コミット途中で死んだら?
    • 裏でCron回して、コミットされていないやつを、解除するタスクを回そうとしていた。
    • DBのバージョンアップで、Prepared状態で固まっていると、自動でロールバックする対応が入ったので、結局使わなかった。
    • が、DBが死んだ際に、復旧のため使った

デッドロック

  • トランザクション掛けた、Updateが2回、クロスしたりする起きる。 解決できないので、サービス側でレコードロックをするようにした。
  • innoDBはインデックス使ってレコードロックかけれる
  • Whereで抽出する際に、インデックスが張っていれば、レコードが特定されてそこだけ行ロックできる
  • インデックスがないと、行が特定できないのでテーブルロックが走る

発生した事例では、テーブルにインデックス貼ってなかったせいで、テーブルロックがかかる。そのため、デッドロックで、DBサーバーのCPUが張り付く問題が発生した。
また、無駄インデックスが残っていると、意図しないインデックスが使われて、思いもよらないロックが取られることがあった。

回避するためには、処理時に

  • ロックの順番を統一すること
    • DBまたがってる場合、DB順もソート
    • 別のテーブルにまたがってる場合、テーブルもソート
  • 参照に、更新処理まぜない(あればSelect、なければInsertSelect)のケース

MYSQLは親切。同じDB内のデッドロックを検知して解除してくれる

  • 片方をロールバックして、いい感じにしてくれる
  • DB分割している場合、他のDBのデッドロックは、検知できないので自力でロック解除できない

まとめ

  • KVSに移すのは問題の先延ばしにしかならない
  • デッドロック対策の前に適切なインデックスを
  • インデックスショットガン、ダメ絶対
  • プロファイラ大事

MMOのサーバーについて「剣と魔法のログレス 〜いにしえの女神〜」の実装例

Aimingの、山藤さんのお話です。

近頃、ランキングで目立ってきている、あのログレスの、実装についての内容でした。
今、目立つタイトルだけに、会場の期待度もかなり大きい印象でした。

MMOのタイトルらしく、通信周りの工夫の話が、大きくウェイト割かれていたように思います。

ここからメモ

ログレスのサーバー

フロントエンド側のサーバー、バックエンド側のサーバーを、それぞれ、階層分けて冗長化している。 WANを縦につなぐ、同階層での接続はしていない。

フロントエンドサーバー

  • ユーザークライアントが、直接接続するサーバー
  • 通信を2種類使いわけている。

バックエンドサーバー

  • ユーザークライアントは接続しないサーバー。
  • フロントエンドサーバー間の、データやりとりを、Socketで行っている。
    サーバー中では、DBとのやりとりも行っていた。
  • サーバーはC++で実装している

Socket

  • サーバープッシュが必要な箇所で使用。
  • レスポンス速度が要求される、常時接続、差分更新
  • 利点
    • オペレーション都度の、接続コストが発生しない
    • 一回の、送受信量を少なくできる
  • 欠点
    • バッテリー消費(セッション維持するとどうしてもつらい)
    • 回線切れに弱い
    • ソフトウェアで対応しないといけないことが増える
    • セッション維持している関係上、ロードバランサー使ったスケールアウト難しい

HTTP

  • サーバープッシュがいらないところ
  • レスポンス速度を要求しないところ
  • 必要都度、通信、切断、
  • 一括更新

実装例

  • キャラクタの移動

    • 歩けない場所の制御、等あるため、サーバーでも管理する
    • キャラクタが動くと
      • 描画範囲外で、見えなかったものが見えるように
      • 見えなかったユーザーも見えるように
  • 見えなかったものが、見えると、どうなる

    • 自分の行動を他のユーザーにも反映しないといけない
    • 見えてる人がとった行動を、自分に反映しないといけない 人が増えることによってやりとりするデータが増えていく
  • 移動すると、

    • 描画通信範囲の更新を繰り返す
    • 移動フロー
    • クライアントで移動開始
    • サーバー側に目的値と到達点を送信
    • サーバー側で移動の妥当性を検証
    • 周辺のプレイヤーに送る

移動の都度、サーバー介すると反応が悪くなるので、いろいろと工夫が必要。

  • 他の人は多少遅れても気にならない
  • クライアントだけで移動させてしまうと、通信範囲の計算できない
  • クライアントだけで移動させると、マップの当たり判定無視できてしまう。
  • チート問題が付きまとうので、サーバー側の妥当性は検証必須

チャット

文字ベースコミニュケーション

  • キャラクタの、チャット送信でのポップアップはSocketで表示させている。
    • チャットの吹き出しは見える人だけ見えればいよね
    • 吹き出しは発言後なるべく早く画面にだしたい
  • チャット履歴はHTTPで
    • チャットログは、オフライン時のやつも見れる必要がある
    • コミニュケーションの途切れを防ぐ

実際に起こった問題と対応

  • 一台のサーバーにログインできるプレイヤー数が限られる
    • エリア毎に別サーバーで割り当て。で分散。
  • クライアントは、裏で、フロントエンドサーバー間の接続を、切り替えながら動いている
    • この切替時のサーバー負荷が大きい
    • ユーザー移動が大量に起こると、バックエンドが時間かかってローディングが終わらない
      • サーバーをラウンドロビンして、解決
      • 影響範囲はバックエンドとフロントエンドの通信部分だけ。
        クライアントには影響なしで修正できた。構成が分離されていることによるメリット。