🐕

ソシャゲ開発におけるマスタ/ユーザーデータのtips

2022/09/18に公開

概要

ソーシャルゲームは開発初期と運用後で仕様が変わるので変化が激しかったり、一般的なWebサービスと比較してユーザーのデータの更新頻度もかなり高い (ex: ユーザーがボタンを連打してレベルを上げる、ガチャを引く等)

今回はそんなソーシャルゲーム開発(以下、ソシャゲ開発)において、知っておくと役に立つDB周りのtipsを紹介出来たらと思う。

概念

ソシャゲのデータは大きく2つの概念に分けられる。

  • マスタデータ
  • ユーザーデータ (トランザクションデータとも言う)

順番に説明していきたい。

マスタデータ

運営側で更新が行われるデータのこと。
例えば、モンスターの名前やレベルなど、ユーザーによって変化する事のない静的なデータが挙げられる。

id name level
mon_1 スライム 5

ユーザーデータ

実際にソシャゲをプレイするユーザー側で更新が行われるデータのこと。
例えば、そのユーザーが倒したモンスターの種類など、ユーザーによって変化する動的なデータが挙げられる。

user_id beat_monster_id
user_1 mon_1

マスタデータのtips

バイナリデータにしてAPIサーバーのメモリに載せるようにする

冒頭に述べたように、モンスターの名前やレベルが頻繁に変更される事は殆どない。

毎回APIが叩かれる度に、上記のような変更が起きていないデータをDBにクエリ叩いて参照していたら、果たしてどうなるか?

  • 負荷が高騰してしまい、余計にインフラ料金がかかってしまう
  • 最悪負荷に耐えきれなくて障害に繋がってしまう

など、様々な問題が起きてしまうだろう。

なので、ソシャゲではデータの管理にはDBを用いるが、予めバイナリデータにしておいて、サーバーの起動時にそのバイナリを取得して、メモリ上に載せてしまうことが多い。

メモリ上にあれば、

  • 特に検索コストを気にせず、APIのロジック都合で参照が可能
  • テーブル変更時のマイグレーションを考慮しなくても良い

などのメリットを享受出来る。

データは表計算ソフトから入力出来るようにする

こちらも冒頭に少しだけ述べたが、基本的にマスタデータを入れるのは運営、すなわち企画職の人達になる。
(エンジニアがモンスターの名前を設定したり、クエストの難易度を決める事はあまりないと思う)

なので、ExcelやGoogleSpreadSheetなどの表計算アプリからマスタデータの入力を行えるようにする場合が多い。

  • 毎回SQLを企画側で作る必要がなくなる
  • そもそも企画側でSQLを学ぶ必要がなくなる
  • 視覚的にも表計算の方が分かりやすい

などのメリットが挙げられる。

データをバージョン管理して、ロールバックできるようにする

例えばマスタデータに不備があって障害が起こった時に、暫定対応として、一旦正常に動いていたマスタデータを入れるとする。

この時、事前にマスタデータをS3などのストレージサービスにバージョン付きでファイルをアップロードしてパッケージ化しておくと、一つ前の状態に切り戻しやすい。

イメージ

以上の3つを満たすような運用方法だと、構成図としてはこんな感じのイメージ

ユーザーデータのtips

検索する際はフルスキャンが走らないようにする

当たり前っちゃ当たり前だが、、

Indexの貼り忘れで障害とかよくある話なので、DB設計の段階から気をつけたい。

https://qiita.com/katsukii/items/3409e3c3c96580d37c2b

レコード数が上限なく増えないように気を付ける

特に履歴系のテーブルは要注意。

上限なく増えていく場合、リリースしたては問題なくても運用が長くなるにつれて

容量が増えると何が苦しいかというと、「DBの負荷は低いのに、容量が足りないからスケールイン出来ない」という問題が発生することだ。

例えば、GCPのSpannerは1ノード4TBまでなので、仮にCPUが2%しか使ってなくても、容量が3.9TBまで来ているので2ノードにしないといけない、、みたいな感じ。

https://cloud.google.com/spanner/docs/compute-capacity?hl=ja#data_storage_limits

このような問題を回避するには、基本的には要件を詰めていって

・上限を設ける事ができないか

・有効期限を設けてしまっても問題ないか

などを企画の人と話して落とし込んでいくことである。

インフレによるオーバーフローを考慮した数値型を決める

ソシャゲを普段やっているだと分かるが、年数を重ねるごとにキャラクターのレベルや、レベルを上げるのに必要なアイテム数はどんどんインフレしていく。

例えば、新規開発時に
「1 → 2Lvに上げるのに、ダイヤが3000個くらい必要だから、MySQLのMEDIUMINTでOKか」

みたいにあまり考えずにやってしまうと、2年運用が続いて

「500 → 501Lvに上げるのに、ダイヤが9000000個必要」

みたいなケースになった時に詰む。
(MEDIUMINTは8388607まで。)

どれくらいインフレしていくかを、しっかり企画の人とエンジニアは話し込んでおくことが大事。

https://dev.mysql.com/doc/refman/5.6/ja/integer-types.html

更新は出来るだけやらないようにする

例えば、

  • ユーザーは1日3回のみ無料のガチャが引ける
  • 00:00になったら、ガチャのトップ画面上に「1日3回無料!」の表示を行う
  • ガチャを引いたら、ガチャのトップ画面から「1日3回無料!」の表示は消して、次の日の00:00になるまで表示しない

という仕様があったとする。

ガチャを引いたら、draw_countを保存するようにして、このカラムを見て再度1日1回無料で弾けるようになるかどうかを判断するとする。

user_id draw_count last_draw_date_time
user_1 3 2021/10/01 00:03:00

この時、draw_countのリセットはどのタイミングでやれば良いだろうか?

ガチャのトップ画面に遷移した時にAPIを叩いてdraw_countの更新を行うのも、悪くはない。

ただ、

  • たまたま画面遷移してガチャTOPに来た人

など、本来ガチャを引く予定がない人の分までクエリを叩く必要があるので、DBに負荷がかかる。

上記のような場合、

  • 00:00にガチャのTOP画面に来たら、APIを叩いてlast_draw_date_timeが1日経ったかどうか確認
  • 1日経ってたら、クライアントへのレスポンスは「1日3回無料!」の訴求が出るようなフラグを返す
  • 実際にユーザーがガチャを引いたら、初めてそこでリセットを行う

という風に、「TOP画面でリセットされてるかのように振舞う」のがよく使われる手法だったりする。

最後に

色々書いていったが、マスタデータもユーザーデータも「長期的にソシャゲを続けていくために、どうすれば良いか」という運用を見据えた観点で設計していくのが良い。

Discussion