Service Worker が実社会に与えるパフォーマンスへの影響を測定する

Service Worker の最も重要なメリットの 1 つ(少なくともパフォーマンスの観点から)は、アセットのキャッシュを事前に制御できることです。ウェブ アプリケーションに必要なリソースをすべてキャッシュできれば、リピーターによる読み込み時間が大幅に短縮されます。では、実際のユーザーにとって、これらのメリットはどのようなものになるでしょうか。これをどう測定すればよいのでしょうか

Google I/O ウェブアプリ(IOWA)は、サービス ワーカーが提供する新しい機能のほとんどを活用したプログレッシブ ウェブアプリで、アプリのような豊富なエクスペリエンスをユーザーに提供しています。また、Google アナリティクスを使用して、大規模で多様なユーザー グループから主要なパフォーマンス データと使用パターンを収集しました。

この活用事例では、IOWA が Google アナリティクスを使用してパフォーマンスに関する重要な疑問を解決し、Service Worker が実際に与える影響についてレポートしました。

質問から始める

ウェブサイトやアプリケーションにアナリティクスを実装するときは常に、収集するデータから答えを出したい質問を特定することから始めることが重要です。

いくつか疑問に思っていましたが、このケーススタディでは、特に興味深いものを 2 つ取り上げて説明します。

1. Service Worker のキャッシュは、すべてのブラウザで使用できる既存の HTTP キャッシュ メカニズムよりも高性能ですか?

ブラウザはリクエストをキャッシュに保存し、再訪問時に即座に提供できるため、リピーターの方が新規訪問者よりもページの読み込みが速くなることが想定されています。

Service Worker には、キャッシュの対象とキャッシュの処理方法をきめ細かく制御できる代替のキャッシュ機能が用意されています。IOWA では、すべてのアセットがキャッシュに保存されるように Service Worker の実装を最適化し、リピーターのユーザーがアプリを完全にオフラインで使用できるようにしました。

しかし、この取り組みは、ブラウザのデフォルトの動作よりもはるかに優れていますか?ある場合は、どの程度改善されましたか?1

2. サービス ワーカーはサイトの読み込みにどのような影響を与えますか?

つまり、従来のページ読み込み指標で測定される実際の読み込み時間に関係なく、サイトの読み込みがどれくらい速く感じられるかということです。

エクスペリエンスがどのように感じられるかに関する質問に答えるのは明らかに簡単なタスクではなく、そのような主観的な感情を完全に表す指標はありません。とはいえ、他よりも優れた指標は確かに存在するため、適切な指標を選択することが重要です。

適切な指標を選択する

Google アナリティクスでは、デフォルトでサイト訪問者の 1% のページ読み込み時間Navigation Timing API 経由)がトラッキングされ、平均ページ読み込み時間などの指標でそのデータを確認できます。

平均ページ読み込み時間は、最初の質問に回答するのに適した指標ですが、2 番目の質問に回答するのに適した指標ではありません。たとえば、load イベントは、ユーザーが実際にアプリを操作できる瞬間に対応しているとは限りません。また、読み込み時間がまったく同じ 2 つのアプリでも、読み込みの感覚が大きく異なる場合があります。たとえば、スプラッシュ画面や読み込みインジケーターがあるサイトは、数秒間空白のページが表示されるだけのサイトよりも、読み込みがはるかに速く感じられます。

IOWA では、スプラッシュ画面のカウントダウン アニメーションを表示しました。これは、アプリの残りの部分がバックグラウンドで読み込まれる間、ユーザーを楽しませるのに非常に効果的でした(私見です)。そのため、ユーザーが感じる読み込みパフォーマンスを測定する方法として、スプラッシュ スクリーンの表示にかかる時間をトラッキングすることは非常に理にかなっています。この値を取得するために、最初のペイントまでの時間という指標を選択しました。

答えを考えている質問を決定し、その答えに役立つ指標を特定したら、次は Google アナリティクスを導入して測定を開始します。

アナリティクスの実装

Google アナリティクスの使用経験があれば、推奨される JavaScript トラッキング スニペットをご存知かもしれません。たとえば、次のようになります。

<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>

上記のコードの 1 行目では、グローバル ga() 関数が初期化されています(まだ存在しない場合)。最後の行では、非同期で analytics.js ライブラリをダウンロードします。

中央部分には次の 2 行が含まれています。

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

これらの 2 つのコマンドは、サイトにアクセスしたユーザーがどのページを閲覧したかをトラッキングしますが、それ以上のことはできません。その他のユーザー操作をトラッキングする場合は、ご自身でトラッキングする必要があります。

IOWA では、さらに次の 2 つをトラッキングしたいと考えました。

  • ページの読み込みが開始してから、ピクセルが画面に表示されるまでの経過時間。
  • Service Worker がページを制御しているかどうか。この情報を使用してレポートを分割し、サービス ワーカーありの場合となしの場合の結果を比較できます。

最初のペイントまでの時間の取得

一部のブラウザでは、最初のピクセルが画面に描画された正確な時刻が記録され、その時刻をデベロッパーが利用できます。この値は、Navigation Timing API を介して公開される navigationStart の値と比較して、ユーザーが最初にページをリクエストしてから最初に何かを表示するまでの経過時間を非常に正確に評価できます。

前述のように、ファースト ペイントまでの時間は、ユーザーがサイトの読み込み速度を初めて体験するポイントであるため、測定すべき重要な指標です。ユーザーが最初に目にするものであり、良い第一印象は、その後のユーザー エクスペリエンスにプラスの影響を与えます。2

First Paint を公開しているブラウザで値を取得するために、getTimeToFirstPaintIfSupported ユーティリティ関数を作成しました。

function getTimeToFirstPaintIfSupported() {
  // Ignores browsers that don't support the Performance Timing API.
  if (window.performance && window.performance.timing) {
    var navTiming = window.performance.timing;
    var navStart = navTiming.navigationStart;
    var fpTime;

    // If chrome, get first paint time from `chrome.loadTimes`.
    if (window.chrome && window.chrome.loadTimes) {
      fpTime = window.chrome.loadTimes().firstPaintTime * 1000;
    }
    // If IE/Edge, use the prefixed `msFirstPaint` property.
    // See http://msdn.microsoft.com/ff974719
    else if (navTiming.msFirstPaint) {
      fpTime = navTiming.msFirstPaint;
    }

    if (fpTime && navStart) {
      return fpTime - navStart;
    }
  }
}

これで、最初のペイントまでの時間を値として含む非インタラクション イベントを送信する別の関数を作成できます。3

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    ga('send', 'event', {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    });
  }
}

これらの関数を両方記述すると、トラッキング コードは次のようになります。

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview for the initial pageload.
ga('send', 'pageview');

// Sends an event with the time to first paint data.
sendTimeToFirstPaint();

上記のコードが実行されるタイミングによって、ピクセルがすでに画面に描画されている場合と、描画されていない場合があることに注意してください。初回ペイントの発生後にこのコードを常に実行するように、sendTimeToFirstPaint() の呼び出しを load イベント後まで延期しました。実際、Google は、アナリティクス データのリクエストが他のリソースの読み込みと競合しないように、すべてのアナリティクス データの送信をページの読み込み後まで延期することにしました。

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

上記のコードでは、firstpaint 回 Google アナリティクスにレポートされますが、これは半分しか記録されません。それでも、Service Worker のステータスをトラッキングする必要がありました。そうしないと、Service Worker で制御されているページと制御されていないページの最初のペイント時間を比較できません。

Service Worker のステータスの確認

サービス ワーカーの現在のステータスを確認するために、次の 3 つの値のいずれかを返すユーティリティ関数を作成しました。

  • controlled: ページは Service Worker によって制御されています。IOWA の場合、すべてのアセットがキャッシュに保存され、ページがオフラインで動作することも意味します。
  • supported: ブラウザは Service Worker をサポートしていますが、Service Worker はまだページを制御していません。これは、初めて訪問したユーザーに想定されるステータスです。
  • サポートされていない: ユーザーのブラウザが Service Worker をサポートしていません。
function getServiceWorkerStatus() {
  if ('serviceWorker' in navigator) {
    return navigator.serviceWorker.controller ? 'controlled' : 'supported';
  } else {
    return 'unsupported';
  }
}

この関数はサービス ワーカーのステータスを取得します。次のステップでは、このステータスを Google アナリティクスに送信するデータに関連付けます。

カスタム ディメンションによるカスタムデータのトラッキング

Google アナリティクスには、ユーザー、セッション、またはインタラクションの属性に基づいて、合計トラフィックをグループに細分化するさまざまな方法がデフォルトで用意されています。これらの属性はディメンションと呼ばれます。ウェブ デベロッパーが関心を持つ一般的なディメンションには、ブラウザオペレーティング システムデバイス カテゴリなどがあります。

サービス ワーカーのステータスは、Google アナリティクスでデフォルトで提供されるディメンションではありません。ただし、Google アナリティクスでは独自のカスタム ディメンションを作成して自由に定義できます。

IOWA では、Service Worker Status というカスタム ディメンションを作成し、そのスコープをヒット(インタラクションごと)に設定しました。4 Google アナリティクスで作成する各カスタム ディメンションには、そのプロパティ内で一意のインデックスが割り当てられます。トラッキング コードでは、そのインデックスでカスタム ディメンションを参照できます。たとえば、作成したディメンションのインデックスが 1 の場合、次のようにロジックを更新して firstpaint イベントを送信し、サービス ワーカーのステータスを含めることができます。

ga('send', 'event', {
  eventCategory: 'Performance',
  eventAction: 'firstpaint',
  // Rounds to the nearest millisecond since
  // event values in Google Analytics must be integers.
  eventValue: Math.round(timeToFirstPaint)
  // Sends this as a non-interaction event,
  // so it doesn't affect bounce rate.
  nonInteraction: true,

  // Sets the current service worker status as the value of
  // `dimension1` for this event.
  dimension1: getServiceWorkerStatus()
});

これは機能しますが、サービス ワーカーのステータスはこの特定のイベントにのみ関連付けられます。Service Worker のステータスは、あらゆるやり取りに役立つ情報であるため、Google アナリティクスに送信されるすべてのデータに含めることをおすすめします。

この情報をすべてのヒット(すべてのページビュー、イベントなど)に含めるため、Google アナリティクスにデータを送信する前に、トラッカー オブジェクト自体にカスタム ディメンションの値を設定します。

ga('set', 'dimension1', getServiceWorkerStatus());

この値を設定すると、現在のページ読み込みの以降のすべてのヒットで送信されます。ユーザーが後でページを再度読み込むと、getServiceWorkerStatus() 関数から新しい値が返され、その値がトラッカー オブジェクトに設定されます。

コードの明確さと読みやすさに関する注意点: このコードを見ている人は、dimension1 が何を指しているのかわからない可能性があるため、意味のあるディメンション名を analytics.js が使用する値にマッピングする変数を作成することをおすすめします。

// Creates a map between custom dimension names and their index.
// This is particularly useful if you define lots of custom dimensions.
var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1'
};

// Creates the tracker object.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sets the service worker status on the tracker,
// so its value is included in all future hits.
ga('set', customDimensions.SERVICE_WORKER_STATUS, getServiceWorkerStatus());

// Postpones sending any hits until after the page has fully loaded.
// This prevents analytics requests from delaying the loading of the page.
window.addEventListener('load', function() {
  // Sends a pageview for the initial pageload.
  ga('send', 'pageview');

  // Sends an event with the time to first paint data.
  sendTimeToFirstPaint();
});

先ほど述べたように、すべてのヒットとともに「Service Worker Status」ディメンションを送信することで、どの指標のレポートでもそのディメンションを使用できるようになります。

ご覧のとおり、IOWA の全ページビューのほぼ 85% は、Service Worker をサポートするブラウザからのものです。

結果: 質問への回答

疑問に答えるためのデータ収集を開始すると、そのデータに関するレポートを作成して結果を確認できるようになります。(注: ここに表示されている Google アナリティクスのデータはすべて、2016 年 5 月 16 日~ 22 日に IOWA サイトにアクセスした実際のウェブ トラフィックを表しています)。

最初の質問は、サービス ワーカーのキャッシュは、すべてのブラウザで利用可能な既存の HTTP キャッシュ メカニズムよりもパフォーマンスが高いか?というものでした。

この質問に答えるために、さまざまなディメンションにわたる平均ページ読み込み時間の指標を調べるカスタム レポートを作成しました。load イベントは、すべての初期リソースがダウンロードされた後にのみ起動するため、この指標が適しています。したがって、サイトのすべての重要なリソースの合計読み込み時間が直接反映されます。5

選択したディメンションは次のとおりです。

  • カスタム Service Worker のステータス ディメンション。
  • ユーザータイプ: ユーザーの初回訪問か、リピート訪問かを示します。(注: 新規の訪問者にはリソースがキャッシュに保存されませんが、リピーターには保存される場合があります)。
  • デバイス カテゴリ: モバイルとパソコン間で結果を比較できます。

Service Worker に関連しない要因によって読み込み時間の結果に偏りが生じる可能性を抑えるために、Service Worker をサポートするブラウザのみを含むようにクエリを制限しました。

ご覧のとおり、Service Worker で制御している場合、アプリのアクセスは管理されていない訪問よりもかなり速く読み込まれました。これには、ページのリソースのほとんどがキャッシュされている可能性が高いリピーターからのアクセスも含まれます。また、サービス ワーカーを使用しているモバイル ユーザーは、デスクトップの新規ユーザーよりも平均的に読み込み時間が短縮されていることも興味深い点です。

「...Service Worker に制御されているときは、制御されていない訪問よりもかなり速くアプリにアクセスした...」

詳細については、次の 2 つの表をご覧ください。

ページの平均読み込み時間(パソコン)
Service Worker のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
制御しました リピーター 2,568 30860
サポート対象 リピーター 3612 1289
サポート対象 新規ユーザー 4664 21991
ページの平均読み込み時間(モバイル)
Service Worker のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
制御しました リピーター 3,760 8162
サポート対象 リピーター 4843 676
サポート対象 新規ユーザー 6158 5779

ブラウザが Service Worker に対応しているリピーターのユーザーが、制御対象外の状態になる可能性があるのはなぜでしょうか。これにはいくつかの原因が考えられます。

  • 最初のアクセス時に、Service Worker の初期化が完了する前にユーザーがページを離れました。
  • ユーザーがデベロッパー ツールを使用してサービス ワーカーをアンインストールした。

どちらの状況も比較的まれです。これは、4 番目の列の [ページ読み込みサンプル] の値で確認できます。中央の行のサンプル数が他の 2 つの行よりもはるかに少ないことに注目してください。

2 つ目の質問は、サービス ワーカーがサイトの読み込みエクスペリエンスに与える影響です。

そこで、「平均イベント値」という指標を表示する別のカスタム レポートを作成し、firstpaint イベントのみが含まれるように結果をフィルタしました。ディメンション「デバイス カテゴリ」とカスタム ディメンション「Service Worker のステータス」を使用しました。

予想に反して、モバイルの Service Worker は、ページ全体の読み込みよりも最初のペイント時間に与える影響がはるかに小さかった。

「…モバイル サービス ワーカーは、ページ全体の読み込みよりも、最初のペイントまでの時間に与える影響がはるかに小さかった。」

なぜこのような結果になったのかを探るには、データをさらに詳しく見ていく必要があります。平均値は、全体的な概要や大まかな傾向を把握するのに適していますが、さまざまなユーザーにわたってこれらの数値がどのように分布しているかを把握するには、firstpaint 回数の分布を確認する必要があります。

Google アナリティクスで指標の分布を取得する

firstpaint 時間の分布を取得するには、各イベントの個々の結果にアクセスする必要があります。残念ながら、Google アナリティクスではこの作業は簡単ではありません。

Google アナリティクスでは、任意のディメンション別にレポートを分割できますが、指標別にレポートを分割することはできません。不可能というわけではありませんが、目的の結果を得るには実装をもう少しカスタマイズする必要がありました。

レポートの結果はディメンション別にしか分割できないため、指標値(この場合は firstpaint 時間)をイベントのカスタム ディメンションとして設定する必要がありました。そのために、「指標値」という別のカスタム ディメンションを作成し、firstpaint トラッキング ロジックを次のように更新しました。

var customDimensions = {
  SERVICE_WORKER_STATUS: 'dimension1',
  <strong>METRIC_VALUE: 'dimension2'</strong>
};

// ...

function sendTimeToFirstPaint() {
  var timeToFirstPaint = getTimeToFirstPaintIfSupported();

  if (timeToFirstPaint) {
    var fields = {
      eventCategory: 'Performance',
      eventAction: 'firstpaint',
      // Rounds to the nearest millisecond since
      // event values in Google Analytics must be integers.
      eventValue: Math.round(timeToFirstPaint)
      // Sends this as a non-interaction event,
      // so it doesn't affect bounce rate.
      nonInteraction: true
    }

    <strong>// Sets the event value as a dimension to allow for breaking down the
    // results by individual metric values at reporting time.
    fields[customDimensions.METRIC_VALUE] = String(fields.eventValue);</strong>

    ga('send', 'event', fields);
  }
}

現在のところ、Google アナリティクスの管理画面では、任意の指標値の分布を可視化することはできませんが、Google アナリティクスの Core Reporting APIGoogle Charts ライブラリを利用すれば、未加工の結果を取得してヒストグラムを作成できます。

たとえば、次の API リクエスト構成を使用して、制御対象外の Service Worker を使用してデスクトップで firstpaint 値の分布を取得しました。

{
  dateRanges: [{startDate: '2016-05-16', endDate: '2016-05-22'}],
  metrics: [{expression: 'ga:totalEvents'}],
  dimensions: [{name: 'ga:dimension2'}],
  dimensionFilterClauses: [
    {
      operator: 'AND',
      filters: [
        {
          dimensionName: 'ga:eventAction',
          operator: 'EXACT',
          expressions: ['firstpaint']
        },
        {
          dimensionName: 'ga:dimension1',
          operator: 'EXACT',
          expressions: ['supported']
        },
        {
          dimensionName: 'ga:deviceCategory',
          operator: 'EXACT',
          expressions: ['desktop']
        }
      ],
    }
  ],
  orderBys: [
    {
      fieldName: 'ga:dimension2',
      orderType: 'DIMENSION_AS_INTEGER'
    }
  ]
}

この API リクエストは、次のような値の配列を返します(注: これらは最初の 5 件の結果のみです)。結果は小さい順に並べ替えられるため、これらの行は最速の時間を示す行です。

API レスポンス結果(最初の 5 行)
ga:dimension2 ga:totalEvents
4 3
5 2
6 10
7 8
8 10

結果の意味は次のとおりです。

  • firstpaint 値が 4 ms のイベントが 3 件ある
  • firstpaint 値が 5 ms のイベントが 2 件ある
  • firstpaint の値が 6 ms のイベントが 10 件ある
  • firstpaint 値が 7 ms のイベントが 8 件ある
  • firstpaint value が 8 ミリ秒のイベントが 10 件ある
  • その他

これらの結果から、すべてのイベントの firstpaint 値を外挿し、分布のヒストグラムを作成できます。これは、実行した各クエリに対して行いました。

制御されていない(ただしサポートされている)Service Worker を搭載したパソコンでのディストリビューションは、次のようになります。

デスクトップでの最初のペイントまでの時間の分布(サポート)

上記の分布の firstpaint 時間の中央値は 912 ms です。

この曲線の形状は、読み込み時間の分布によく見られます。一方、次のヒストグラムは、Service Worker がページを制御していたアクセスでの最初のペイント イベントの分布を示しています。

デスクトップでの最初のペイントまでの時間の分布(制御済み)

サービス ワーカーがページを制御していた場合、多くの訪問者が最初のペイントをほぼ即座に体験し、中央値は 583 ms でした。

「…Service Worker がページを制御している場合、多くのユーザーがほぼ即座に最初のペイントを体験しました…」

これら 2 つの分布の比較をより深く理解できるように、次のグラフに 2 つの分布を統合して表示しています。制御対象外の Service Worker のアクセスを示すヒストグラムは、制御対象のアクセスを示すヒストグラムの上に重ねて表示されます。また、両方のヒストグラムは、両方のヒストグラムを組み合わせたヒストグラムの上に重ねて表示されます。

パソコンでの Time to First Paint の分布

これらの結果で興味深いのは、制御された Service Worker を使用した分布でも、最初の急増後もベル型の曲線が維持されていることです。最初は急増し、その後は徐々に減少すると予想していましたが、グラフに 2 つ目のピークが現れるとは予想していませんでした。

この原因について調べたところ、Service Worker がページを制御できても、そのスレッドが非アクティブになる可能性があることが判明しました。ブラウザはリソースを節約するためにこの処理を行います。これまでにアクセスしたすべてのサイトのすべての Service Worker を、いつでもアクティブにしておく必要はありません。これは分布の尾部を説明しています。一部のユーザーでは、Service Worker スレッドの起動中に遅延が発生しました。

ただし、この分布からわかるように、この最初の遅延があっても、Service Worker を使用するブラウザは、ネットワーク経由のブラウザよりもコンテンツを速く配信しています。

モバイルでは次のように表示されます。

モバイルでの First Paint の分布

最初の描画までの時間はかなり長くなりましたが、テールはかなり大きく、長くなりました。これは、モバイルでは、アイドル状態の Service Worker スレッドの開始がデスクトップよりも時間がかかるためです。前述したように、平均 firstpaint 時間の差が期待を下回った理由も説明されています。

「...モバイルでは、アイドル状態の Service Worker スレッドを開始するのに、デスクトップよりも時間がかかります。」

次に示すのは、モバイルとパソコンでの First Paint 時間の中央値の変化を Service Worker のステータス別にグループ化したものです。

中央値の Time to First Paint(ミリ秒)
Service Worker のステータス パソコン モバイル
制御しました 583 1634
サポート対象(制御対象外) 912 1933

これらの分布ビジュアリゼーションを構築するには、Google アナリティクスでカスタム レポートを作成するよりも時間と労力がかかりましたが、平均値のみよりもサービス ワーカーがサイトのパフォーマンスに与える影響をはるかに明確に把握できました。

Service Worker によるその他の影響

サービス ワーカーはパフォーマンスに影響するだけでなく、Google アナリティクスで測定できる他の方法でもユーザー エクスペリエンスに影響します。

オフライン アクセス

サービス ワーカーを使用すると、ユーザーはオフラインでもサイトを操作できます。プログレッシブ ウェブアプリではなんらかのオフライン サポートが重要ですが、その重要性は、オフラインでの使用頻度に大きく左右されます。では、それをどのように測定するのでしょうか。

Google アナリティクスにデータを送信するにはインターネット接続が必要ですが、インタラクションが発生した正確なタイミングでデータを送信する必要はありません。Google アナリティクスでは、時間オフセットを指定して(qt パラメータを使用して)インタラクション データを後で送信できます。

過去 2 年間、IOWA では、ユーザーがオフラインのときに Google アナリティクスへのヒットが失敗した場合に検出し、後で qt パラメータを使用して再送信するサービス ワーカー スクリプトを使用してきました。

ユーザーがオンラインかオフラインかをトラッキングするため、Online というカスタム ディメンションを作成し、その値を navigator.onLine に設定しました。次に、online イベントと offline イベントをリッスンし、それに応じてディメンションを更新しました。

また、IOWA を使用しているユーザーがオフラインになる頻度を把握するために、オフラインでのインタラクションが少なくとも 1 回あるユーザーをターゲットにするセグメントを作成しました。結果として、ユーザーの約 5% が該当しました。

プッシュ通知

Service Worker を使用すると、ユーザーがプッシュ通知の受信をオプトインできます。アイオワ州では、スケジュールされたセッションが開始される直前にユーザーに通知されていました。

どのような通知でもそうですが、ユーザーに価値を提供するのと煩わしい通知との間でバランスを取ることが重要です。どちらの状況であるかを把握するには、ユーザーがこれらの通知の受信をオプトインしているかどうか、通知が届いたときにユーザーが操作しているかどうか、以前にオプトインしたユーザーが設定を変更してオプトアウトしているかどうかをトラッキングすることが重要です。

アイオワ州では、ログインしたユーザーのみが作成できるユーザーのパーソナライズされたスケジュールに関連する通知のみを送信していました。これにより、通知を受け取ることができるユーザーは、ログインしているユーザー(Signed In というカスタム ディメンションでトラッキング)に限定され、そのブラウザがプッシュ通知をサポートしている(Notification Permission という別のカスタム ディメンションでトラッキング)ユーザーに限定されました。

以下のレポートは、指標「ユーザー」とカスタム ディメンション「通知の権限」に基づいており、ログインしたことがあり、ブラウザがプッシュ通知に対応しているユーザーで分類されています。

ログイン ユーザーの半数以上がプッシュ通知の受け取りを選択しているのは素晴らしいことです。

アプリのインストール バナー

進捗状況ウェブアプリが条件を満たしていて、ユーザーが頻繁に使用している場合、そのユーザーにはアプリのインストール バナーが表示され、アプリをホーム画面に追加するよう求めるメッセージが表示されることがあります。

IOWA では、次のコードを使用して、これらのメッセージがユーザーに表示される頻度(および承認されたかどうか)を追跡しました。

window.addEventListener('beforeinstallprompt', function(event) {
  // Tracks that the user saw a prompt.
  ga('send', 'event', {
    eventCategory: 'installprompt',
    eventAction: 'fired'
  });

  event.userChoice.then(function(choiceResult) {
    // Tracks the users choice.
    ga('send', 'event', {
      eventCategory: 'installprompt',
      // `choiceResult.outcome` will be 'accepted' or 'dismissed'.
      eventAction: choiceResult.outcome,
      // `choiceResult.platform` will be 'web' or 'android' if the prompt was
      // accepted, or '' if the prompt was dismissed.
      eventLabel: choiceResult.platform
    });
  });
});

アプリのインストール バナーを表示したユーザーのうち、約 10% がホーム画面に追加を選択しました。

追跡の改善(次回に向けて)

今年の IOWA で収集したアナリティクス データは非常に有益でした。しかし、後から振り返ると、必ず穴や改善の余地が見つかります。今年の分析を終えて、次のような 2 つの点について、同様の戦略を実装しようとしている読者の皆様が検討することをおすすめします。

1. 読み込みエクスペリエンスに関連するイベントをさらにトラッキングする

技術的な指標に対応する複数のイベント(HTMLImportsLoadedWebComponentsReady など)をトラッキングしましたが、読み込みの多くが非同期で行われていたため、これらのイベントが発生したポイントが、全体的な読み込みエクスペリエンスの特定の瞬間に対応するとは限りませんでした。

読み込みに関連する主なイベント(トラッキングできなかったが、トラッキングできたらよかった)は、スプラッシュ画面が消えてユーザーがページ コンテンツを表示できるポイントです。

2. アナリティクスのクライアント ID を IndexedDB に保存する

デフォルトでは、analytics.js はクライアント ID フィールドをブラウザの Cookie に保存します。残念ながら、Service Worker スクリプトは Cookie にアクセスできません。

このため、通知のトラッキングを実装しようとしたときに問題が発生しました。通知がユーザーに送信されるたびに、サービス ワーカーから(Measurement Protocol を介して)イベントを送信し、ユーザーが通知をクリックしてアプリに戻ってきた場合に、その通知の再エンゲージメントの成功をトラッキングしたいと考えました。

utm_source キャンペーン パラメータを使用して通知の成功を一般に追跡することはできましたが、特定の再エンゲージメント セッションを特定のユーザーに関連付けることはできませんでした。

この制限を回避するには、IndexedDB を介してクライアント ID をトラッキング コードに格納することで、その値に Service Worker スクリプトからアクセスできるようになりました。

3. Service Worker がオンライン/オフラインのステータスを報告できるようにする

navigator.onLine を検査すると、ブラウザがルーターに接続できるかどうかを確認できますが、ユーザーが実際に接続しているかどうかはわかりません。また、オフライン アナリティクスの Service Worker スクリプトは、失敗したヒットを(変更したり、失敗としてマークしたりせずに)単に再生していたため、オフライン ユーザー数の報告が過小評価されていた可能性があります。

今後は、navigator.onLine のステータスと、最初のネットワーク障害によってヒットが Service Worker によってリプレイされたかどうかの両方を追跡する必要があります。これにより、実際のオフライン使用状況をより正確に把握できます。

まとめ

このケーススタディでは、Service Worker を使用することで、さまざまなブラウザ、ネットワーク、デバイスで Google I/O ウェブアプリの負荷パフォーマンスが確かに向上することがわかりました。また、さまざまなブラウザ、ネットワーク、デバイスにわたる負荷データの分布を調べると、このテクノロジーが実際のシナリオをどのように処理するかについて、より多くの分析情報を得ることができ、想定外のパフォーマンス特性を発見できることも示されています。

IOWA の調査から得られた主な知見は次のとおりです。

  • 新規ユーザーとリピーターの両方において、Service Worker がページを制御している場合、ページの読み込み時間が Service Worker を使用しない場合に比べて平均で大幅に短縮されました。
  • Service Worker によって制御されるページへのアクセスは、多くのユーザーでほぼ瞬時に読み込まれました。
  • アクティブでない Service Worker は、起動に少し時間がかかります。ただし、無効な Service Worker でも、Service Worker を使用しない場合よりもパフォーマンスは優れています。
  • 非アクティブなサービス ワーカーの起動時間が、モバイルの方がデスクトップよりも長かった。

特定のアプリケーションで確認されたパフォーマンスの向上は、一般に大規模なデベロッパー コミュニティに報告するのに役立ちますが、これらの結果は IOWA のサイトの種類(イベント サイト)と IOWA のユーザーの種類(主にデベロッパー)に固有のものであることを覚えておく必要があります。

アプリにサービス ワーカーを実装する場合は、独自のパフォーマンスを評価し、将来の回帰を防ぐために、独自の測定戦略を実装することが重要です。試した方は、結果を共有して他のユーザーの参考にしてください。

脚注

  1. サービス ワーカー キャッシュの実装のパフォーマンスを、HTTP キャッシュのみを使用したサイトのパフォーマンスと比較するのは、必ずしも公平ではありません。Service Worker 向けに IOWA を最適化していたため、HTTP キャッシュの最適化には多くの時間を費やしませんでした。もしそうしていたら、おそらく結果は違っていたでしょう。HTTP キャッシュ用にサイトを最適化する方法については、コンテンツを効率的に最適化するをご覧ください。
  2. サイトのスタイルとコンテンツの読み込み方法によっては、コンテンツやスタイルが利用可能になる前にブラウザがペイントを開始する可能性があります。このような場合、firstpaint は空白の白い画面に対応している可能性があります。firstpaint を使用する場合は、サイトのリソースの読み込みの有意なポイントに対応していることを確認することが重要です。
  3. 厳密には、イベントではなくタイミングヒット(デフォルトでは非インタラクション)を送信して、この情報を取得することもできます。実際、timing のヒットは、特にこのような負荷に関する指標を追跡するために Google アナリティクスに追加されました。しかし、timing のヒットは処理時に大量にサンプリングされるため、セグメントでは使用できません。現在のこうした制約を考慮すると、非インタラクション イベントは依然として、より適しています。
  4. Google アナリティクスでカスタム ディメンションに割り当てられるスコープについて詳しくは、アナリティクス ヘルプセンターのカスタム ディメンションをご覧ください。また、ユーザー、セッション、インタラクション(ヒット)で構成される Google アナリティクスのデータモデルを理解することも重要です。詳しくは、Google アナリティクスのデータモデルに関するアナリティクス アカデミーのレッスンをご覧ください。
  5. ただし、読み込みイベントの後に遅延読み込みされるリソースはカウントされません。