GS2 Blog

Game Server Services(https://gs2.io/) の最新情報をお届けします

パフォーマンスチューニング用のツール提供を開始しました

はじめに

通信時間を最適化する一番のポイントは通信処理の並列化です。
依存関係のないデータ取得ではなるべく並列化することで、通信の待ち時間を最適化できます。

ただ、今の実装が最適化できているかどうかを正確に判断するのは困難が伴います。

追加された機能

GS2-Log のアクセスログAPM(Application Performance Monitoring) で可視化するためのツールとして GS2-Telemetry をオープンソースで公開しました。
GS2-Telemetry は Open Telemetry というオープンなテレメトリデータ収集プロトコルに則って動作し、さまざまなオープンソースを使ってテレメトリを可視化できます。

リポジトリ

github.com

GS2-Telemetry に GS2-Log からデータを取得する期間などのパラメータを指定して起動すると、
指定したホスト・ポートに対して Open Telemetry プロトコルに基づいてテレメトリデータに変換し、送信します。

Open Telemetry サーバーであり、ビジュアライズツール機能を持つ Jaeger でデータを受け止めると、以下のようにユーザーIDごとにテレメトリが分類されます。

任意のユーザーを選択することで、そのユーザーの通信履歴をシーケンサーのようなビジュアルで可視化できます。

いつ頃どういったAPIを呼び出し、処理にどれだけ時間がかかったかをわかるだけでなく
GS2-Exchange で交換処理を実行した後、各マイクロサービスでアイテムの消費や入手といった処理がどいった時系列で発生したかをツリー形式で整理された状態で閲覧できます。

この機能はうまく活用すれば、パフォーマンスチューニングだけでなく開発中の動作確認も容易に行えるようなるはずです。

GS2-Script でトランザクションを発行できるようになりました

はじめに

GS2-Script は GS2 の機能を拡張するもので、任意のタイミングでクライアントから呼び出せるようなものではなく
レベルアップした時や、アカウント引き継ぎを登録したときなどあらかじめ用意されたトリガーと関連づけることで、イベント駆動で実行できるスクリプトです。

トランザクションとは、GS2内のリソースを増減する処理を指します。

機能追加された背景

GS2-Script からは GS2-SDK を利用できるため、もともとアイテムの増減といった処理は実装可能でした。
しかし、スクリプトの実行内容はクライアントから把握できないため、キャッシュへの値反映を行いたければ
GS2-Exchange で交換レートを用意して、交換処理を実行することで、交換処理の完了通知などを持ってクライアントに結果を反映する必要がありました。

機能追加された内容の詳細

GS2-Script のネームスペースにトランザクション設定が追加されました。
さらに、GS2-Script にトランザクション実行関連の記法が追加されました。

transaction.execute({
  consumeActions={},
  acquireActions={
    transaction.service("inventory").acquire.acquire_simple_items_by_user_id({
      namespaceName="namespace",
      inventoryName="inventory",
      acquireCounts={
        {
          itemName="item",
          count=1,
        },
      },
    })
  }
})

acquireActions や consumeActions には各マイクロサービスのトランザクションアクションを設定可能で
ドキュメントにも GS2-Script での記述方法のサンプルが追加されています。

docs.gs2.io

これによって、GS2-Exchange などに交換レートを用意しなくてもスクリプトからトランザクションを発行できるようになります。

GS2-Inventory のシンプルインベントリに入手・消費時のスクリプトを設定できるようになりました

はじめに

GS2-Inventory はプレイヤーの所持品を管理するマイクロサービスです。
なかでも、シンプルインベントリはアイテム所持数の上限やインベントリの容量といった機能は持たない代わりに複数のアイテムをまとめて増減できる特徴があります。

機能の詳細は以下の記事を参照ください。

gs2.hatenablog.com

スクリプトは、GS2 内のマイクロサービスで何かイベントが生じた時に追加の処理をする仕組みです。
GS2-Script に Lua で定義したスクリプト、もしくは Amazon Event Bridge を使用して AWS 上で処理を実行できます。

追加された機能の詳細

これまでスタンダードインベントリの増減時のスクリプトは定義可能でしたが、シンプルインベントリに関しては未対応でした。
今回シンプルインベントリもスクリプトの実行に対応しました。

入手時に実行されるスクリプト

namespace = args.namespace
inventoryName = args.inventoryName
simpleItems = args.simpleItems
userId = args.userId
acquireCounts = args.acquireCounts

result = {
  permit=permit,
  overrideAcquireCounts=overrideAcquireCounts
}

消費時に実行されるスクリプト

namespace = args.namespace
inventoryName = args.inventoryName
simpleItems = args.simpleItems
userId = args.userId
consumeCounts = args.consumeCounts

result = {
  permit=permit,
  overrideConsumeCounts=overrideConsumeCounts
}

限界突破(凸)機能が追加されました

はじめに

限界突破とは、同一キャラクターを合成したり、特定の進化素材を使用することでキャラクターを強化する仕組みを指します。
限界突破を行うと、キャラクターのレベル上限が引き上げられ、より強力に育成することが可能となります。

機能追加の背景

GS2-Experience はレベルキャップの引き上げはサポートしていましたが、レアリティごとに初期値が異なったりするケースの取り回しは必ずしも扱いやすい状態とは言えませんでした。

今回 GS2-Grade というマイクロサービスを追加することで、レベル上限の管理をより直感的に行えるようにしました。

追加された機能の詳細

GS2-Grade はグレードとレベルキャップの組み合わせのみを管理し、グレードの上昇方法についてはスコープ外としています。
初期グレードはプロパティIDの正規表現にマッチするかどうかで設定が可能となっています。

具体的な例を示しましょう。
以下のような条件でグレードが定義されていたと仮定します。

GradeEntryModel

グレード ゲーム上の表現 レベルキャップ
1 ☆☆★ 50
2 ☆★★ 60
3 ★★★ 70
4 ★★★+1 75
5 ★★★+2 80

キャラクターには以下の種類があるとします。

ItemModel

キャラクターID 初期グレード
N-0001 ☆☆★
N-0002 ☆☆★
N-0003 ☆☆★
R-0001 ☆★★
R-0002 ☆★★
R-0003 ☆★★
SR-0001 ★★★
SR-0002 ★★★
SR-0003 ★★★

この場合、初期グレード用の正規表現

DefaultGrade

グレード ItemModel に対して適用する正規表現
2 ^R-.*$
3 ^SR-.*$

となります。(いずれにも該当しない場合初期グレードは1になります)

このような情報を組み合わせた型として

GradeModel

名前
defaultGrades DefaultGrade[]
experienceModelId レベルキャップを反映するGS2-Experience の経験値モデルID
gradeEntries GradeEntryModel[]

このようなデータをマスターデータとして GS2-Grade に登録します。

GS2-Grade にはグレードの更新API(加算・減算・上書き)が用意されており、グレードの値を書き換えると同一プロパティIDの GS2-Experience のランクキャップが自動的に更新されます。

GS2-Enhance の更新内容

GS2-Grade をより効率的に扱うための機能追加が GS2-Enhance に対して行われました。
GS2-Enhance は素材となるアイテムやキャラクターを消費して経験値を得る仕組みでしたが、新しく限界突破(凸)をするための専用のインターフェースが追加されました。

GS2-Enhance がサポートする限界突破は同一キャラクターや同一属性を合成するようなケースで利用できるもので、素材を使用した限界突破であれば、GS2-Exchangeなどを使用して素材を消費してグレードを加算する交換レートを用意するだけで実装できます。

GS2-Enhance は素材が同種のキャラクターであることを検証した上でグレードを上昇させるトランザクションを発行する仕組みをより簡便に実装できるようにすることを目的に機能追加されています。

そのため、GS2-Grade の GradeEntryModel には素材が同種であることを判定するための正規表現を定義することが可能です。
グレードごとに条件が設定できますので、グレード1 の時は同属性であればどんなキャラクターでもいいが、グレード3以降は完全に同種のキャラクターでなければ限界突破できないような設定も可能です。

GS2-Enhance で利用する同種判定のための正規表現は以下の構造を持ちます。

GradeEntryModel

名前
propertyIdRegex 限界突破対象のプロパティIDから変数を取り出すための正規表現
gradeUpPropertyIdRegex グレードアップに利用可能な素材を判定するための正規表現

propertyIdRegex の入力例

grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:(.*):inventory:character:item:(.*):.*

gradeUpPropertyIdRegex の入力例

grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:$1:inventory:character:item:$2:.*

プロパティIDから、propertyIdRegex でユーザーIDとアイテム名をキャプチャし、gradeUpPropertyIdRegex の $1, $2 に値を反映して正規表現を作成しています。
この正規表現にマッチするプロパティIDのリソースのみが限界突破に利用できる素材となります。

GS2-StateMachine に投機的実行機能が追加されました

はじめに

GS2-StateMachine はステートマシンをサーバーで管理することで、ゲーム開発者が定義した独自のゲームロジックを、チート耐性が高い状態で実行する仕組みです。

GS2-StateMachine の詳細は以下の記事で解説しています。

gs2.hatenablog.com

機能追加の背景

ステートマシン管理機能は柔軟な処理を、安全に実行できる仕組みです。
しかし、欠点としてステートマシンにメッセージを送信するたびに通信をしなければならないため、通信頻度の高いゲーム性ではプレイ体験の面でもGS2のAPI呼び出しコストの面でも課題がありました。

追加された機能の詳細

ステートマシン定義をサーバーとゲームで共有し、ゲームはステートマシン定義に基づいてステートマシンを実行することで通信待ちせずに投擲に処理を実行し
ステートマシンに送信したメッセージや、状態遷移した際の状態変数の変化を一定間隔でまとめて GS2-StateMachine に送信し、GS2-StateMachine は受け取ったイベントを元に同じステートマシン定義を使用したステートマシンで状態を再現しつつ、状態変数の変化が正しいかを検証することができるようになりました。

以下のメカニズムで処理が実行されます。

これにより、ステートマシンの実行にあたって通信待ちをすることなく、通信回数を減らすことが可能となり、GS2-StateMachine がより扱いやすいものになりました。

詳細なドキュメントは以下をご確認ください。

docs.gs2.io

トランザクションをより細かい粒度で待てるようになりました

はじめに

GS2 におけるトランザクションとは、GS2-Showcase の商品購入APIの戻り値 や GS2-Exchange の交換実行API の戻り値です。
このような複数のマイクロサービスの値を書き換える動作は非同期処理で実行されます。

この処理単位を 「トランザクション」 と呼んでいます。

gs2.hatenablog.com

そして、8月には非同期処理の完了を待てるように SDK を拡張しました。

機能追加の背景

ガチャを例に説明します。ガチャは一般的に以下のプロセスで実行されます。

消費「GS2-Money から課金通貨を300減らす」
入手「GS2-Lottery でガチャを実行する」

そして、ガチャを実行すると新しいトランザクションが発行されます。

消費 なし
入手「ガチャで排出されたキャラクターを GS2-Inventory に格納する」

8月に追加した内容は、トランザクション内で新しいトランザクションが発生した時にその結果も待つかどうかのオプションがあります。

var transaction = await gs2.Showcase(
  "namespace"
).Me(
  gameSession
).Showcase(
  "showcase"
).DisplayItem(
  "displayItem"
).BuyAsync(
  1
);
await transaction.WaitAsync();

このように記述すると

消費「GS2-Money から課金通貨を300減らす」
入手「GS2-Lottery でガチャを実行する」

の部分だけを待って、アイテムの入手処理は終わっていない状態で処理が返ります。
これによって、通信待ちの時間を最小にして抽選結果に応じた演出を開始し、抽選の演出している間にアイテムの付与が行えました。

var transaction = await gs2.Showcase(
  "namespace"
).Me(
  gameSession
).Showcase(
  "showcase"
).DisplayItem(
  "lottery1"
).BuyAsync(
  1
);
await transaction.WaitAsync(true);

このように記述すると

消費「GS2-Money から課金通貨を300減らす」
入手「GS2-Lottery でガチャを実行する」

消費 なし
入手「ガチャで排出されたキャラクターを GS2-Inventory に格納する」

の全てを待つことができました。
しかし、これではトランザクションがもう少し複雑な構造を持っている場合に、完全に処理を待つことしかできませんでした。

追加された機能の詳細

WaitAsync の第一引数に何も指定しない場合に続きのトランザクションオブジェクトを返すようになりました。

この機能の実例として複雑なトランザクションを追いながら解説します。
10連ガチャで9個と1個で異なる GS2-Lottery の抽選ロジックを適用する場合を考えてみましょう。

最初のトランザクション

消費「GS2-Money から課金通貨を3000減らす」
入手「GS2-Lottery で LotteryA を9回実行する」
入手「GS2-Lottery で LotteryB を1回実行する」

となりますが、このような入手処理が複数存在する場合は GS2 においてはジョブキューを経由するように変換されます。
つまり、以下のようになります。

消費「GS2-Money から課金通貨を3000減らす」
入手「GS2-JobQueue に《GS2-Lottery で LotteryA を9回実行する》と《GS2-Lottery で LotteryB を1回実行する》ジョブを登録」
JobQueue を実行し
《GS2-Lottery で LotteryA を9回実行する》と《GS2-Lottery で LotteryB を1回実行する》を実行し、最大10個のトランザクションを発行
(最大としているのは同一アイテムが排出された場合、トランザクション処理の最適化処理で数が減ることがあるため)
入手「ガチャで排出されたキャラクターを GS2-Inventory に格納する」× 最大10個

内部的には 最大11個のトランザクション、2個のジョブキューで構成されますが
SDK上は開発者がより直感的に扱えるように、上記の3段階のトランザクションステップで扱い、トランザクションステップ単位で処理を待つことができるようになりました。

つまり、改めてコードを示すと

var transaction = await gs2.Showcase(
  "namespace"
).Me(
  gameSession
).Showcase(
  "showcase"
).DisplayItem(
  "lottery10"
).BuyAsync(
  1
);
var transaction2 = await transaction.WaitAsync();
await transaction2.WaitAsync();

と記述することで、2つ目のトランザクションステップまで実行を待つことができます。
つまり

消費「GS2-Money から課金通貨を3000減らす」
入手「GS2-JobQueue に《GS2-Lottery で LotteryA を9回実行する》と《GS2-Lottery で LotteryB を1回実行する》ジョブを登録」

JobQueue を実行し
《GS2-Lottery で LotteryA を9回実行する》と《GS2-Lottery で LotteryB を1回実行する》を実行し、最大10個のトランザクションを発行

までということになります。

これで、ガチャの抽選結果が確定しつつ、アイテムをインベントリに追加する前までトランザクション処理を待つことができるようになりました。

SimpleInventory / BigInventory に任意の所持数量を設定できるようになりました

はじめに

SimpleInventory はアイテムの所持数量を管理する仕組みで、通常の Inventory が持つスタックサイズの制限や、Inventory の容量制限の管理の仕組みがない代わりに、複数のアイテムを同時に増減することが可能なインベントリです。

BigInventory はアイテムの所持数量を int64 の範囲を超えて保持できるインベントリです。

今回の更新の背景

アイテムの所持数量の操作は、これまで増減APIを通して実行することになっていました。
しかし、任意の数量を持っている状況を作り出したい時に、すでに所有している数量を考慮した上で増減量を調整して処理を実行するのはロジックが関わり不具合の原因となりかねない処理を組み込む必要がありました。

今回の更新の詳細

setSimpleItemsByUserId
GS2-Inventory SDK API リファレンス | Game Server Services | Docs

setBigItemByUserId
GS2-Inventory SDK API リファレンス | Game Server Services | Docs

APIが追加され、それぞれに対応したトランザクション入手アクションとして

Gs2Inventory:SetSimpleItemsByUserId
GS2-Inventory トランザクションアクション | Game Server Services | Docs

Gs2Inventory:SetBigItemByUserId
GS2-Inventory トランザクションアクション | Game Server Services | Docs

が追加されました。

(C) Game Server Services, Inc.