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

Philip Walton 氏
Philip Walton (Philip Walton)

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

Google I/O ウェブアプリ(IOWA)はプログレッシブ ウェブアプリで、Service Worker が提供する新機能のほとんどを活用して、アプリのようなリッチなユーザー エクスペリエンスを提供します。また、Google アナリティクスを使用して、多様で幅広いユーザーから主要なパフォーマンス データと使用パターンを取得しました。

この活用事例では、IOWA がどのように Google アナリティクスを使用して、パフォーマンスに関する重要な質問に答え、Service Worker の実世界への影響についてレポートを作成したかを説明します。

まずは質問から

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

回答したい質問がいくつかありましたが、このケーススタディの目的上、特に興味深いものを 2 つ紹介します。

1. Service Worker のキャッシュ処理のパフォーマンスは、すべてのブラウザで利用できる既存の HTTP キャッシュ メカニズムよりも優れていますか?

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

Service Worker には代替のキャッシュ機能があり、デベロッパーはこれを使用して、キャッシュに保存する内容と方法をきめ細かく制御できます。IOWA では、すべてのアセットがキャッシュされるように Service Worker の実装を最適化し、リピーターがアプリを完全にオフラインで使用できるようにしました。

とはいえ、この取り組みは、すでにブラウザのデフォルトで行われていることよりも効果的ではないでしょうか。もしそうなら、どれくらい改善できるでしょうか?1

2. Service Worker はサイトの読み込みエクスペリエンスにどのような影響を与えますか。

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

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

適切な指標の選択

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

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

IOWA では、スプラッシュ画面のカウントダウン アニメーションを表示することで、アプリの他の部分をバックグラウンドで読み込みながら、(私の意見では)ユーザーを楽しませることに成功しました。そのため、スプラッシュ画面が表示されるまでの時間をトラッキングすることは、予想される負荷のパフォーマンスを測定する方法として非常に合理的です。この値を取得するため、指標 [time to first Paint] を選択しました。

答えたい質問を決定し、その答えに役立つ指標を特定したら、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 がページを制御しているかどうかを示します。この情報を基に、レポートを分割して、Service Worker を使用した場合と使用しなかった場合の結果を比較できます。

初回ペイントまでの時間のキャプチャ

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

すでに説明したように、First Paint の時間は、ユーザーがサイトの読み込み速度を体験する最初のポイントであるため、測定すべき重要な指標です。これはユーザーの第一印象を示すもので、良い第一印象は残りのユーザー エクスペリエンスにプラスの影響を与えます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 イベントの後に延期しました。実際、これらのリクエストが他のリソースの読み込みと競合しないように、ページが読み込まれるまですべての分析データの送信を延期することにしました。

// 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();
});

上記のコードでは、Google アナリティクスに firstpaint 回ずつレポートが送られますが、それだけでは不十分です。やはり、Service Worker のステータスを追跡する必要がありました。そうしないと、Service Worker によって制御されるページと制御されていないページのファースト ペイント時間を比較できません。

Service Worker のステータスの確認

Service Worker の現在のステータスを判断するために、次の 3 つの値のいずれかを返すユーティリティ関数を作成しました。

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

この関数は、Service Worker のステータスを取得しました。次のステップでは、このステータスを Google アナリティクスに送信しているデータと関連付けました。

カスタム ディメンションを使用したカスタムデータのトラッキング

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

Service Worker のステータスは、Google アナリティクスがデフォルトで提供するディメンションではありませんが、Google アナリティクスでは、独自のカスタム ディメンションを作成して自由に定義することができます。

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

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 のステータスがこの特定のイベントに関連付けられるだけです。Service Worker のステータスはあらゆるインタラクションで把握しておくと役立つ可能性があるため、Google アナリティクスに送信されるすべてのデータにステータスを含めることをおすすめします。

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

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 サイトにアクセスした実際のウェブ トラフィックを表しています。)

まず、Service Worker のキャッシュは、すべてのブラウザで利用できる既存の HTTP キャッシュ メカニズムよりもパフォーマンスが優れているのでしょうか?

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

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

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

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

ご覧のとおり、Service Worker に制御されているアプリへのアクセスは、制御されていない訪問よりもかなり速く読み込まれました。これは、ページのリソースの大部分がキャッシュされている可能性が高いリピーターからのアクセスであってもです。また、興味深いことに、Service Worker を使用しているモバイル サイト訪問者は、平均して、新しい PC 訪問者よりも読み込みが速いことがわかりました。

「...Service Worker で制御されている場合、アプリへのアクセスは、制御されていないアクセスよりもかなり速く読み込まれます...」

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

ページの平均読み込み時間(パソコン)
Service Worker のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
が管理する リピーター 2568 30860
サポート対象 リピーター 3612 1289
サポート対象 新規ユーザー 4664 21991
ページの平均読み込み時間(モバイル)
Service Worker のステータス ユーザータイプ 平均読み込み時間(ミリ秒) サンプルサイズ
が管理する リピーター 3,760 8162
サポート対象 リピーター 4,843 676
サポート対象 新規ユーザー 6158 5779

ブラウザが Service Worker をサポートしているリピーターが、どのように制御されていない状態になるのか疑問に思われているかもしれません。これには、次のような理由が考えられます。

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

どちらの状況も比較的まれです。データの 4 番目の列の [Page Load Sample] 値を確認します。中央の行のサンプルは、他の 2 つの行よりもはるかに小さくなっています。

2 つ目の質問は、Service Worker はサイトの読み込みエクスペリエンスにどのように影響するかです。

この問いに答えるため、指標「平均イベント値」のカスタム レポートをもう 1 つ作成し、結果をフィルタして firstpaint イベントのみが含まれるようにしました。今回は、[デバイス カテゴリ] ディメンションとカスタム [Service Worker Status] ディメンションを使用しました。

私が予想していたことに反して、モバイルの 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 グラフのライブラリを使用すれば、生の結果をクエリし、独自のヒストグラムを作成できます。

たとえば、次の 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:ディメンション 2 ga:totalEvents
4 3
5 2
6 10
7 8
8 10

これらの検索結果をわかりやすい英語で表すと、次のようになります。

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

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

制御されていない(ただしサポートされている)Service Worker を使用するデスクトップでのディストリビューションは、以下のようになります。

パソコンでの初回ペイントまでの時間の分布(サポート)

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

この曲線の形状は、読み込み時間の分布に典型的なものです。以下のヒストグラムは、Service Worker がページを制御している訪問の First Paint イベントの分布を示しています。

パソコンでの初回ペイントまでの時間の分布(管理)

Service Worker がページを制御していたとき、多くの訪問者でほぼ即座に First Paint が発生し、中央値は 583 ミリ秒でした。

「...Service Worker がページを制御しているとき、多くの訪問者はすぐに最初のペイントを経験しました...」

これら 2 つの分布を比較して理解を深められるように、次のグラフでは 2 つの分布を結合したビューを表示しています。管理されていない Service Worker の訪問を示すヒストグラムは、コントロールされた訪問を示すヒストグラムの上に重ねて表示されます。この両方が、両方を合算したヒストグラムの上に重ねて表示されます。

パソコンでの初回ペイントまでの時間の分布

この結果で興味深いことに、制御された Service Worker の分布は、最初のスパイクの後もまだベル型の曲線をしていました。当初は大きく上昇し、その後緩やかに減少すると予想していましたが、この曲線に 2 つ目のピークがあることは想定していませんでした。

原因を調べたところ、Service Worker はページを制御できても、そのスレッドが非アクティブになっている場合があることがわかりました。ブラウザは、リソースを節約するためにこの処理を行います。これまでにアクセスしたすべてのサイトで、すべての Service Worker が即座にアクティブになり、準備が整っているわけではありません。これにより、分布のテールがわかります。一部のユーザーで、Service Worker スレッドの起動中に遅延が発生していました。

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

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

モバイルでの First Paint の分布にかかる時間

ほぼ即時の First Paint の時間は依然として大幅に増加しましたが、テールはかなり大きく、長くなりました。これは、モバイルでは、アイドル状態の Service Worker スレッドの開始がデスクトップよりも時間がかかることが原因と考えられます。また、firstpaint の平均時間の差が期待していたほど大きくなかった理由も説明されています(上記参照)。

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

以下に、モバイルとパソコンでのファースト ペイント時間の中央値の変動を Service Worker のステータス別に分類して示します。

初回ペイントまでの時間の中央値(ミリ秒)
Service Worker のステータス パソコン モバイル
が管理する 583 1634
サポート対象(管理対象外) 912 1933

このような分布の可視化は、Google アナリティクスでカスタム レポートを作成するよりも時間と労力がかかりますが、平均値のみの場合よりも、Service Worker がサイトのパフォーマンスに及ぼしている影響をより正確に把握できます。

Service Worker によるその他の影響

Service Worker は、パフォーマンスへの影響以外にも、Google アナリティクスで測定可能ないくつかの点でユーザー エクスペリエンスに影響を与えます。

オフライン アクセス

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

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

過去 2 年間、IOWA は Service Worker スクリプトを使用していました。Service Worker スクリプトは、ユーザーがオフラインのときに Google アナリティクスへのヒットに失敗したことを検出して、後で qt パラメータでそのヒットをリプレイするものです。

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

また、IOWA の使用中にオフラインになっていることが一般的な状況を把握するため、Google はオフラインのインタラクションを少なくとも 1 回受けるユーザーをターゲットとするセグメントを作成しました。その結果、約 5% のユーザーが利用していたことがわかりました。

プッシュ通知

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

どのような形式の通知でもそうですが、ユーザーにとって価値ある通知と煩わしい通知のバランスを見出すことが重要です。何が起きているのかを理解するには、ユーザーが通知の受け取りをオプトインしているかどうか、通知を受信したときに興味を持っているかどうか、以前にオプトインしたユーザーが設定を変更してオプトアウトしているかどうかを追跡することが重要です。

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

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

うれしいことに、ログインしたユーザーの半数以上がプッシュ通知の受け取りを選んでいます。

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

進行状況ウェブアプリが基準を満たし、ユーザーが頻繁に使用している場合、そのユーザーにはアプリのインストール バナーが表示され、ホーム画面にアプリを追加するよう促されます。

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 など)に対応するいくつかのイベントを追跡しましたが、読み込みの大部分は非同期で行われていたため、これらのイベントが発生した時点が、読み込みエクスペリエンス全体の特定の時点に対応しているとは限りません。

Google が追跡していない(もっともらしい)負荷関連の主なイベントは、スプラッシュ画面が表示されなくなり、ユーザーにページ コンテンツが表示されるようになった時点です。

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

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

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

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 がない場合よりもパフォーマンスは良好です。
  • モバイルでは、非アクティブな Service Worker の起動時間がパソコンよりも長くなりました。

ある特定のアプリケーションで観察されたパフォーマンスの向上は、より大きな開発者コミュニティに報告するのには一般的に有用ですが、これらの結果は、IOWA のサイトの種類(イベントサイト)と IOWA のオーディエンスの種類(主に開発者)に固有であることを覚えておいてください。

アプリケーションに Service Worker を実装する場合は、独自の測定戦略を実装して、独自のパフォーマンスを評価し、将来の回帰を防止することが重要です。もしそうなら、全員が恩恵を受けられるように結果を共有してください。

脚注

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