Liberent-Dev’s blog

株式会社リベル・エンタテインメントのテックブログです。

ゲームのデータをBigQueryにインポートする方法

f:id:Liberent-Dev:20211210161913p:plain

こんにちわ。 システム開発部ネットワーク課のsupercontinueです。

はじめに

http://takano-plantuml-server.herokuapp.com/svg/SoWkIImgAStDuU8gpixCAqWiIinLoCtFoq_EAChFJLKeAIfDvUBYqdZSFEznqyx7JViVD-zvtDmE8flsQohewjefQ3XbfWUMfXPLQeBLBGFBRGEHZUiUD-q6QvX6WfuN2tkUTazzkd7fixLRC5Kx9fVa5ocW22fOARoStVPYHSVZvjrFEzgUpPl0vP2QbmBq5000

ゲームのサービスを運営する上で、ユーザーの行動データを集計・分析する必要があります。

集計・分析するプラットフォームとして、Google Cloud の BigQuery は一般的なオプションの1つです。

BigQueryは膨大なデータを分析するのに特化したデータベースです。

以下では、ゲームのサービスで使うデータの種類ごとに BigQuery にインポートする方法を説明します。

  • マスターデータ

    • ゲームサービスにおいて、ガチャやストーリーやレベルアップに必要な経験値など、一般的にマスターデータと呼ばれるデータがあります。
    • 内容によってデータのサイズはまちまちですが、一般的にはユーザーデータやログと比較すれば、十分小さいと言えます。
  • ユーザーのセーブデータ

    • 経験値や持っているアイテムなどを保存しているデータです。
    • ユーザー数に応じてデータ全体のサイズが大きくなります。数百万のユーザーを考えると、大きなデータになることが想像できます。
  • ユーザーの行動履歴

    • ユーザーが、ゲームで、いつ・何をしたのかを記録したデータです。
    • ユーザー数とサービス期間によって、どんどんデータは蓄積し、膨大な大きさのデータになります。

マスターデータをインポート

本番サービスのデータを更新したら、BigQueryのマスターデータも更新します。

マスターデータは比較的サイズが小さい・更新頻度が低いため、BigQueryのマスターデータを一旦全部削除し、新規にマスターデータを追加します。

費用・時間・オペレーションの単純さの面で、一旦全部削除し、新規に追加する方がベターです。

http://takano-plantuml-server.herokuapp.com/svg/SoWkIImgAStDuU8gpixCAqWiIinLoCtFoq_EAChFJLKeAIfDvUBIA2akqrJGjLFGSCfC3onDBQhKLB1IyCbFJE5oICrB0Me70000

下記は tsv のマスターデータをインポートする例です。

bq --location=${MY_REGION} rm --force --table ${GCLOUD_PROJECT}:${DATASET}.${table}

bq load --autodetect --source_format=CSV --encoding=UTF-8 --field_delimiter='\t' --skip_leading_rows=1 \
        --max_bad_records=10 \
        ${GCLOUD_PROJECT}:${DATASET}.${table} ${file}

CIで自動で更新されるように設定します。

分析時の煩雑さが増しますが、マスターデータを日付ごとにBigQueryに保存するほうが良いかもしれません。データ自体が小さいため、BigQueryに置いておいてもコストの心配がないからです。

ユーザーのセーブデータをインポート

ユーザーのセーブデータは、1日1回、ユーザーデータベースである Google Cloud Spanner から Google Cloud Storage にエクスポート(バックアップ)をしています。

そのバックアップデータを、BigQuery にインポートします。

http://takano-plantuml-server.herokuapp.com/svg/SoWkIImgAStDuU8gpixCAqWiIinLoCtFoq_EAChFJLKeAIfDvUBI22v8pCjBBT9KqBLJq71t3jPKi59mIIn9JCl9B-U2281ad3BJ0qjJYof1qZxvYIbS3gbvAK330G00

ユーザーデータベースからBigQueryへ直接インポートもできますが、バックアップとしてGCSにデータがありますし、トラブルがあった時のやり直しや負荷を考えて、GCSへエクスポートされたものをソースとするのがベターです。

まずは avro形式でGCSヘエクスポート

Google Cloud Storage にエクスポートするには、Google Cloud Dataflow を使います。

下記のように、Google Cloud Storageには、avro形式でエクスポートしています。

TEMPLATE_LOCATION="gs://dataflow-templates/2020-01-09-00_RC00/Cloud_Spanner_to_GCS_Avro"

gcloud dataflow jobs run ${JOB_NAME} \
  --gcs-location=${TEMPLATE_LOCATION} \
  --region=${MY_REGION} \
  --staging-location=${MY_SPANNER_BACKUP_GS_URL}/temp \
  --parameters=instanceId=${MY_INSTANCE_ID},databaseId=${MY_DATABASE_ID},outputDir=${MY_SPANNER_BACKUP_GS_URL}/avro > tmp.log

上記の例では、ユーザーデータベースの Google SpannerからGCSへavro形式でエクスポートする、Googleが提供するテンプレートを利用しています。

テンプレートジョブは時々更新されており、最新版が自分の環境で突然動かなくなることがあります。

そのため、リリースを指定して使うのが良いでしょう。

つまり、下記のような latestは、使わない方が良いです。

TEMPLATE_LOCATION="gs://dataflow-templates/latest/Cloud_Spanner_to_GCS_Avro"

GCSのコストはとても低いので、過去の必要な日数分を保存するために利用できます。

GCSのavro形式のデータをBigQueryへインポート

以下では最新のセーブデータだけをBigQueryに入れています。

bq --location=${MY_REGION} rm --force --table ${GCLOUD_PROJECT}:${DATASET}.${table}

bq --location=${MY_REGION} load \
        --source_format=AVRO \
        ${DATASET}.${table} \
        "${MY_SPANNER_BACKUP_GS_URL}/avro/${MY_INSTANCE_ID}-${MY_DATABASE_ID}-${DATAFLOW_JOBID}/${table}.avro*"

上記のようなバッチスクリプトを1日1回実行します。

ユーザーデータをBigQueryに日付別に保存するかどうかは、分析方法とコストによって判断する必要があります。BigQueryに毎日蓄積させるとコストが増大していきます。そもそもGCSに日付別データがあるので、必要に応じてBigQueryに読み込むことができ、そのほうがコストが下がるケースがあるためです。

ユーザーの行動履歴をインポート

ユーザーの行動履歴は、1秒間に何千レコードも発生・追加する必要があるようなログデータです。ゲームによってはテラバイトのオーダーになるような巨大なデータです。

このようなデータは、一般的にストリーミングとして扱います。

ゲームサーバが出力するユーザー行動ログを、ストリーミングとしてBigQueryへ送信することで、ログデータを取り込むことができます。

http://takano-plantuml-server.herokuapp.com/svg/SoWkIImgAStDuU8gpixCAqWiIinLoCtFoq_EAChFJLKeAIfDvUBIUDoqwN7pdiVD2nutRGKp9ZnkNFUuUUtZfWsQ2hfs2XfEcUc1fQb5bPeALWeGySVDgq4gc_Q3oCRbZvjsFcxkUDoy2cBEouR69_iNSZcavgK0BGS0

ゲームサーバでログを出力する

BigQueryで扱いやすいように、jsonフォーマットでログを出力します。

go だと下記のような感じです。

var LoggerChild *logging.Logger
func LogJSON(ctx context.Context, level logging.Severity, v interface{}) {
    jsonPayload, err := json.Marshal(v)
    if err != nil {
        return
    }

    ret := ctx.Value(myLogDataContextKey)
    if ret != nil {
        if myLogData, ok := ret.(*MyLogData); ok {
            LoggerChild.Log(logging.Entry{
                Payload:  json.RawMessage(jsonPayload),
                Severity: level,
                Trace:    myLogData.TraceID,
                Labels: map[string]string{
                    // 自分で定義したラベルとデータ
                },
            })
        }
    }
}

BigQueryにテーブルを作る

ユーザー行動ログのテーブルの設定は下記のようにするのがベターです。

  • テーブルタイプ
    • 分割
  • 分割基準
    • DAY
  • フィールドで分割
    • レコードのtimestamp

ゲームの場合、日付単位での集計が多く、テーブルを日付で分割することで、速度とコストでメリットがあります。

ログルーターでログを送信する

Google CloudでログをBigQueryにストリームで送信するには、ログルーターシンクを定義します。

  • フィルタで、プロジェクトIDやログとして扱う条件を設定します。

    • ログにMyGameLogType のようなフィールドを設定しておき、そのフィールドに値があるログのみを送信するという感じで設定します。
  • 送信先にBigQueryのテーブルを選びます。

おわりに

ゲームのデータをBigQueryで集計・分析するために、必要なデータをインポートする方法を説明しました。

基本的に一度構築すれば手間はかかりません。少ない労力でBigQueryの活用を始めることができます。

BigQueryのコストは利用方法によって大きく変わるので、適切な構成にする必要があります。


リベル・エンタテインメントでは、このような最新技術などの取り組みに興味のある方を募集しています。もしご興味を持たれましたら下記サイトにアクセスしてみてください。 https://liberent.co.jp/recruit/