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

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

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

このケーススタディでは、IOWA が Google アナリティクスを使用してパフォーマンスに関する重要な質問に答え、サービス ワーカーの実際の影響を報告した方法について説明します。

質問から始める

ウェブサイトやアプリにアナリティクスを実装する際は、まず、収集するデータから回答を得たい質問を特定することが重要です。

回答したい質問はいくつかありましたが、このケーススタディでは、特に興味深い 2 つの質問に焦点を当ててみましょう。

1. サービス ワーカーのキャッシュは、すべてのブラウザで利用可能な既存の 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>

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

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

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

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

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

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

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

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

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

最初のペイント値を公開するブラウザでその値を取得するために、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 の場合、すべてのアセットがキャッシュに保存され、ページがオフラインで動作することも意味します。
  • サポートされている: ブラウザは 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 Status は、あらゆるインタラクションの把握に役立つ可能性があるため、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 のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
制御しました リピーター 2568 30860
サポート対象 リピーター 3612 1289
サポート対象 新規ユーザー 4664 21991
ページの平均読み込み時間(モバイル)
Service Worker のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
制御しました リピーター 3760 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 を使用したパソコンでの分散は次のようになります。

パソコンでの Time to First Paint の分布(サポート対象)

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

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

パソコンでの Time to First Paint の分布(コントロール)

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

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

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

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

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

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

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

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

モバイルでの Time to First Paint の分布

最初のペイント時間は大幅に短縮されましたが、その分、ペイント時間の分布の尾が長くなりました。これは、モバイルではアイドル状態のサービス ワーカー スレッドの開始にデスクトップよりも時間がかかるためと考えられます。また、平均 firstpaint 時間の差が予想していたほど大きくなかった理由も説明できます(上記を参照)。

「…モバイルでは、アイドル状態の 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 を保存し、その値にサービス ワーカー スクリプトからアクセスすることもできました。

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

navigator.onLine を検査すると、ブラウザがルーターまたはローカル エリア ネットワークに接続できるかどうかがわかりますが、ユーザーが実際に接続しているかどうかはわかりません。また、オフライン アナリティクスの Service Worker スクリプトは、失敗したヒットを単に再生するだけで(変更したり、失敗としてマークしたりしなかったため)、オフライン ユーザー数の報告が不足していた可能性があります。

今後は、navigator.onLine のステータスと、最初のネットワーク障害が原因でヒットが 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. 厳密には、イベントではなくタイミングヒット(デフォルトでは非インタラクション)を送信して、この情報を取得することもできます。実際、タイミング ヒットは、このような読み込み指標をトラッキングするために Google アナリティクスに追加されました。ただし、タイミング ヒットは処理時に大量にサンプリングされるため、その値をセグメントで使用することはできません。現時点では、非インタラクション イベントの方が適しています。
  4. Google アナリティクスでカスタム ディメンションに設定するスコープについて詳しくは、アナリティクス ヘルプセンターのカスタム ディメンションをご覧ください。また、Google アナリティクスのデータモデル(ユーザー、セッション、インタラクション(ヒット)で構成)を理解することも重要です。詳しくは、Google アナリティクスのデータモデルに関するアナリティクス アカデミーのレッスンをご覧ください。
  5. ただし、読み込みイベントの後に遅延読み込みされるリソースはカウントされません。