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

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>

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

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

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

これら 2 つのコマンドは、ユーザーがアクセスしたページをトラッキングしますが、それ以上のことは行いません。それ以外のユーザー インタラクションもトラッキングする場合は、ご自身でトラッキングする必要があります。

IOWA については、さらに次の 2 つの点を追跡しました。

  • ページの最初に読み込みが開始されてから、ピクセルが画面に表示されるまでの経過時間。
  • Service Worker がページを制御しているかどうか。この情報を使ってレポートをセグメント化し、Service Worker の有無による結果を比較できます。

First Paint までの時間のキャプチャ

ブラウザによっては、最初のピクセルが画面に描画された正確な時刻を記録し、その時間をデベロッパーが利用できるようにしているブラウザもあります。この値は、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 イベント後まで延期しました。実際、他のリソースの読み込みとリクエストが競合しないように、すべての分析データの送信をページの読み込み後まで延期することにしました。

// 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 で制御されているページと制御されていないページの First Paint 時間を比較できません。

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 というカスタム ディメンションを作成し、スコープを hit(インタラクションごと)に設定しました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 アナリティクスにデータを送信する前に、トラッカー オブジェクト自体にカスタム ディメンション値を設定します。

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 キャッシュ メカニズムよりもパフォーマンスが高いのか?」というものです。

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

ディメンションは次のとおりです。

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

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

ご覧のとおり、Service Worker で制御している場合、アプリのアクセスは管理されていない訪問よりもかなり速く読み込まれました。これには、ページのリソースのほとんどがキャッシュされている可能性が高いリピーターからのアクセスも含まれます。また、Service Worker を利用するモバイル ユーザーは、PC からの新規訪問者より、平均して読み込みが速くなっています。

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

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

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

Service Worker をサポートするブラウザを使用しているサイト訪問者が、どうして制御されていない状態になるのか、不思議に思われるかもしれません。これには、いくつかの理由が考えられます。

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

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

2 つ目の質問は、「Service Worker はサイトの読み込みエクスペリエンスにどのような影響を与えるのでしょうか?」です。

そこで、この疑問に答えるために、指標「平均イベント値を指定し、firstpaint イベントのみが含まれるように結果をフィルタしました。ここでは、デバイス カテゴリ ディメンションと、カスタム Service Worker ステータス ディメンションを使用しました。

私の予想に反して、モバイルの Service Worker はページ全体の読み込み時間よりも、初回ペイントまでの時間にほとんど影響がありませんでした。

「...モバイルの Service Worker は、ページ全体の読み込み時間よりも、First Paint までの時間にほとんど影響がありませんでした。」

その理由を探るためには、データを深く掘り下げる必要があります。平均は一般的な概要や大まかなストロークには適していますが、こうした数値が幅広いユーザーにどのように分類されているかを把握するには、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 ミリ秒のイベントが 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 がページを制御している最中に、多くの訪問者がすぐに最初のペイントを実行しました。その中央値は 583 ms です。

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

これら 2 つの分布の比較をより深く理解できるように、次のグラフに 2 つの分布を統合して表示しています。管理対象外の Service Worker 訪問を示すヒストグラムは、管理対象訪問を示すヒストグラムの上に重ねて表示されています。また、両方が結合されたヒストグラムの上に重ねられています。

パソコンでの First Paint の分布にかかる時間

この結果で興味深いことに気付いたことの一つは、制御された Service Worker の分布は、最初の急増の後、依然としてベル状の曲線になっていたことです。最初は大きく急増し、その後徐々に止まると思っていましたが、カーブの 2 つ目のピークは予想していませんでした。

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

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

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

モバイルでの First Paint の分布

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

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

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

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

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

Service Worker によるその他の影響

Service Worker は、パフォーマンスへの影響だけでなく、Google アナリティクスで測定可能な他のいくつかの方法でユーザー エクスペリエンスにも影響を及ぼします。

オフライン アクセス

Service Worker を使用すると、ユーザーはオフラインでサイトを操作できます。プログレッシブ ウェブアプリの場合、なんらかのオフライン サポートはおそらく重要ですが、実際のケースでどの程度重要であるかは、オフラインでの使用率によって決まります。どうやって測定するのでしょうか?

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

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

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

また、IOWA を使用しているユーザーがオフラインになる頻度を把握するために、オフラインでのインタラクションが少なくとも 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 など)をトラッキングしましたが、読み込みの大部分は非同期で行われたため、これらのイベントが発生したポイントが、読み込みエクスペリエンス全体の特定の時点に対応しているとは限りません。

私たちが追跡しなかった負荷関連の主要なイベントは、スプラッシュ画面が消えて、ユーザーがページ コンテンツを見ることができた時点です(ただし、そうであるべきでした)。

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 の起動時間は、デスクトップよりもモバイルで長くなりました。

ある特定のアプリケーションで観察されたパフォーマンスの向上は、一般的に、大規模な開発者コミュニティに報告するのに役立ちますが、これらの結果は、IOWA のサイトの種類(イベントサイト)と読者のタイプ(主に開発者)に固有であることに留意することが重要です。

アプリケーションに Service Worker を実装する場合は、独自のパフォーマンスを評価し、将来の回帰を防止できるように、独自の測定戦略を実装することが重要です。その場合は、結果を共有して、誰もがメリットを得られるようにしましょう。

脚注

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