はじめに
コロプラには長期間運用しているゲームタイトルが複数存在します。
何も対策を行わないと長期運用タイトルではマスターデータの量が多くなり、次第にレスポンスタイムが悪化していきます。
その上コロプラではなるべくノーメンテナンスで運用するポリシーを持っているため、随時更新され続けていくマスターデータの管理には工夫が必要です。(ノーメンテナンスで運用するノウハウなどについては別の記事で紹介させていただくと思うので今回は割愛します。)
今回はそのための工夫の一つとして、ノーメンテナンスで運用しつつ、長期間運用してもレスポンスタイムを悪化させないための仕組みについて紹介させていただきたいと思います。*1
忙しい人のための要約
- ゲーム開始時にマスターデータを一括でダウンロードしアプリ上にキャッシュ
- 次回以降は更新があった場合のみサーバーからマスターデータを取得
- 不要な通信を減らしキャッシュすることで読み込み時間短縮・負荷軽減
そもそもマスターデータとは
ここで言うマスターデータとは、アイテムの名称のようなユーザーに紐付かないデータのことを指しています。
そんなマスターデータの中にもいくつかの属性があります。
例)
- お知らせなど単発的で即時性が求められるデータ
- イベントミッションなど更新頻度が高く期限が短いデータ
- キャラなど更新頻度は低いがアクセス頻度が高いデータ
今回紹介させていただく仕組みは、キャラなど更新頻度は低いがアクセス頻度が高いデータにスポットを当てた仕組みとなっています。アクセス頻度が高いというのは一箇所だけではなく、様々なシチュエーションで参照する必要のあるデータということです。
従来の仕組み
そもそも従来のマスターデータの取り扱い方がどのようなものだったか、代表的な手法を紹介させていただきます。
- アプリを起動するたびに初回APIでマスターデータを取得
- データ更新時にタイトルに戻しマスターデータ全件再取得
- 長期運用タイトルはデータ量が膨大になり起動が遅くなる
- 各APIで必要なマスターデータを都度取得 *2
- 一般的なwebページと同様にその画面で必要な情報は全て取得
- 複数画面で参照されるマスターデータを各画面で都度取得するので非効率
メンテナンスタイムを設けて次回起動時に必要なマスターデータを全てアプリに渡しておけば、マスターデータが不足するというパターンは考慮する必要はなくなります。バージョン管理の必要なくマスターデータをアプリ上にキャッシュすることも可能です。
しかしコロプラのゲームは基本的にノーメンテナンスで更新が行われます。そのためアプリ上にマスターデータが存在しないパターンを考慮し、上記のような手法でマスターデータを取り扱っていました。
なぜ仕組みを変える必要があったのか
従来の仕組みでは常に最新のマスターデータをサーバーから取得するため通信に時間がかかっていました。
ゲームの運用が続くにつれてマスターデータが肥大化し、レスポンスタイムが次第に悪化してしまいます。場合によってはタイムアウトが発生することもありえるため、根本的に仕組みを変えて通信を高速化する必要がありました。
また運用目線ではマスターデータを追加する度に、影響あるAPIで返すマスターデータが足りているか確認する必要がありました。 ソースコードが肥大化するとこの作業だけでも多くの工数を要していました。
そのためマスターデータの取得方法を一元化し保守性を高めエンジニアの工数を削減したいという副次的な目的もありました。
具体的な仕組み
本稿の仕組みを1文にまとめると、マスターデータに対してバージョン管理を行い、バージョンの更新があった時のみサーバーからデータを取得する仕組みです。
詳細を説明するに当たり図を用意しました。
図だけではわかりにくいので具体例を挙げ、順を追ってご説明します。
まずはバージョン管理対象のテーブルを決定します。
例としてChara(キャラ)とWeapon(武器)マスターをバージョン管理の対象とします。
初回データを登録していきます。
それぞれのテーブルには2021年11月時点のキャラと武器情報が入っているとします。なので下記のように初期データを登録します。(図1)
マスターデータバージョン: 202111
Charaバージョン: 1
Weaponバージョン: 1
APIを2種類用意します。
準備はこれで完了です。
この状態で初めてアプリを起動するとまずget/versionにリクエストし、マスターデータバージョン、Charaバージョン、Weaponバージョンを取得します。(図2)
次にget/masterにアクセスし、CharaとWeaponの最新データを取得します。
この時取得したいマスターデータごとにリクエストを投げます。(図3)*3
アプリは取得したバージョン情報とマスターデータ(Chara、Weapon)をキャッシュします。
今後マスターデータバージョンの更新がない間はキャッシュしたマスターデータ(Chara、Weapon)を使い回すのでget/version、get/masterにはアクセスしません。
次にマスターデータを更新します。
どのようにマスターデータの更新を検知するか流れを確認していきましょう。
2022年2月になり、キャラの数は変わらず、武器の数が増えたとします。
そうすると下記のようにバージョンを更新します。(図4)
マスターデータバージョン: 202202
キャラバージョン: 1
武器バージョン: 2
マスターデータバージョンはバージョン管理対象のマスターデータ(Chara、Weapon)のうち1つでも更新があればバージョンを更新します。
マスターデータそれぞれのバージョン(Charaバージョン、Weaponバージョン)は、対象のテーブルが更新された場合のみ更新します。
それではマスターデータの更新を検知する流れを追っていきます。
アプリを一度落として再起動します。
アプリを立ち上げると最初に初回API(game/start)にアクセスしゲームを開始します。するとgame/startのレスポンスヘッダーでマスターデータバージョン=202202と返却されます。(図5)
アプリがキャッシュしているマスターデータバージョンは202111なので更新があったことがわかります。(図6)
一旦ゲームを立ち上げるのを中断し、get/versionにアクセスしバージョン情報(Charaバージョン、Weaponバージョン)を再取得します。
Charaバージョン=1、Weaponバージョン=2というバージョンが取得できます。
アプリがキャッシュしているCharaバージョンは1で変更がないので何もしません。
次にアプリがキャッシュしているWeaponバージョンは1なので更新があったことがわかります。(図7)
get/masterでWeaponの最新データを取得しなおします。(図8)
これでアプリ上の武器情報も最新のものになったのでゲームを開始できます。
課題、問題点。その対応策
何事にも銀の弾丸はないもので、この仕組みにも欠点があります。
マスターデータ更新時の負荷のスパイク
マスターデータの更新が発生すると全ユーザーが一斉にgame/masterにアクセスしマスターデータを取得するため、負荷がスパイクしてしまうのです。
これを避けるためにget/masterのレスポンスはCDNにキャッシュしています。*4
またゲームの特性上マスターデータの更新の度にゲームタイトルに戻すことが難しいタイトルもあります。
そういったタイトルではバージョン管理するマスターデータの対象テーブルを絞り、ゲームタイトルに戻すことなくバックグラウンドでマスターデータの更新を行っています。
マスターデータの日時管理の複雑化
開始日時、終了日時などを保持しているテーブルの場合テーブルの保持している日時と別に、マスターデータバージョンの有効化日時も考慮する必要があり、管理が複雑化します。ここはパフォーマンスとのトレードオフがあるので一概には言えませんが、日時管理が必要なテーブルをバージョン管理の対象にしない方が運用コストを下げる意味では賢明です。
どう変わったのか
この仕組みを導入することで各APIの通信量が減りレスポンスタイムが改善されました。
また、ゲームを起動する度にマスターデータを全てダウンロードする必要がなくなったので起動時間も短くなりました。
サーバーコードに関してもマスターデータの受け渡し方法を一元管理することによって保守性を向上させることができました。
おわりに
拙文ではございますが、長期間の運用でもレスポンスタイムを悪化させないマスターデータ管理方法について説明させていただきました。ブログの執筆は初めてですが、いかがでしたでしょうか。
ブログで紹介するには少し地味な内容かもしれませんが、こうした地道な対応がコロプラのタイトルを支えています。
今後もコロプラ技術ブログでは、今回のような小技的な内容から革新的なものまで様々な内容をお届けする予定です。更新の励みになりますのでぜひ購読の方よろしくお願いいたします!