KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

カテゴリ: KG SDK

KLab Advent Calendar 10日目の記事です。KLab分析基盤チームの高田です。

分析基盤チームでは、社内向けに各種KPIを提供している他、KG SDKのKPIレポートシステムを通じて、パートナー向けにシステムを提供しています。

今回は、先日re:Invent 2016で発表されたばかりのAWSの新サービスAthenaを試してみました。KLabの分析基盤システムでは、すでにRedshiftやEMRを使用していますが、Athenaには、これらを補うような役割(低コストで導入し、アドホックな分析や定型的なレポートの作成をサポートするといった用途)を期待しています。

Athenaとは?

logo

ひとことで言えば、Athenaとは、S3上に置いてあるデータを高速にSQLで集計・分析できるサービスです。内部はPrestoをベースとしつつ、独自の改修をくわえて使用しているようです。Athenaの紹介としては、Amazon Web Serviceブログのこちらの記事も参考になるでしょう。

AWSでは以前より、EMRというサービスでHadoopやPrestoの機能を提供していましたが、Athenaでは自前でクラスタを組む必要なしに、クエリ検索機能を使用できます。なお、料金はクエリ量単位(5TBスキャンあたり$5)で設定されています。検索機能のみを提供するサービス形態や、課金形態はGoogleのBigQueryに似たものになっています。

複数のサービス・システム名が登場して複雑なので、以下に関連するサービスをまとめておきました。

名称 説明 提供者
Hadoop データの分散処理用のフレームワーク。 Apache Software Foundation
Hive Hadoop上で動くクエリエンジン。SQLベースの検索機能を提供。 Apache Software Foundation
Presto Hiveに似たクエリエンジン。Hadoop上でも動作するがそれ以外のデータソースも選択可能。 Facebook
EMR AWS上でHadoop/Prestoなどの分散システムを提供するサービス。 Amazon
Athena Presto相当の検索機能のみを提供。 Amazon

クエリ実行画面を試す

では早速クエリの実行を試してみましょう。sampledb というデータベースが最初から作成されており、クエリの実行をすぐに試すことができます。

athena1

新しいデータベースやテーブルの作成も、Web画面から実行できます。

athena2

データ準備

現実的なユースケースでパフォーマンスを見たいので、自前のデータも準備し、普段の分析業務で使用するようなクエリを投げてみます。

KLabの分析基盤チームの場合、データの多くは、tsv形式でS3上に置かれています。これをそのまま検索対象にできれば理想的なのですが、残念ながら圧縮形式やディレクトリ構成の問題で、何も手をくわえずに検索対象にするということはできませんでした。

※Athenaの検索対象にするためには、ディレクトリ構成に一定のルールが必要です。また、圧縮形式としては、現状Snappy, Zlib, GZIPのみがサポートされているようです(FAQを参照)。

今回は以下の三種類のデータを用意しました。

種別 説明 1日分のファイルサイズ目安(gzip圧縮時)
dau ゲームにアクセスしたユーザー 数百KB
install インストールしたユーザーのリスト 数十KB
locale ユーザーの国情報 数十MB

それぞれ、S3上の以下のようなパスにアップロードしておきます。Athena用にカラム名を除外し、gzip圧縮しましたが、それ以外はごく普通のtsv形式です。データはすべて日別にわかれており、 dt=日付 というパスにアップロードします。それぞれ11月1日から12月5日までのデータをアップロードしてあります。この key=value というパス名は、Athenaにパーティションを認識させるためのルールになります。この形式にのっとらない場合は、手動でパーティションを追加する必要があります。また、パスはテーブルごとにわける必要があります。

s3://--bucket--/athena/dau/dt=2016-12-01/dau_2016-12-01.tsv.gzip
s3://--bucket--/athena/dau/dt=2016-12-02/dau_2016-12-02.tsv.gzip
...
s3://--bucket--/athena/install/dt=2016-12-01/install_2016-12-01.tsv.gzip
...
s3://--bucket--/athena/locale/dt=2016-12-01/locale_2016-12-01.tsv.gzip

テーブル作成

あとは、Athena上でテーブルを作成するだけで、上記のファイルを検索対象とすることができます。

今回は以下のようなテーブルを作成します。テーブルの作成は、Web画面からも実行できるのですが、単純にCREATE TABLE文を実行するだけでも問題ありません。なお、ここでは dt (日付)をパーティションに指定しました。パーティションに指定したカラムは、SQL上で疑似的なカラムとして扱われるため、テーブル本体には同名のカラムを含めることができません。そこでテーブル本体の方の日付カラムには、 dtDontQuery という名前を設定しておきます。

ちなみに、テーブル定義はあれこれ試行錯誤していたのですが、テーブルスキーマがキャッシュされているのか、同名のテーブルをDROPしたあと、定義を修正して再度CREATE TABLEを実行しても、しばらくの間、古い定義が参照されてしまうという問題がありました。

CREATE EXTERNAL TABLE IF NOT EXISTS dau (
  dtDontQuery date,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/dau/'
CREATE EXTERNAL TABLE IF NOT EXISTS install (
  dtDontQuery date,
  player_id string,
  datetime timestamp
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/install/'
CREATE EXTERNAL TABLE IF NOT EXISTS locale (
  dtDontQuery date,
  player_id string,
  region string,
  datetime timestamp
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--athena--/athena/locale/'

S3上のパーティションを認識させるには、各テーブルについて以下のクエリを実行します。

MSCK REPAIR TABLE dau

athena-msck

パーティションが正常に認識されたかどうかは以下のクエリでパーティション一覧を表示することで確認できます。

SHOW PARTITIONS dau

検索

いくつかクエリを実行し、性能を見てみます。残念ながらデータそのものはお見せできないのですが、以下、参考のため、クエリと実行時間を掲載しておきます。現状SQL上で使用できる関数なども、公式ドキュメントに記載がないため、やや試行錯誤が必要でした(ANSI SQLに準拠ということなので、ある程度は検討がつくのですが)。

今回追加したファイルのうち、このlocaleのデータがもっとも巨大です。全体で1.45GBほどあるのですが、単純なCOUNTクエリであれば6秒ほどで返ってきました(スキャン自体は行なっているようです)。

SELECT COUNT(*) FROM locale

(Run time: 6.87 seconds, Data scanned: 1.45GB)

DAUを国別に集計してみます。これもlocaleのファイルが大きいためか、20秒ほどかかりました。

SELECT dau.dt, locale.region, COUNT(*)
FROM dau
JOIN locale
  ON locale.player_id=dau.player_id
     AND locale.dt=DATE('2016-12-05')
WHERE
  dau.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
GROUP BY dau.dt, region
ORDER BY dau.dt, region

(Run time: 18.45 seconds, Data scanned: 45.54MB)

インストールユーザーの3日後の継続率を集計してみます。数秒です。

SELECT i.dt, COUNT(i.player_id) AS install,
  COUNT(dau3.player_id) AS r3
FROM install AS i
LEFT JOIN (
  SELECT player_id, dt
  FROM dau
  WHERE
    dt BETWEEN DATE_ADD('DAY', 3, DATE('2016-11-01')) AND DATE_ADD('DAY', 3, DATE('2016-11-05'))
) AS dau3
  ON dau3.player_id=i.player_id AND dau3.dt=DATE_ADD('DAY', 3, i.dt)
WHERE
  i.dt BETWEEN DATE('2016-11-01') AND DATE('2016-11-05')
GROUP BY i.dt

(Run time: 3.62 seconds, Data scanned: 3.65MB)

3日連続でログインしているユーザー数を出してみます。Athenaが苦手なクエリなのか、スキャン量が少ない割に1分半もかかっています。

SELECT t.dt, COUNT(*) as cnt FROM
  (SELECT d.dt
     FROM dau AS d
     LEFT JOIN dau AS d2
        ON d.player_id=d2.player_id
          AND d2.dt>=DATE_ADD('DAY', - 2, d.dt) AND d2.dt<d.dt
     WHERE
       d.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
     GROUP BY d.dt, d.player_id
     HAVING COUNT(d2.dt)=2
  ) t
GROUP BY t.dt

(Run time: 1 minutes 28 seconds, Data scanned: 24.86MB)

こちらはサブクエリを使用するように書き変えることで大きく実行時間が変化しました。RedshiftやBigQueryと同じで、直接テーブルをJOINするのはあまり効率がよくないようです。参考のため、修正後のクエリも掲載しておきます。

SELECT t.dt, COUNT(*) as cnt FROM
  (SELECT d.dt
     FROM dau AS d
     JOIN (SELECT player_id, dt FROM dau
           WHERE
             dt BETWEEN DATE_ADD('DAY', - 2, DATE('2016-12-01'))
               AND DATE('2016-12-05')
          ) AS d2
     ON d2.player_id=d.player_id
       AND d2.dt >= DATE_ADD('DAY', -2, d.dt)
       AND d2.dt < d.dt
     WHERE
       d.dt BETWEEN DATE('2016-12-01') AND DATE('2016-12-05')
     GROUP BY d.dt, d.player_id
     HAVING COUNT(d2.dt)=2
  ) t
GROUP BY t.dt

(Run time: 4.34 seconds, Data scanned: 6.97MB)

以上のように、いくつかクエリの書き方で気をつけるべき点があるようですが、基本的には十分実用的な性能です。

Parquetへの変換

より効率のよいデータの格納方法として、AthenaではApache Parquetのような列志向のフォーマットもサポートされています(列志向フォーマット: データをカラムごとに格納するフォーマット)。Parquetへの変換方法として、公式ドキュメントで紹介されているHiveによる変換を試してみました(参照)。

EMR上でHiveを立ち上げ、S3上のテーブルを読み込ませ、Hive上でテーブルをParquet形式に変換します。

概念図: parquetへの変換

まず以下のようなSQL(HiveQL)スクリプトを用意し、S3上に保存します(DAU以外のテーブルは省略してあります)。なお残念ながらParquet形式はDATE型に対応していないため、ここでは文字列に変換しています。また以下のクエリでは、ダイナミックパーティション(クエリ結果によってパーティションを決定する)を使用しているため、 最初の方にある hive.exec.dynamic.partition の設定が必要となります。

ADD JAR /usr/lib/hive-hcatalog/share/hcatalog/hive-hcatalog-core-1.0.0-amzn-5.jar;
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;

CREATE EXTERNAL TABLE IF NOT EXISTS dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '    ',
  'field.delim' = ' '
) LOCATION 's3://--bucket--/athena/dau/';
MSCK REPAIR TABLE dau;

CREATE EXTERNAL TABLE  p_dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt string)
STORED AS PARQUET
LOCATION 's3://--bucket--/athena/parquet/dau/';

INSERT OVERWRITE TABLE p_dau PARTITION (dt) SELECT dtDontQuery, player_id, pv, dt FROM dau where dt BETWEEN '2016-11-01' AND '2016-12-05';

awscliを利用し、Hiveを立ち上げて実行させます。

export REGION=us-east-1
export SAMPLEURI=s3://--bucket--/athena/dau/
export S3BUCKET=--bucket--


aws emr create-cluster --applications Name=Hadoop Name=Hive Name=HCatalog \
--ec2-attributes KeyName=kg-kpi-keypair,InstanceProfile=EMR_EC2_DefaultRole \
--service-role EMR_DefaultRole --release-label emr-4.7.0 \
--instance-type m1.large \
--instance-count 1 --steps Type=HIVE,Name="Convert to Parquet",\
ActionOnFailure=CONTINUE,ActionOnFailure=TERMINATE_CLUSTER,Args=[-f,\
s3://path/to/hive-script.q ,-hiveconf,INPUT=${SAMPLEURI},-hiveconf,OUTPUT=s3://${S3BUCKET}/athena/parquet,-hiveconf,REGION=${REGION}] \
--region ${REGION} --auto-terminate

ジョブの実行には1時間程度かかりました。ジョブの完了後S3を見ると、確かにファイルが作られています。つづけてAthena側でもCREATE TABLEを実行し、作成されたParquetファイルを認識させます。

CREATE EXTERNAL TABLE  p_dau (
  dtDontQuery string,
  player_id string,
  pv int
)
PARTITIONED BY (dt date)
STORED AS PARQUET
LOCATION 's3://--bucket--/athena/parquet/dau/';
MSCK REPAIR TABLE p_dau

Parquet形式の場合、単純なカウントは、ファイルスキャンの必要がなくなるようです。

SELECT COUNT(*) FROM p_locale;

(Run time: 2.95 seconds, Data scanned: 0KB)

SELECT dt, COUNT(*) FROM p_dau
WHERE
  dt>=DATE('2016-12-01')
GROUP BY dt
ORDER BY dt

(Run time: 0.99 seconds, Data scanned: 0KB)

ただし試した範囲では、スキャン量が増えてしまうこともありました。実行時間もものによっては改善しましたが、大きな変化が見られないケースが多いようです。以下、実行結果の比較をまとめておきます。

形式 csv.gz実行時間 csv.gzスキャン量 Parquet実行時間 Parquetスキャン量
localeのCOUNT 6.87s 1.45GB 2.95s 0KB
国別DAU 18.45s 45.54MB 9.52s 300.03MB
3日後継続率 3.62s 3.65MB 2.69s 12.33MB
3日連続ログイン(修正前) 1m28s 24.86MB 1m32s 86.49MB
3日連続ログイン(修正後) 4.34s 6.97MB 3.28s 24.39MB

まとめ

Athenaを使用し、データ分析基盤で日常的に使用するような検索を試してみました。

以下は、触ってみた上での個人的な感想です。

  • リリース直後ということもあり、サービスとしての完成度は、まだこれからの印象。ドキュメントなどはまだ不足しているように感じられた。
  • 今後はAPIからの操作や、データインポート方法のバリエーションが増えることを期待したい。
  • 一方、S3にテキストファイルを置くだけで使用できる気軽さや、値段の安さは魅力的。
  • 性能も、定型レポートやアドホックな調査用途を想定すれば、十分実用的なもの。

KG SDKについて

KG SDKでは、データ分析基盤システムによるKPIレポートへの対応も行なっています。KG SDKの概要についてはこちら を、お問い合わせにつきましてはこちら をご覧下さい。

今回は、以前このブログで紹介した KMSについて、そのサーバサイドの構成を紹介したいと思います。

KMS の基本的なところ

汎用性

KMS は、チャットシステムを手軽に実現するための、SaaS型サービスとして企画されました。 つまり、1つの KMS のシステムを複数のゲームアプリが利用することが前提にありました。 そのため KMS は汎用性を常に意識して設計しています。

汎用的なシステムとして設計したため、KMS ではチャットに関するもの以外のデータ、例えばゲーム内のギルド情報などのデータは扱いません。 一方で NGワードなどのチャット機能に必要なデータは、利用ゲームごとに個別に保持し、必要に応じてゲームサーバから更新できるようにしています。

チャンネルを軸としたシステム

KMS はメッセージをやりとりする場であるチャンネルを軸としたチャットシステムです。 ユーザは特定のチャンネルに所属することで、そのチャンネルに対して発言することができるようになり、また同じチャンネルに所属する他のユーザが発したメッセージを受け取ることができます。 ユーザが発言したメッセージは、リアルタイムに他のユーザに転送されます。

KMS のチャンネル構成は、KMSを利用するゲームアプリ次第です。 例えば、自由に参加できるワールドチャットや、ギルドやチームに参加するユーザに限定したチャット、また 1 対 1 の個人チャットなどの用途が考えられます。

ユーザ認証

KMS はゲーム内で使うためのチャットシステムですので、クローズドなチャットです。 つまり、クライアントが KMS のサーバに接続する際は、ユーザ認証を実施する必要があります。 ユーザ認証に必要なデータは KMS 自体では生成せずに、ゲームサーバ側から受け取る形にしています。

KMS サーバの構成

KMS のサーバサイドの主要部分は、次の4種類のサーバで構成されています。

KMSの構成

UA サーバ

UA とは User Agent の略で、 クライアントアプリが接続する先のサーバです。 ユーザの発言をリアルタイムに他のユーザへ配信するために、クライアントアプリとサーバとの間の通信プロトコルとしては WebSocket を使っています。

構成としては、フロントエンドに nginx を置き、バックエンドには tornado を使っています(※)。 また tornado のスーパーバイザとして Circusを使っています。 nginx と Circus = tornado の間は Unix Domain Socket で接続しています。

※ フロントエンドサーバ、バックエンドサーバ
KLab では Web アプリケーションサーバを立てる際、フロントエンドとバックエンドの 2種類のサーバを動かします。 クライアントからの接続はフロントエンドのサーバが受け付け、バックエンドのサーバに proxy する構成です。

バックエンドのサーバは、Web アプリケーションの本体を動作させる環境になります。

フロントエンドのサーバの役割はいくつかあります。 例えば

  • アクセスログの標準化
    • tornado や uwsgi など、異なるバックエンドを併用する場合でも、アクセスログの形式を揃えられる
  • バックエンド処理の、タイムアウト管理
    • バックエンドの処理に時間がかかりすぎている場合に、フロントエンドがそのリクエストを強制的にエラーにすることで、接続リソースを開放する
  • クライアントリクエストのパーキング
    • 大量アクセス時に、全てのリクエストを並列に処理するとスラッシングなどの状態に陥るので、バックエンドの並列数は絞りつつ、クライアント接続をパーキングすることで混雑時のユーザエクスペリエンスの低下を緩和する

などがあります。

フロントエンドとバックエンドのサーバは、前記のようにその間を Unix Domain Socket で接続するので、同一のマシン上で動作させています。

API サーバ

ゲームサーバからのリクエストを受け付けるサーバです。 こちらはプロトコルとして HTTPS を使っています。 ユーザ認証用のデータなど、クライアントアプリが KMS を利用する上で必要なデータの追加更新削除の操作に加えて、ゲーム運用者がチャット上のデータを参照したりするためのリクエストも、この APIサーバが担当します。

構成としては、フロントエンドに nginx を置き、バックエンドには uwsgi を使っています。 こちらも、nginx と uwsgi の間は Unix Domain Socket で接続しています。

IRC サーバ

UA サーバ間及び、UAサーバと API サーバの間でのメッセージ配信用基盤として利用しています。

UA サーバも API サーバも、当然ながら複数台のサーバマシンを用意しています。 なので、 例えばサーバ S1 に接続している U1 ユーザが C チャンネルに対して発言した内容を、同じ C チャンネルに所属している S2 サーバ上の U2 ユーザに配信するためには、 S1 サーバと S2 サーバの間でメッセージを転送する必要があります。 そのための基盤として KMS では IRC サーバをチューニングした上で利用しています。

また、ユーザの発言を UA サーバ間で配送するためだけでなく、ゲームサーバがAPIサーバを通じてチャットへ送ってくるシステムメッセージの配信や、チャンネルへのユーザの入退室情報の配信などにも、IRCサーバを利用しています。

DB サーバ

ユーザ認証情報や、 チャンネルに対して発言されたメッセージのログや、ユーザが所属するチャンネル情報などを保管しています。 MySQL を利用しています。

KMS サーバアプリのバージョン管理

KMS は、複数のゲームアプリが利用するシステムです。 当然ながら KMS とゲームアプリの開発および運用は独立しています。 ですので KMS がバージョンアップしたからと言って、それに合わせてゲームアプリのバージョンアップを強制することはできません。

そのために KMS では UA サーバ・API サーバ共に、クライアントがサーバに接続する際に利用したい KMS バージョンを選択できるようにしています。 この仕組みにより KMS では下位互換性の無い改修を新規バージョンに施すことを容易にしました。 極端な話をすれば、利用するゲームアプリ専用バージョンの KMS を用意して同時に運用することも、可能になっています。

バージョン選択の仕組み

クライアント側からの、KMS サーバのバージョン選択は、次のような形で実現しています。

バージョン選択の仕組み(UAの場合)

まず、tornado(UAサーバ)と uwsgi(APIサーバ)上では、KMS を利用する全ゲームアプリが利用する予定のバージョンのサーバアプリを全て動かしています。 これらのサーバアプリと(フロントエンドの) nginx の間は Unix Domain Socket で接続しますが、この待ち受け用の Unix Domain Socket をサーバアプリのバージョンごとに用意しておきます。 つまり、バージョン1.1 とバージョン1.2 のサーバアプリが平行稼働していた場合、バージョン1.1用の待ち受けソケットと 1.2用の待ち受けソケットの2つの待ち受けソケットができます(※)。

※待ち受けソケット

Unix Domain Socket なので、待ち受けソケットとは、具体的にはファイルシステム上に配した socket ファイルのことです。

クライアントからの KMS サーバへの接続は、最初にフロントエンドの nginx が受け付けます。 その際にクライアントは利用したい KMS バージョンを、URL の中に埋め込みます。 nginx はその URL を見て、クライアントからの接続をどのバージョンのサーバアプリにプロキシーするかを判断し、適切な待ち受け用の Unix Domain Socket へ接続を回します。

この仕組みにより、KMS を利用するゲームアプリは、任意のタイミングで、利用する KMS のバージョンを変更することができます。 また KMS の運営側としては、利用するゲームアプリに気兼ねすること無く、新しいバージョンのサーバアプリをデプロイすることができます。

KMS の裏側の裏話

ここまでで説明した構成は、実装前の設計段階で固まっていて、大きな変更はありませんでした。 しかしながら、細かいところでは当然ながら初期の設計どおりではまずい箇所がいくつか出てきました。 また、負荷テストを実施してみると思わぬところがボトルネックになっていることが分かりました。

IRC サーバと UA の関係

最初期の構想では、UA の役割はクライアントと IRC ネットワークの間の橋渡し役として、ユーザ認証と NG ワードチェック、そして参加チャンネルの管理程度に留めたいと考えていました。 そのため、クライアント = UA 間の接続に 1 対 1 対応する UA = IRC 間の接続を作り、接続クライアントの数だけ IRC ネットワーク上にユーザを登録する構成を考えていました。

この方式では、UA を動かしている tornado が管理する接続の数は、1クライアントにつき2つの接続になります。 当然ながら tornado が管理する接続の数が増えるとそれだけリソースを消費するため、さばけるクライアントの上限が低くなってしまいます。 性能テストをしてみたところ、このクライアント接続に 1 対 1 対応する IRC 接続を作るという設計では、目標とする性能を実現できませんでした。

この問題に対処するために、当初の設計を変更して、UA と IRC の間の接続は 1つで済ませることにしました。 これに伴って、当初の設計では IRC の機能に依存していたクライアント間のメッセージの適切な配送機能を、UA 上にも実装する必要に迫られることになりました。

UA のチューニング

KMS では、クライアントと UA の間の接続は WebSocket を使った常時接続です。 前述したように、同時に取り扱う接続の数が多ければそれだけ tornado のリソースを消費することになります。 また UA の応答性はクライアントのユーザビリティに直結します。 そのため、UA チューニングには特に時間を費やしました。

その中で実施した施策の1つに、PyPy の導入があります。 PyPy は python インタプリタ実装の 1つで、 JIT を実装しているため標準のインタプリタである CPython よりも実行速度が期待できます。 PyPy の導入によりパフォーマンス向上の効果は一定ありましたが、それでもまだ目標とするパフォーマンスには到達しませんでした。

そのため、ひたすら cProfile プロファイリングを取得して、ボトルネックを解析し、対策を施しました。 その際に活躍したのが、 KCachegrind です。 KCachegrind を使った解析により、主なボトルネックとしては JSON の解析部分と tornado のマルチタスク処理部分であることが分かりました。

JSON

KMS はクライアントと UA の間や、(IRC を通じた) UA 同士、UA と API の間などの通信の全てにおいて JSON を使っています。 そのため JSON 処理の高速化が肝になりました。

一般的には JSON エンコードには json.dumps() を使うかと思いますが、その実装の実体は json.encoder.JSONEncoder クラスの encode() メソッドになります。 json パッケージでは json.dumps() 呼び出し時の JSONEncoder クラスの初期化コストを抑えるために、パッケージの初期化時にこのオブジェクトをキャッシュしますが、このオブジェクトキャッシュが使われるのは json.dumps() のデフォルト引数群に、デフォルト値以外の値が渡されない場合だけです。 KMS では separators オプションを指定したかったので、 独自に json.encoder.JSONEncoder クラスのオブジェクトをキャッシュしておくようにしました。

また、PyPy の JSON エンコードライブラリでは呼び出しオプションの与え方によって、その実装の一部分で C言語実装のコードが使われる場合と Python 実装が使われる場合があります(※)。 C言語実装が使われるか否かは、json.dumps() の引数 ensure_asciiTrue (default) か False かで変わります。 True であれば C言語実装が使われ、False であれば Python 実装が使われます。 当然ながら Python 実装版よりも、C言語版の方がパフォーマンスが高いので、コードを見直して必ず C言語版の実装が使われるようにしました。

※ JSON encoder の実装切替ポイント

前述の通り json.dumps() の実装の実体は json.encoder.JSONEncoder クラスにあります。 このクラスのコンストラクタ中で、 ensure_ascii 引数の値によって利用する実装を切り替えている部分(160行目)があります。

        if ensure_ascii:
            self.__encoder = raw_encode_basestring_ascii
        else:
            self.__encoder = raw_encode_basestring

この raw_encode_basestring_asciiraw_encode_basestring の実装の実体はそれぞれ同じファイルの49行目40行目にあるのですが、 raw_encode_basestring_ascii だけは更に536行目付近の

try:
    from _pypyjson import raw_encode_basestring_ascii
except ImportError:
    pass

このコードで、C言語実装をロードしています。

マルチタスク処理

Python はインタプリタの実装上、ネイティブ thread による並列実行のパフォーマンスがよくありません。 そのため tornado はノンプリエンプティブ型のマルチタスク処理システムを独自実装して、並列処理を実現しています。 このマルチタスク処理システムでは、ユーザコードから tornado への実行権限の譲り渡すタイミングを、ユーザコード上で指定する形になっています。 プロファイリングを取得してみると、このマルチタスク処理の部分がそれなりに重いことが分かりました。 その対策として、実行権限の譲渡のポイントを精査し、不要な実行権限の移譲が発生しないように対策しました。

最後に

KMS は SaaS として、1つのシステムで複数のゲームアプリ向けにチャットシステムを提供するために開発しました。 SaaS 型のシステムにした理由は、リアルタイムにチャットメッセージを配信するためのサーバシステムは、どうしても一般的なゲームのサーバシステムとは異なる部分が大きいため、利用案件の数だけチャットサーバシステムを立てていたのでは、運用が回らなくなると考えたからでした。

汎用的なシステムにしたために少し複雑になっている部分もありますが、KMS を利用するゲームアプリの自由度を損なわずに、それでいて運用性の高いシステムに仕上がりました。 この KMS の汎用的かつ柔軟な構造は、将来あるであろう、何か新しいことを KMS の上で実現したい、というリクエストに十分応えてくれることでしょう。

KG SDKに関するお問い合わせ

KLabでは、開発パートナー様と共同開発したゲームをKLabにてパブリッシング、プロモーションを行うというモデルを積極的に進めており、開発パートナー様にKG SDKの提供もしています。
パブリッシング事業につきましてはこちら
KG SDKに関するお問い合わせにつきましてはこちら
をご覧ください。

KLabでクライアント開発基本ライブラリの開発をしている鬼塚です。

今回はKLabで使用しているクライアント開発基本ライブラリの紹介をしたいと思います。
クライアント開発基本ライブラリとはUnityを用いたスマートフォンゲーム開発において共通して必要になる機能、及び実装ノウハウを集約したライブラリ群です。

kgsdk_img

KLabではUnityを用いたスマートフォンゲームのほとんどでこのクライアント開発基本ライブラリを導入しています。
現在リリース中のアプリでも使用されている為、UnityやiOS/Androidといった各PFのバージョンアップの対応やそれよって生じた不具合等の改修が日々が行われています。
また、Unity4、Unity5どちらにも対応しているので環境を問わず利用することができます。
クライアント開発基本ライブラリの概要についてはコチラの記事をご確認ください。

提供ライブラリ一覧

(1)ストア課金処理ライブラリ

App Storeの In-App Purchase、Google Play の In-App Billing を利用してアプリ内課金を行う際に必要な機能を提供するライブラリです。
iOS/Androidの各PF間の仕様の差異を吸収した作りとなっており商品一覧の取得、 商品の購入、レシート更新(iOS)、レジューム処理の機能を提供しています。

(2)アセット管理ライブラリ

アプリ本体の起動後に必要な画像などの追加データファイル(アセットと呼びます)のダウンロード及びそれらのアセットの読み込みにまつわる様々な問題を解消したライブラリです。
なお、バックグラウンドでのダウンロード機能やDownload機構及び、Object Pooling機構をモニタリングする機能なども提供しています。

(3)チャットシステム

ゲーム内でユーザ同士のチャット機能を提供する際に必要な機能をまとめたライブラリです。
テキストだけのやりとりだけでなく、画像を用いたスタンプや投稿した内容に対するリアクション機能なども提供しています。
チャットシステムの詳細についてはコチラの記事をご確認下さい。

(4)コアユーティリティ郡

ファイル管理、オンメモリキャッシュ、ロケール取得、タイムゾーン取得といった、アプリケーション開発で必要となる基本的な機能をまとめています。
クライアント開発基本ライブラリでは全てのライブラリでこのコアユーティリティ群が含まれています。

(5)通信ライブラリ

HTTPやHTTPS通信にまつわる機能をまとめたライブラリです。
Unity標準のWWWクラスの互換性のない仕様や挙動の吸収やリクエストキューイング、リクエストタイムアウト、自動リトライ等HTTP通信を行う上で必要な機能を提供しています。

(6)WebViewライブラリ

iOS/AndroidのWebView機能をOSによらず透過的に扱えるようにするライブラリです。
WebViewで標準的に利用可能な戻る、進む、リロード、アプリ実装との相互連携などももちろん利用可能で、その他キャッシュの無効化やUserAgentの指定、iOS8から導入されたWKWebViewにも対応しています。

(7)ローカライズテキスト管理ライブラリ

ローカライズテキストを管理するライブラリです。
アプリ内テキストのローカライズを行う為に必要な表示言語、表示するテキスト、表示するテキストを参照する為のIDを簡単に管理することができます。
テキストのローカライズと言語設定管理の2つの機能を提供しています。

(8)PFアチーブメント対応ライブラリ

Google Play Game Servicesと Game Centerのアチーブメント機能を透過的に扱えるようにするライブラリです。
アチーブメントの一覧や達成状況、アンロック等各PFが提供している機能を統一されたインターフェースで利用することができます。

(9)Bluetooth Low Enargyライブラリ

iOS/AndroidのBluetooth Low Energy機能をOSによらず透過的に扱えるようにするライブラリです。
Bluetooth LE通信に必要な機能が含まれおり、Peripheral機器としての利用、Central機器としての利用どちらにも対応しています。

通信ライブラリについてもう少し詳しく

上で紹介したクライアント開発基本ライブラリの中から今回は通信ライブラリにフォーカスしてどのような機能を提供しているかいくつか紹介させて頂きます。

特徴

先ずは通信ライブラリの特徴から。
通信ライブラリではUnity標準の通信クラス(UnityEngine::WWW)と.NET FrameworkのWebClientクラスの拡張クラスのどちらを利用するかを指定できるようになっています。
Unity標準のWWWクラスにはUnityのバージョンによっていくつかの互換性のない仕様や挙動があります。通信ライブラリではそれらの差異を吸収し、また機能拡張することで利用者はUnityのバージョンによる互換性のない仕様や挙動を気にすることなく開発に専念することができます。

RequestTypeによる処理の切り替え

通信ライブラリでは利用者が取得したいデータ形式に合わせて処理を切り替えています。
通信ライブラリで用意しているデータ形式はAPIレスポンス等に使用する為のテキストデータ、Unityで使用するアセットバンドル、テクスチャ用のアセットバンドルではない画像やバイナリファイル、汎用的に使用できるファイルダウンロード等があります。

リクエストキューイング

通信ライブラリではクライアントから投げられたリクエストは一度キューに積まれます。
もし、キューに何も無ければリクエストを開始しますが、前のリクエストが完了していない場合はそのリクエストが完了するまで待機します。こうすることで、複数のスレッドで通信を行った場合でもリクエストした順番に処理することができます。
一度に実行できるリクエストの上限はデフォルトでは5回に設定されていますが、もちろんこの回数等は利用者が自由に変更することができます。

リクエストタイムアウト

リクエストがタイムアウトする迄の時間を設定することができます。
モバイル通信環境で起こりがちな、電波の届かない場所での通信や、正常にレスポンスが返って来なかった場合にタイムアウトを設定することでユーザーを待たせずエラー処理を実施することが可能になります。
また、Unity標準のWWWクラスではこのリクエストタイムアウト処理が提供されていない為自前で実装する必要がありますが、通信ライブラリではリクエストキューイング同様自由にタイムアウト時間を設定することができます。

自動リトライ

通信失敗時にリクエストのリトライ回数を設定することができます。
一時的なネットワーク上のエラーでリクエストが失敗した場合に自動的にリトライされるのでライブラリ利用者のリトライ実装の負担と対アプリユーザへの通信失敗通知によるストレスを軽減することができます。

その他

その他にも通信ライブラリでは便利な機能を提供しています。
例えば、HTTPRequestを同期/非同期に処理するかを切り替えることができ、この機能はUnityEditor拡張実装において複雑になりがちなHTTP通信処理実装の手助けになります。
加えて、ダウンロード時の進捗状況を表示できたり、デバッグ用にダミーのレスポンスを受け取れるように設定できたりなどネットワークを扱う上で必要となる機能を提供しています。

最後に

このようにクライアント開発基本ライブラリではスマートフォンゲームの開発を行う上で必須となる機能を機能単位で利用者が必要な物を取捨選択して導入できるようになっています。
KLabでは、開発パートナー様と共同開発したゲームをKLabにてパブリッシング、プロモーションを行うというモデルを積極的に進めており、開発パートナー様にKG SDKの提供もしています。

パブリッシング事業につきましてはコチラ
KG SDKに関するお問い合わせにつきましてはコチラ
をご覧ください。

KLabでは、「エンジニアが使いやすい」をコンセプトに、ゲーム共通で必要となる機能をまとめたライブラリ群『KG SDK』を開発しています。

KG SDKを利用することでゲーム開発者はゲーム特有機能の開発のみに注力することができるため、開発スケジュールの短縮及び開発コストの削減並びに開発品質の向上が期待できます。

今回は、KG SDKが提供するチャットシステム『KLab Messaging Service(以下KMSと略称)』を紹介したいと思います。

1. KMSの特徴

(1) SaaS型サービス

KMSは、チャットシステムをアプリに容易に実装可能にするSaaS型サービスです。

サーバサイドの運用はKMS運用チームが担当しているため、KMS利用者はチャット機能の開発のみに集中することが出来ます。

KMSではサーバAPIとクライアントAPIを提供しており、ゲームサーバ及びゲームクライアントはそれぞれサーバAPI、クライアントAPIを利用してチャット機能を実装することになります。

サーバAPI

  • ゲームサーバが利用するAPI
  • HTTPSで通信
  • JSON形式のメッセージを送受信
  • マスターデータ管理等のDB操作機能を提供
  • チャット管理用機能を提供

クライアントAPI

  • ゲームクライアントが利用するAPI
  • WebSocketで通信
  • JSON形式のメッセージを送受信
  • 発言や発言ログ取得等のチャット機能を提供
  • 他ユーザの発言やチャンネル入退室情報等をリアルタイムで通知

※KG SDKでKMS用のクライアントライブラリ(Unityのみ対応)も提供しているため、アプリへの導入も容易となっています。
※KMSを利用するためには事前にKMS運用チームが発行するアプリコードとアプリキーを取得しておく必要があります。

KMS構成図

(2) 高い拡張性

自由なUI設計

チャットのUIを利用者側で自由に設計できます。
KMSを導入することでゲームの世界観を壊す心配はありません。

自由なチャンネル設計

KMSでは以下2種類のチャンネル作成機能を提供しています。

チャンネル 特徴
1対1のダイレクトチャット用チャンネル KMS側でチャンネル参加可能人数を2人までに制限しています。
そのためダイレクトチャット以外に使用できません。
ユーザ自由参加型チャンネル KMS側で何も制限していません。
参加可能ユーザなど制限を設ける場合は、利用者側で制限する必要があります。

上記の通り、ユーザ自由参加型チャンネルは利用者で自由に設計可能ですので

  • ワールドチャット用チャンネル
  • 同盟チャット用チャンネル
  • ゲームキャラクター情報交換用チャンネル
  • ステージ攻略情報交換用チャンネル

のように様々なチャンネルを作成することができます。

また

  • 全体お知らせ用チャンネル
  • 個人お知らせ用チャンネル

のようにチャット目的以外にもKMSを利用することができます。

メッセージデータのフォーマットを自由に設定可能

KMSではメッセージデータのフォーマットを制限していません。
平文だけでなく、JSONメッセージ等を登録しておくことで、豊富なチャット表現が可能となります。

例えば、発言内容の他に文字色やフォントを含める場合、以下のようにメッセージデータを登録できます。

{"text":"こんにちは!","color":"#FF0000","font":"font-name"}

(3) 発言のフィルタリング

フィルタリング用のワードを事前に登録しておくことで、発言をフィルタリングすることが出来ます。
また

  • 完全/部分一致
  • スペースと見なす文字
  • 伏字と見なす文字
  • 類似文字
  • 正規表現対応
  • 除外文字

などのフィルタリングオプションを用いることで、より高機能なフィルタリングを提供しています。

2. KMSを利用したチャット開始までの流れ

以下のステップでチャットを開始することができます。

(0)アプリコード・アプリキー作成

  • 事前準備として、アプリコード・アプリキーの発行をKMS運用チームに依頼します

(1)【ゲームサーバ】ユーザ作成

  • ユーザ名や認証キー等を設定してKMSユーザ(※)を作成します
  • 設定情報は『手順(4)KMSサーバにログイン』で使用するため、事前にゲームクライアントに送信しておく必要があります

※KMSユーザとは、KMS側が管理するユーザのことです。
ゲーム内ユーザとKMSユーザとを1対1で紐づけてください。

curl -i -H 'Content-type: application/json' -X POST -d \ '{"app_code":"アプリコード","app_key":"アプリキー", "person_id":"user_1", "auth_key":"auth_key", "user_name":"user_name" }' \ KMSサーバのURL/user/create_user

【ゲームサーバ】ユーザ作成

(2)【ゲームサーバ】チャンネル作成

  • チャンネルIDや初期参加ユーザ等を設定して、チャンネルを作成します

curl -i -H 'Content-type: application/json' -X POST -d \
'{"app_code":"アプリコード","app_key":"アプリキー",
"channel":"workd_channel",
"channel_name":"channel_name",
"channel_type":1,
"add_users":[
{"person_id":"user_1","invisible":0}]}
}' \
KMSサーバのURL/channel/create_channel

【ゲームサーバ】チャンネル作成

(3)【ゲームクライアント】KMSサーバに接続

  • KMSサーバのURLにWebSocketで接続します

wscat -c ws://KMSサーバのURL/

【ゲームクライアント】KMSサーバ接続

(4)【ゲームクライアント】KMSサーバにログイン

  • ユーザID等を設定したログイン用JSONメッセージを送信し、KMSサーバにログインします
{
"req": "login",
"app_code": "アプリコード",
"person_id": "user_1",
"auth_value": "auth_value"
}

【ゲームクライアント】KMSサーバログイン

(5)【ゲームクライアント】(2)で作成したチャンネルに発言

  • 発言チャンネルや発言内容を設定した発言用JSONメッセージを送信します

{
"req": "addtalk",
"channel": "world_channel",
"message_body": "こんにちは"
}

【ゲームクライアント】発言

3. その他KMSの提供機能

(1) スタンプ

以下2種類のスタンプ機能を提供しています。

  • ユーザ固有
  • 全ユーザ利用可能

なお、KMSが保持するのはスタンプのIDのみで、スタンプの本体画像等は利用者側で保持・管理する必要があります。

(2) チャット管理機能

チャット管理のため

  • 発言内容の全文検索機能
  • 発言内容のデータベース検索機能
  • ユーザ検索機能
  • チャンネル検索

などの機能を提供しています。

※KMSはGroongaという全文検索エンジンと連携しており、高速な全文検索を提供しています。
なお、Groongaについてはこちらを参照ください。

(3) NGワードランキング

NGワード発言数やNGワード発言ユーザを、ランキング形式で取得することが出来ます。
利用例としては

  • NGワード発言回数が多いユーザのアカウントを凍結する

などが挙げられます。

(4) ホットワードランキング機能

発言に含まれる単語をホットワードとして抜粋し、ランキング形式で取得することが出来ます。
利用例としては

  • イベント中に発言が多かったキャラクター名の集約
  • 発言回数が多い施策名の集約

などのユーザ分析が挙げられます。

なお、ホットワードの抽出には形態素解析エンジンMeCabを利用しており、解析結果が『名詞』の単語を登録しています。
解析結果は辞書情報に依存するため、利用者の要望通りのホットワードが抽出されない可能性があります。
そこで、KMSでは利用者が任意でカスタマイズできるユーザ辞書設定機能を併せて提供しています。
例えば、『KLabの天クラってゲームおもしろいよー』と発言した場合、デフォルト辞書のみだと『天』『クラ』『ゲーム』がホットワードとして抽出されますが、ユーザ辞書として『天クラ』を登録しておくことで『天クラ』『ゲーム』がホットワードとして抽出されるようになります。

MeCabについてはこちらを参照ください。

(5) リアクション機能

チャットにリアクション機能を追加することが出来ます。
画像データ等のリアクションに関する設定情報は利用者側で自由に設定できるため、facebookの『いいね』やslackの『リアクション』機能など、任意のリアクション機能を実現可能です。

(6) 翻訳サービス

発言済みのトークを各種言語に翻訳することが出来ます。
なお、当翻訳機能はMicrosoft Translator等の外部APIを利用して翻訳しており、外部APIの契約は利用者側で実施する必要があります。
※現在KMSで利用できる外部翻訳APIはMicrosoft Translatorのみです。
 Microsoft Translatorについてはこちらは参照ください。

(7) ファイルアップロード機能

画像データ等を任意のサーバやAmazon S3上にアップロードすることが出来ます。
ゲームのキャプチャ画面アップロードをチャットに組み込むことで、チャットをより活性化させる事ができます。
なお、Amazon S3の契約は利用者側で実施する必要があります。
 

4. 2016年6月時点のKLabにおけるKMS利用案件

利用案件 公式サイト
BLEACH Brave Souls https://www.bleach-bravesouls.com/index.php
パズルワンダーランド https://www.puzlan.com/index.php
Age of Empires: World Domination https://www.aoewd.com/ja/index.php

KG SDKに関するお問い合わせ

KLabでは、開発パートナー様と共同開発したゲームをKLabにてパブリッシング、プロモーションを行うというモデルを積極的に進めており、開発パートナー様にKG SDKの提供もしています。
パブリッシング事業につきましてはこちら
KG SDKに関するお問い合わせにつきましてはこちら
をご覧ください。

分析基盤チームの高田です。

私のチームでは、各種ゲームのデータを集積するデータ分析システムの開発・運用を行なっています。

この記事では、モバイルオンラインゲームのクリティカルなポイントのひとつである仮想通貨の管理について、開発・運用という立場から気をつけていることを紹介したいと思います。

仮想通貨の扱いは、最終的には、法律・会計上の要請から決まってくるものだったりします。このため、開発者が単独で決められることは決して多くないです。しかし、ゲームの運用チームと会計部門の間に入ってルール作りを進めていくのは、経験上開発チーム主導で行なう方がうまくいくように思います。

重要なのは、「さまざまなユースケースを先に想定し、ルールを決めさせる」「システムの都合と、各部門の要件を整理する」というあたりです。要するに、「こういうケースがありえるんですが、この場合会計部門としてはどういうデータが必要ですか?」「運用チームはこのデータが見れれば問題ないでしょうか?」というのをどんどん聞いていくことが一番重要な仕事になります。

注意事項:

  • この記事は開発者としての立場で書かれたものであり、仮想通貨に関する法律上の判断については責任を負いかねますのでご注意ください。

仮想通貨とは?

ここで仮想通貨と呼ぶのは、(1)ユーザーが対価を支払って購入し、(2)アイテムとの交換、行動力回復、ガチャなどに使用できるもののことです。

「仮想通貨」と呼ぶとあまりなじみがないかもしれませんが、モバイルオンラインゲームでは、特別なアイテム(「石」「ジェム」など)を販売し、ユーザーはそれらを使用することで体力回復やガチャなどの機能を使用するという仕組みをとるものが多いです。これらのアイテムも仮想通貨と呼ばれます。なお、この記事では、iOS、Googleのネイティブアプリの事例を想定しています。

日本では、利用者保護の観点から、仮想通貨の扱いは資金決済法という法律で厳しく制限されています。また、モバイルオンラインゲームの売上は仮想通貨の発行時ではなく、消費時に計上することが多いため、仮想通貨の発行・消費ログは会計上も重要な数値となります。

恐怖の仮想通貨

KLabの場合、各ゲームのログを、共通のデータ分析システムに集約し、そこから会計部門、運用チーム、分析者などに各種データを提供するという形をとっています(図参照)。仮想通貨に関するデータも、このデータ分析システムに集約されます。私のチームが担当しているのは、このデータ分析システムの開発・運用の部分です。

virtual-coin

仮想通貨の管理は、以下のような理由で運用難易度の高いシステムになりがちです。

  1. 会計などに直結するクリティカルな部分であり、データの間違いなどが致命的な問題になりえる。
  2. ゲームごとに仕様やデータの格納方法もかなり異なってくる。
  3. 会計部門、運用チーム、データ分析に関わる部署など、関係者が多く、それぞれの要件も複雑である。

詳細はあとで紹介しますが、細かい要件がたくさんあり、「こんなに考えることがたくさんあると思わなかった」というくらい複雑になっていきます。

また、多くの関係者が存在するため、うまく調整ができなければ悲惨な事故にもつながりかねません。発生しうる事故の具体例をあげてみましょう。取りまとめるチームからすると、悪夢のような状況です。

  • 会計部門の要件がゲームの開発チームに伝わっていなかった。必要なログがデータベースにも残っておらず管理上必要なデータを用意できない。
  • 大量のデバッグ用ポイントを発行してしまって、本来記録すべき仮想通貨の発行と区別できない。
  • 新しい販売アイテムが法務や会計部門のチェックなしに追加されてしまった。本来仮想通貨として管理すべきものだが、管理に必要な機能が実装されていない。

こうした事故を防ぐためには、各方面からの要望を整理し、ルールを決めて運用していくことが重要です。

リリース時のチェックリスト

仮想通貨の仕様は各ゲームごとにかなり異なります。新規ゲームのリリース時などによく問題になるポイントを以下にあげてみます。リリース前にこの種のチェックポイントを確認していき、とにかく考慮漏れや想定外の事態を発生させないことが重要です。開発段階で関係者を集めてレビューすることが望ましいでしょう。

  • (1)仮想通貨を無償で付与することはあるか?
  • (2)どの仮想通貨から消費するか?
  • (3)通貨単位はどうなっているか?
  • (4)仮想通貨の種類は何種類あるか?
  • (5)引き継ぎ時のプラットフォームの扱い
  • (6)時間の扱い
  • (7)補填・デバッグの扱い

(1)仮想通貨を無償で付与することはあるか?

多くのゲームでは、仮想通貨相当のアイテム(「石」「ジェム」)を無償でも付与します。こうした事例では、有償で発行した仮想通貨と無償で発行したものを区別し、仮想通貨として管理するのは有償発行のものに限定するといったポリシーを採用する場合も多いでしょう。会計上売上として計上されるのは、有償発行のものに限定されますし、本来仮想通貨として管理すべきものは、ユーザーが対価を支払って購入したものに限定されるからです。

(2)どの仮想通貨から消費するか?

無償発行のものと有償発行の仮想通貨の両方がある場合、消費される順序をルールとして定めなければなりません。代表的なものは、以下の3パターンでしょう。ゲーム内でどのパターンを利用するのかは関係者間でよく確認しておきましょう。

  1. 有償発行分から消費する
  2. 無償発行分から消費する
  3. 有償無償問わず、先に発行した分から消費する。

(3)通貨単位はどうなっているか?

海外向けに提供している場合など、価格は日本円で統一されているとはかぎりません。また、iOSの場合、価格の設定にはApple Tierという独自の単位が使用されます。いずれにしても、どのような単位を使用して集計するのか、各チームで合意しておかなければなりません。

(4)仮想通貨の種類は何種類あるか?

仮想通貨は一種類とはかぎりません。ガチャチケットなどを販売する場合、そちらも仮想通貨として扱わなければならない可能性もあります。

(5)引き継ぎ時のプラットフォームの扱い

iOS端末からAndroid端末に変更した際など、異なるプラットフォーム間でユーザーデータを引き継ぐことがあります。引き継ぎに関しては、ゲームのシステム上のルールとして、仮想通貨も引き継ぐことを認める場合と、引き継ぎを認めない場合があります。仮想通貨の引き継ぎを許容する場合、消費された仮想通貨がどのプラットフォームで発行されたものかを取得する必要が生じるかもしれません。

(6)時間の扱い

時間の定義も問題になることがあります。海外向けに提供しているゲームの場合、タイムゾーンに気をつけなければならないのはもちろんですし、国内限定でも、取引日時を「ユーザーが端末上で操作した日時」とするのか「サーバー上でDBにレコードが作成された日時」とするのかが問題になることがあります。端末上の時間に合わせていると、過去の取引データが遅れて送信されてくることがあるからです。

例えば以下のようなケースを考えてみてください。

  1. 10/01 23:00 - ユーザーが仮想通貨を購入。通信エラーのため、データの送信がしばらく延期される。
  2. 10/02 01:00 - 仮想通貨集計バッチが10/01のデータを集計。
  3. 10/02 02:00 - 1のデータが再送されてくる。ところが10/01のデータはすでに集計済みである。

ここで再送されたデータを10/01のログとして集計しようとすると、集計から漏れてしまいます。

(7)補填・デバッグの扱い

障害時などに、通常の購入処理とは異なる形でユーザーに仮想通貨を付与することがあります。また運用中、購入処理などのデバッグが必要になる場面も想定されます。こうしたユースケースでは、デバッグのためにテスト用の仮想通貨を発行することがありえます。

こうしたイレギュラーなデータがログから漏れないよう、あらかじめ付与方法などについてもルールを決めておくことが必要です。

集計するログの種類

以下、KLabで仮想通貨管理のために集計しているログを参考のためにあげてみます。データの詳細はゲームごとの仕様によっても異なりますが、原則的には全ゲームで統一のフォーマットを使用して集計しています。

A. 仮想通貨発行ログ

仮想通貨の発行ログです。発行した個数や販売した仮想通貨商品(仮想通貨セット)のIDなどを集計します。モバイルゲームでは、App Store、Google Playストアなどのプラットフォームを通じて、仮想通貨をセット販売するので、セット単位で集計することになります。

項目 詳細
集計するタイミング デイリー
集計内容 ユーザーID、発行額、価格、通貨(JPY/USDなど)、日時、仮想通貨商品ID、プラットフォーム、仮想通貨の種類

B. 用途別仮想通貨消費ログ

消費用途別の仮想通貨消費ログです。発行した仮想通貨が、いつ、何の目的で、いくつ消費されたのかを集計します。

項目 詳細
集計するタイミング デイリー
集計内容 ユーザーID、消費額、日時、仮想通貨の種類、消費用途

C. セット別仮想通貨消費ログ

用途別の消費ログと似ていますが、発行時の仮想通貨商品ID(仮想通貨セット)別に消費を追ったものです。なぜこれが必要かというと、仮想通貨1個あたりの金額がセットごとに異なる場合があるからです。多くの場合、まとめ買いの方が得になるように商品価格が設定されています。また、途中で値段の変更を行なった場合などにも、この種のログが必要になります。

項目 詳細
集計するタイミング デイリー
集計内容 ユーザーID、消費額、日時、仮想通貨の種類、仮想通貨商品ID、発行時プラットフォーム

D. 仮想通貨残高

ここまでのログが正確に集計できていれば、そこから未使用の残高を計算することもできるはずです。基本的には、残高は、発行額合計 - 消費額合計になるはずです。

KLabでは、チェックのために、月に1回の頻度で、未使用仮想通貨の残高と上記のログから計算した残高を付き合わせています。

項目 詳細
集計するタイミング マンスリー
集計内容 仮想通貨個数、日時、仮想通貨の種類、発行時プラットフォーム

まとめ

以上、モバイルオンラインゲームの仮想通貨管理に関して、KLabで気をつけていることを紹介してきました。仮想通貨の扱いは、健全なゲームの運営やユーザーの利益保護という観点からきわめて重要なポイントになってきます。参考になれば幸いです。

KG SDKについて

KG SDKでは、KLabのノウハウを活かし、仮想通貨管理ライブラリやKPIレポートへの対応も行なっています。KG SDKの概要についてはこちら を、お問い合わせにつきましてはこちら をご覧下さい。

↑このページのトップヘ