Google Optimize ログ周りあれこれ

# 概要
Google Optimize利用に関してGoogle AnalyticsやBigQueryでのログ抽出に必要な仕様を整理していきます。
フロント実装での疎通確認や、効果検証のためにAnalyticsやDWHからデータ抽出が必要になった場合の参考になればと思います。

## Optimizeについて
この記事に関連する、基本的なOptimizeの機能は以下の通りです。

### 1) エクスペリエンスの種別
「A/Bテスト」だけでなく特定の条件で表示を切り替えることのできる「パーソナライズ」も実施可能です。これ以外にも「多変量テスト」や「リダイレクトテスト」もあります。

### 2) 実行タイミング
「ページの読み込み(同期)」あるいは「任意のタイミング(非同期)」を選ぶ事ができます。後者は「アクティベーションイベント」をトリガーに実行し、例えばシングルページアプリケーション(SPA)であったり、AJAXのコールバックで実行させたい場合での利用が想定されます。

**参考ページ：**
・[アクティベーション イベント - Optimize ヘルプ](https://support.google.com/optimize/answer/7008840?hl=ja)
・[オプティマイズのランタイム中の動作 - Optimize ヘルプ](https://support.google.com/optimize/answer/7579180?hl=ja)


## A/Bテストに関するディメンション

Analyticsであれば`テスト名`、`テストID`、`パターンを含むテストID`の3つのディメンションにてテスト情報を取得可能です。BigQueryの場合は `hits.experiment` に含まれています。

`パターンを含むテストID`は、`NiC0qgqDSOelDKJ-_GEYTg.0`のような形になり、0がオリジナルで1から続く連番は割り当てられたテストパターンとなります。多変量テストの場合には`NiC0qgqDSOelDKJ-_GEYTg.1-1-1` のように各セクションがハイフンで区切られる形となります。

### ディメンションのスコープ
ヘルプにもあるように、テストディメンションのスコープは「セッション」となります。

> テストディメンションはセッションスコープですが、他の多くのセッションスコープのディメンションとは異なり、ユーザーが複数のテストで同じセッションに含まれている場合、テストディメンションは他のディメンションを上書きしない複数の値を持ちます。テストディメンションの値はすべてのテストセッションで設定されます。

> [Google アナリティクスのオプティマイズ データ - Optimize ヘルプ](https://support.google.com/optimize/answer/7364397?hl=ja#experiment-dimension-scope)


セッションスコープであるが故に、Analyticsの集計上ではA/Bテストとは無関係のページビューやイベントにもテストディメンションが割り当てられる事になります。例えば、テスト対象のページビュー数を抽出する場合に「テストID」ごとの「ページビュー数」とすると、想定している数値から大きく乖離してしまうため注意が必要です。

なお「Optimizeにおけるセッション」と「Analyticsにおけるセッション」は解釈が異なります。A/Bテストでセットされるディメンションはテストセッションの範囲内で有効になるため、実質的なスコープは「テストが終了するまでの範囲」ということになります。

> テストに関連するすべてのセッション。ユーザーのアトリビューションは、テストがユーザーに表示されたときに開始され、テストが終了したときに終了します。この期間内のすべてのセッションは、テストが適用されたかどうかにかかわらず、カウントされます。

> [用語集 - Optimize ヘルプ](https://support.google.com/optimize/answer/6211939#experiment-sessions)

BigQuery側でも以下のようなクエリで、ディメンションが複数セッションに跨っていることが確認できます。

```sql
SELECT
  visitId,
  experiment.experimentId,
FROM
  `project_name.dataset_name.ga_sessions_20201230`,
  UNNEST(hits) AS hits,
  UNNEST(experiment) AS experiment
WHERE
  experiment.experimentId = "{experimentId}"
  fullVisitorId = "{fullVisitorId}"
```

## 同期と非同期でのログの違い

ここでの同期・非同期とは「Optimizeの実行タイミングがページの読み込みと同期的かどうか」となります。それぞれで、フロントから確認できるヒットタイプが異なります。

### 同期の場合

ページビューヒットのタイミングで「割り当てられたテスト情報」を取得できている場合は、そのヒットに当該ディメンションが付加されます。
具体的にはMeasurementProtocolにおいて、tパラメータを `pageview`、expパラメータを `テストID.テストバリアント` としてセットします。


| 順序| ヒットタイプ  | 付加情報  |
|---|---|---|---|
|1| pageview  | exp : `テストID.テストバリアント`   | 

### 非同期の場合


ページビューヒットに間に合わない場合には別のヒットで送る事になります。この場合は tパラメータを `data`、expパラメータを `テストID.テストバリアント` 、niパラメータを `1` としてセットします。
TagManagerからOptimizeを配信している場合や、リダイレクトテストを利用している場合にもヒットタイプはdataで送信されます。

| 順序| ヒットタイプ  | 付加情報  |
|---|---|---|---|
|1| pageview  | 無し  | 
|2| data |  exp : `テストID.テストバリアント`<br>ni : `1`  | 

※「niパラメーターが1」とは「非インタラクションヒットがtrue」を意味します。
※ [APIリファレンス](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters?hl=en#t)のHit typeにはdataの記載がありませんが、[Resource Hub](https://support.google.com/optimize/answer/6315879)には言及がありました。`data`はA/Bテスト用のヒットタイプであり費用請求(Analytics360/Optimize360)のヒット数に含まれていますが、将来的にはヒット数のカウントから外すようです。※2021年1月現在

#### ページビューヒットの直後にヒットがある場合

ページビューヒットの直後に後続処理でイベントログ送信がある場合には、その後続ヒットにテスト情報が載せられるケースもあります。

| 順序| ヒットタイプ  | 付加情報  |
|---|---|---|---|
|1| pageview  | 無し  | 
|2| event | exp : `テストID.テストバリアント` | 
|3| data | ni : `1`  | 


## パーソナライズの場合

A/Bテストとは違いパーソナライズでは任意の確率での表示切り替えをしないため、テストバリアントという概念もありません。
結果的にログはページビューヒットではなくイベントヒットとして以下のようにセットされることになります。

| 順序| ヒットタイプ  | 付加情報  |
|---|---|---|---|
|1| pageview  | 無し  | 
|2| event |  イベントカテゴリ: `Google Optimize`<br> イベントアクション: `エクスペリエンスID` | 

※ サイト規模によっては`Google Optimize`のイベントヒットだけで数億・数千万のヒット発生はあり得るので、Analytics360/Optimize360の課金(Tier)観点で注意が必要です。

## BigQueryエクスポート

### dataのHit typeについて
前述したように同期の場合にはHit typeが `data` としてログが送られる事になりますが、BigQuery側には当該Hit typeは出力されていません。そのためAnalytics側で内部的に処理してテストセッションの範囲でpageviewやeventに紐付けてBigQueryにエクスポートしているようです。

### hits.experimentの構造

A/Bテストでは1つのサイトで複数のテストに割り当てられる可能性があり、テストディメンションも複数になり得ます。そのためBigQueryでは以下のような構造でネストされています。

```JSON
"experiment":[
   {
      "experimentId":"Bzp_KBJ2R0-ZL-fzvzPtKw",
      "experimentVariant":"0"
   },
   {
      "experimentId":"NiC0qgqDSOelDKJ-_GEYTg",
      "experimentVariant":"1"
   },
   {
      "experimentId":"pcDsWZGXQ42uFfpe_bXN6Q",
      "experimentVariant":"0"
   }
```

このJSONはある1ヒットおけるhits.experimentの構造ですが、3つのテストディメンションが割り当てられていることが分かります。つまりこのヒットを発生させたユーザーは、「ヒットを発生させたタイミングに実行されている3つのA/Bテストに、過去に1度でも遭遇していた」ことになります。

## おわりに
Optimizeの利用にあたっては、フロント実装では「任意のタイミングで正しくテストを実施ができるか」「テスト関連のログは正しく送信されているか」の確認、データ抽出・分析では「ディメンションのスコープ仕様」と「BigQueryエクスポートの仕様」を理解できていると良いと思います。