ソーシャルゲームの価値を上げるログデータのつくりかた

はじめに

現在データ分析基盤の再構築を担当している、サーバーサイドエンジニアの小川詩織です。これまで私は4つのソーシャルゲームの新規開発・運用を経験してきました。そこでの知見と考察をまとめます。

ログデータは、調査などに必要なただの履歴という立場に置かれがちです。ですが、作業工数を大幅カットしたり、定量的な効果測定や判断ができるなど、適切なログ設計と活用により利益に繋がる施策の指針にすることも出来るものです。

ログには、コンテンツ内容により色々な種類や設計があるため、全てに共通する最適解はありません。ですが設計の指針となるべき事柄はあるので、ログの種類や活用例、設計の仕方、工夫などについて入門的な内容について一通り触れていきます。その中でログの可能性も一緒に感じていただきたいと考えています。

対象とするログと、全体像

今回はこれまでの経験が最活かせるソーシャルゲームのログのみを想定した内容になります。

また、ゲームのログデータは大きく分類すると、

  • ゲームのログ内容を載せたアプリケーションログ
  • CPU使用率やメモリ、サーバのエラーなどを載せたシステムログ

の2つがありますが、今回はアプリケーションログ(※以下、ログ)の話です。

また、エラーログについての記事は多いですが、今回の内容ではエラーログだけではなくユーザの行動ログについても多く触れていきます。

以下、ゲームから出力するデータの全体像を載せておきます。

スクリーンショット 2018-05-04 14.43.30.png (90.2 kB)

図1. ゲームデータの全体像

ログの背景と必要性

ログは開発段階でのバグやチューニング、またリリースしてからも問い合わせやバグ発見の大きな手掛かり、あるいは足掛かりにもなるものです。これが欠落していたりする場合、

  • バグに気づけない
  • バグの原因の特定ができない
  • 効果的な施策が立てられない
  • 補填ができない
  • 問い合わせに対する調査ができない

などのゲームにとっては致命的な結果を生む事になりかねません。正しくログが出力、保持、参照できる事はゲームを開発・運用する上で必要な事です。

そこでまずログの設計や出力する情報が重要になってくるのですが、これまでの経験上、ログの設計や出力を実装するのは主にエンジニアですが、データを見る立場がエンジニア以外にも居る事がほとんどで、そこで認識のズレや、データの欠損が起こりがちです。例えばエンジニアは不具合の調査は行うが、決済などのデータや、データ分析にはほとんど関与しないため、ノウハウがなく出力するべきデータが欠損する、などがそれにあたります。

ログの要件はゲーム性などにもよって異なるため、体系的に纏められていたり定義されている事がどうしても少なく、エンジニアの経験や、会社の文化に則りログ設計を行っている事が多いのではないかと思います。なので今回は改めて、ログにはどういった活用ができるか、おおよそ何がどんな形で必要であるのかなど、基本的な部分ではありますが、まとめていきます。

現在のシステム構成

前提として、アプリボットのとあるコンテンツのシステム構成例を載せておきます。

実際はもっと複雑ですが、システムログは主にElasticSearchに出力してKibana、アプリケーションログは主にストリーミングでDWHであるRedshiftに集約して、BIツールであるTableauやre:dashで参照しています。これから載せるサンプルは、Redshiftへ出力する想定でのログを想定して記載しています。今回は触れませんが、システム構成によってファクトテーブルとディメンションテーブルの結合の仕方など適宜最適化して出力してください。

Applibotのメイン構成(for てっくぼっと).png (65.8 kB)

図2. アプリボットのシステム構成例

ログの種類と概要

文献などによって分類方法や命名が異なり、混乱する方もいると思いますので、前提として先ほど載せた出力データ全体像の各出項目に関して少しだけ説明を行っておきます。大まかな概要は先ほどの図を参照してください。

業務データ

こちらは普段、ログとは呼ばず、ユーザの最新状態を保持するデータや、イベントなどのマスタデータを指します。今回の内容とは直接的に関わりはありませんが、後述するログの調査や、データ分析などで、ログデータと結合して扱いやすいデータとする事が多々あるので、ログデータの出力内容もある程度は考慮して設計しておくと後々楽になります。

ログデータ

アプリケーションログとシステムログに関しては先述の通りですが、アプリケーションログの中でも大きく分けてアクションログと、アクセスログが存在します。

例えば「ユーザxxさんがアイテムAを1つ使用した」という例で考えてみます。

アクションログ

ユーザの行動や、主にデータベースの更新が行われた場合に、その更新内容を履歴として残しておくものです。上記の行動が行われた場合には、アイテムを使うAPIが叩かれ、その処理の後にユーザのアイテム使用履歴として記録されます。

// アイテム使用履歴ログ例
{
    "iUserItem": {
        "userId": 123456,
        "itemId": 2,
        "count": 2, // アイテム使用後のユーザアイテム所持残数
        "addCount": -1, // アイテム増減数(取得は正の値、消費は負の値)
        "updateDatetime": "2017-11-02 17:18:16", // データ更新日時
        "insertDatetime": "2017-11-02 17:18:16", // データ挿入日時
        "logFunctionType": 22 // ログ機能タイプ(後述)
    }
} 

途中でエラーが起こり処理はされていないのに、先に成功ログを出力してしまって間違ったログが残っている、などが極力起こってしまってはいけないので、信頼のあるデータである事を担保できる様、出力タイミングやトランザクション処理には気を遣う必要があります。更新削除はされず、ただ蓄積されていきます。精度としては1件ずつロールバックしていくと過去の業務データまで戻せるような設計・内容にしておくことが望ましいです。また、調査などのために時間やマスタデータとの紐付けが出来る要素もしっかり出力するようにします。

アクセスログ

こちらは、ユーザの端末からサーバまでのHTTPリクエストやHTTPレスポンスの内容を保存しておく履歴です。どのAPIが、どのユーザにどんな内容で叩かれたか、どんな値を返したか、などを各リクエスト、レスポンスに対して1レコードずつ残しておくものです。例えばリクエストログであれば、以下のような形の内容だと扱いやすいです。

// ユーザテーブルのRequestログ例
2018 - 03 - 14 18: 36: 59 {
    "apiResponse": {
        "uuid": "bc86ae54-5cdd-4c83-bd4aa-a81e616f176c",  // UUID(後述)
        "responseCommon": {
            // 全リクエスト共通部分
            "common": {
                "status": 0,
                "titleTextId": null,
                "messageTextId": null,
                "token": "xxxx",
                "appVersionStatusType": 1,
                "responseDatetime": "2018-03-14 18:36:59",
                "achievedMissionInfoList": [],
                "maintenanceInfo": null,
            },
            "tableName": "user_data",  // 更新するテーブル名
            // 更新するデータ内容
            "tableDataList": [{
                "userId": 1234,
                "name": "マヨさん",
                "birthday": null,
                "registrationDatetime": "2018-03-14 18:36:30",
                "languageId": 2,
            }]
        }
    }
}

ゲームサーバで処理を行う前の生データなので、そもそも渡ってきた値が想定外のものである、など調査には欠かせないもので、運用だけでなく、開発中にもとても役に立つログです。これも基本的に更新削除はなく、蓄積されていきます。

基本的なものを挙げましたが、ゲームの仕組みやゲーム性によっては出力しないログやデータがある場合はあります。何をどんな形で出力するのが適切かどうかは、これから記載する活用方法などを考慮して選び、設計する必要があります。

ログの活用方法

経験上、ログのおおよそ代表的な活用方法は以下の通りです。

お客様からのお問い合わせ調査

運用中にお客様からのお問い合わせの調査を行います。主にアクションログからアイテムの使用履歴や、購入履歴などを調べますが、ここでの調査からバグが発覚する事も少なくありません。業務データだけではなく、時系列に並べられたアクションログが必要になります。アクションログで記載の通り、データの出力されるタイミングが適切である事がとても大切です。エンジニアが調査を行う事も多いですが、カスタマーサポートの方が調べられる様に仕組みを整備する事も有効なので、そういった工夫ができる様、設計面を意識しなくて良い、全面的に信頼のできるデータである事を担保してあげる事が重要です。

バグや、ユーザの不正、バグの影響範囲調査

バグなどに気づくタイミングは様々ですが、APIのアクセスログや、APIが叩かれた順番、内容などをアクションログなどと照らし合わせて調査を行います。ユーザIDやイベントIDなどで業務データと照らし合わせて整合性が取れているかどうかや、1ユーザの動きを追っていったり、APIだけでなくOSや端末情報と結びつけられる設計になっている事も調査が楽になります。また、時間やイベントID、その時間にアクセスした人などのアクセスログからバグに対する影響範囲を調べて補填作業を行う事もあるので、時間なども適切にログに含まれている事が必要です。

データ分析

ここは一番悩ましく知識や経験の必要なログだと思います。しかしデータ分析の効果は絶大です。データ分析に関してはそれだけで話が大きくなりすぎてしまうので、要点のみを挙げますが、そもそもデータ分析の目的は大きく分けて以下の様なものが挙げられます。

  • 目標指標:目標に対しての効果分析や計画立てなどの指標を定義し、コンテンツの成長を助ける
  • 予測:これまでの傾向や、要素から未来の動きや情報を予測する
  • サービス改善:ユーザの行動や動機傾向などの分析し、コンテンツの改善を図る

データは情報自体ではなく、分析して価値ある情報に変える事にとても価値があり、その分析によって大きな利益や改善を生み出す事が出来ると言えます。データを基盤とした判断が出来れば、施策の効果測定や、根拠のある検証、次回への改善、利益の最大化なども定量的に行うことができます。まずはどんな分析を行うか、必要なデータのための要件定義をしっかり行うことが一番大切です。その分析にとってもログの設計によって分析に向く設計や、適さない設計が存在します。

分析以外の活用方法では主に調査だったので、RDBなどでの行指向データが活躍するものでしたが、データ分析は主に集計を行うため、列指向のデータの方が扱いやすくなります。ETLなどで適宜データ形式を変えるなど、データの持ち方を工夫する事が有効です。ログ出力のコストや、ストレージ代、管理コストなども実際にはあるため、アクションログをデータ分析に使う事が多いと思いますが、特別よく使うデータや、DAUなどのデータ出力は、データ分析用のログに最適化してアクションログとは別途出力する事もあります。アクションログに対しては出力されたデータに対して前処理を行い、集計する事がほとんどですが、そもそもデータが欠損している場合は集計が難しいため、ここに関してはデータアナリストとエンジニア、集計されたデータを見るプランナーなどで必要なデータを洗い出し、適切にログを設計する必要があります。

また、業務データの説明で少し触れましたが、分析内でイベントIDとイベント期間との繋がりなど、業務データのマスタやユーザデータと結合して分析する事が多々あります。マスタなどは正規化される事が多いですが、アクションログを分析に用いる場合などを考慮すると、マスタデータとの紐付けができる様、ある程度冗長にイベントIDなども持たせておく事が大切なので、アクションログの設計を考慮したり、分析・取得したいデータによってマスタデータの持ち方を工夫する事も考えるべきです。

ログ設計に必要なこと

では実際に使いやすいログを設計するにはどうしたら良いのか、自分なりに工夫するべき点や、設計手順を説明していきます。

ログを出力する形式や、分け方などはシステム設計にもよって最適な形があるので、全てに共通する正解はありません。データレイクやデータマートなどによっても粒度や分割方法、構成を考慮してください。

設計に関しては、先述の通り、活用方法は多々あり、かつデータ分析に関しては考慮すべき点が多く挙げられます。個人的には調査系だけではなく、データ分析も少し触れさせて貰える機会が多く、それぞれの活用法に関して苦労した点がいくつもありました。そこでのログの問題はいくつもありますが、よく体験した内容は以下の様な事が挙げられます。

  • そもそもデータが足りず、調査や分析ができない
  • 正しいデータが出力されていない
  • データの出し方が悪く、結合などに無駄な手間が掛かる

データが欠損していたり、出力したいデータが上手く保存されていなかった場合、これは一番の痛手です。運用が始まって、失ったデータは取り戻す事ができません。特に、リリース初期のデータは今後の方針を決める大きな材料にもなるため、取りこぼす事はしたくないものです。また、実際にデータを使った検証や分析を行わずに出力していた場合、もしそれが間違ったデータが出力されていたとしても気づく事ができません。必要になって参照した時に、使えないデータである事が判明します。これもデータの欠損と同じです。また、データは出力されていても、いざ使おうと思った時に、いくつも結合したり工夫して一時テーブルを作るなどして使うまでに大きな工数が生まれる場合があります。余計な事をすればするほど、人的ミスも増え、時間も掛かります。

これらの問題が起こる理由のほとんどが、使い方を考慮した設計になっていないためです。データは検索され、集計され、出力、または可視化されて初めて利用される形になります。新規開発や新機能の実装時には、KPIや目的を設計するものですが、それに合わせてログにまでそれを落とし込む必要があります。ログを設計する前に、最低限明確にしておくべき項目を挙げておきます。

  • 何が主体のゲームなのか
  • データから追うべきKPI
  • アクションログとアクセスログの各目的と使い方をはっきりさせる
  • 想定されるお問い合わせや調査の内容
  • 最終的に出力される形 (できればクエリや、可視化されたデータなどの構成)
  • プライバシーに注意 (個人情報は載せない)

これは時にはエンジニアだけではなく、データアナリストや、プランナー、ディレクター達と擦り合わせておく必要があります。特に、主要なKPIや、課金周りの調査ログなど、取りこぼしが許されない項目だけでも必ず洗い出しておくべきです。実際にログを出力したら、想定されるケースのクエリや問い合わせで調査や集計を行ってみる事が確実で、望ましいです。

データ分析での集計に関しては、出力されたログに関して前処理を行う場合がほとんどだと思うので、なかなか想定は難しいですが、アクションログがおおよそ丁寧に出力されていれば後からでも整形して使う事は可能だと思うので、とにかくまずはアクションログはきちっと出すと良いと思います。データの取りこぼしは取り戻せませんが、出力したデータはいつでも破棄や、出力しない様にする事はできます。

ログ設計の工夫と実例

基本的な部分を抑えた上で、どの様なログがあると調査やデータ活用がしやすいかを少しだけご紹介しておきます。実際のデータは、そのゲームに最適化したものにするのが一番良いので、参考程度にしてください。

アクセスログの出し方

私が直近で所属していたコンテンツでは、アクセスログをAPIアクセスログと、リクエストログ、レスポンスログに分けて出力していました。以下の様な内容です。リクエストログは先程記載した例があるのと、レスポンスログについても大枠は同じですので割愛します。APIアクセスログについてはどのAPIがいつ叩かれたのかだけを記録するもので、これによってユーザIDやAPI名でgrepなど出来て色々と便利です。

// APIアクセスログ例
{
    "apiAccess": {
        "uuid": "bc86ae54-5cdd-4c83-bd43-a81e616f176c",  // 同一トランザクション内での各種ログと紐付けるUUID(後述)
        "userId": 123456,
        "requestApi": "/user/info/get",  // API名
        "statusCode": 0,  // 通信のステータス
        "accessDatetime": "2018-03-14 18:36:59" // アクセス日時
    }
}

目視やざっと調査する上でAPIログはシンプルにユーザ動向が終えるため、APIベースで追う事が便利で、より詳細な内容について調査する場合はAPIログに紐付くリクエスト、レスポンスログを追う様にしていました。

また、本番ではリクエストとレスポンスログを全て出力するのはデータ量的にも大変だった事と、時間が経ったデータで調査を行う事はほぼなかったため、本番環境ではエラーを起こした内容のみを出力する様にしたり、一部のログのみ出力して数日置きに破棄する様な工夫をしていました。

UUIDによるログ同士の紐付け

先ほどのAPIログとリクエスト・レスポンスログの紐付けもそうなのですが、アクションログにおいても、一つのAPI(同一のトランザクション内)でも複数のアクションログが記録される事が多いです。例えばミッション達成の例で言えば、「APIログ」「リクエストログ」「ミッション完了ログ」「ミッションの報酬アイテム付与ログ」「レスポンスログ」の様に、複数のテーブルと履歴に追加と更新が走ります。これらがユーザのIDと時間のみでしか関連付けられてない場合、調査が困難でロールバック等も不可能です。そこで、サーバ側でリクエストが来た時点でUUIDを発行し、その1リクエスト内の上記のログに全て同一のUUIDを付加するなどの工夫を行いました。これにより、APIログから、実際にそのリクエスト中にどの様なテーブルにどの様な変更が行われたかを容易に検索できるようになりました。

また、その更新がクエストによるものか、購入によるものか、アイテム消費によるものか大体の機能毎に分けてlogFunctionType(ログ機能タイプ)という値を定義して付加することで機能に絞った調査も簡単にすることにしていました。

// ミッション完了のAPIアクセスログ
{
    "apiAccess": {
        "uuid": "6451ef69-7da7-4de5-8ba3-22b19asd48",  
        "userId": 123456,
        "requestApi": "/mission/reward/clear",
        "statusCode": 0,
        "accessDatetime": "2018-05-25 16:37:42"
    }
}
// ミッション完了ログ
{
    "UserMissionAchievement": {
        "uuid": "6451ef69-7da7-4de5-8ba3-22b19asd48",
        "userId": 123456,
        "missionId": 111,  
        “missionStatus": 3,  // ミッション状況のEnum 3:完了
        "updateDatetime": "2018-05-25 16:37:42",
        "insertDatetime": "2018-05-25 16:37:42"
    }
}
// 報酬のアイテム付与ログ
{
    "UserItem": {
        "uuid": "6451ef69-7da7-4de5-8ba3-22b19asd48",
        "userId": 123456,
        "itemId": 4,
        "count": 1,  // 付与後の所持数
        "addCount": 1,  // 付与数
        "updateDatetime": "2018-05-25 16:44:54",
        "insertDatetime": "2018-05-25 16:44:54",
        "logFunctionType": 3 // ログ機能タイプ 3:ミッション系
    }
}

(リクエストとレスポンスログは略)

全てに同じUUIDが付帯されていることと、アイテム付与ログでは何によって付与されたアイテムなのか、logFunctionTypeだけでひと目で分かるようになっています。logFunctionTypeはデータ分析の観点でもとても便利です。

ログのリファレンス

これそれとなかなか忘れがちなのが、アクションログのリファレンスを残す事です。これは設計したエンジニアが、どの要素をどんな型でどんな意味で出力しているかの一覧リストを作成するのが良いと思います。アナリストの方が分析に使う場合にどこのデータがどのテーブルに出力されているかを把握しやすくするためと、エンジニアが一見してログのクオリティを担保しやすくなります。ログの設計はゲーム開発の最後の工程に追いやられがちですが、ここはしっかりやっておき、追加があればリファレンスもきちんと管理する事で、後々に起こる可能性のあるアーキテクチャの更新や、データ分析、引き継ぎなどで大きく役に立ちます。逆にこれがない場合は、どこにどんなログが出力されているかも分からないため、最悪、ログを出力しているのに使われない事すら有り得ます。

ログの出力の仕方や調査によって、上記の方法や、出力するデータにマスタの値を持たせたりと、少なくともよく使うデータに関しては出力時に最適化を行っておくと、後々がとても便利です。また、もし不便さを感じるログがあったら見直してみることをオススメします。

参考:ログの可視化 re:dash

ログの内容とは直接的には関係ありませんが、お問い合わせ調査の部分で少し触れた、便利なデータ可視化ツールとしてre:dashを紹介しておきたいと思います。データは出力されたデータをいかに使うか、もとても重要な要素です。上手くデータを活用できなければ設計もデータも意味を成しません。re:dashはOSS(有料のフルマネージド版も有)のダッシュボードツールです。エンジニア的に嬉しい部分はOSSである事の他にも、クエリが使えるところです。また最近、複数のデータソースからのデータ結合も可能になりました。 また、導入も楽で、スケジューリング機能や、一度取得したデータはしばらくはキャッシュしてくれるため表示も早く、エンジニア以外にも安心して使ってもらう事ができます。追加は簡単で、大まかに以下の流れになります。

スクリーンショット 2018-05-25 16.55.22.png (19.1 kB)

re:dashの活用例

実際にre:dashの活用方法をいくつかご紹介します。

例1:お問い合わせ対応

ログデータを使ったお問い合わせ調査はエンジニアが行う事も多いと思いますが、基本的にはカスタマーサポート(※以下、CS)の方が受けて対応してくれるようなところも多いと思います。これまでのコンテンツの中でも、エンジニアが調べるところもあれば、自前で実装した管理画面にユーザIDだけ入力すると履歴が出てエンジニア以外でも調査ができる、などの仕組みを作ってCSの方が対応するところもありました。ただ、どちらもとてもエンジニア工数的には調査も実装も大変です。そこでアプリボットの一部のコンテンツではre:dashを導入しました。

パラメータなどの入力もさせる事ができるため、ユーザの履歴などを参照するクエリを書いてダッシュボードに登録しておき、CSの方はそこにユーザIDを入力するだけでユーザ履歴の照会ができるようになります。簡単なものに関してはここに登録しておき、お問い合わせ調査はCSの方だけで完結してもらえるのに加え、画面も実装する事なくクエリの登録のみで済むので学習コストもなく、大幅な工数削減になりました。

スクリーンショット 2018-05-25 17.09.04.png (99.1 kB)

図3. re:dashを用いたCS調査ダッシュボードの例

例2:ちょっとしたデータ分析案件

re:dashはお問い合わせ対応だけでなく、ちょっとしたデータ分析の結果の受け渡しなどにもとても便利です。アプリボットでは現在詳細なデータ分析はTabelauなどを使って行っていますが、エンジニアが少しクエリを叩いてデータを可視化したい時などにもre:dashはとても便利です。UIがシンプルで学習コストの低い事と、項目が指定しやすく分析の知識がなくても棒グラフなど作りやすく、その割にはSankey DiagramやSunburst Sequence Diagramなど、複雑な可視化もサポートしてくれています。Excelなどに結果を貼り付けて共有するなどの工数が、もっとシンプルで綺麗に分かりやすく、スピード感を持って共有する事ができるようになりました。

redashサンプル.png (104.8 kB)

図4. re:dashを用いた分析結果の可視化例

おわりに

ここまででざっくばらんにログについての要件や、設計方法などを書いてきました。全てのゲームに共通する最適解はないと考えているため、あえて曖昧性のある表現にした部分も多く、実際最適化したログに落とし込むためには経験と知識が必要なので、私自身も今後も検討と勉強を重ねていきます。また実際に最適化されたログの設計にはデータ基盤も含む多くの知識が必要になります。オススメの文献を参考文献に少しだけ書いておきましたので興味のある方は参照してみてください。

拙い文章ではありますが、もしふと息抜きにでも読んでいただき参考になる部分などありましたら、これ以上嬉しいことはありません。また、これらのほとんどが個人的な経験によるものなので、間違いや、異なる手法や考え方、最適なログ設計に関して知見をお持ちの方がいれば是非ご指摘いただければ大変有り難く思います。

てっくぼっとでは以前データ分析基盤の記事の掲載がありましたが、ゲーム運用の長期化やコンテンツ数の増加により、改めてデータ分析基盤の構築を行う検討をしています。また文章に起こせそうな内容が纏まりましたら、また是非記事にさせていただきたいと思います。

参照・参考文献