서비스 워커의 실제 성능에 미치는 영향 측정

서비스 워커의 가장 큰 이점 중 하나는 (적어도 성능 관점에서) 애셋 캐싱을 사전에 제어할 수 있다는 것입니다. 필요한 리소스를 모두 캐시할 수 있는 웹 애플리케이션은 재방문자에게 훨씬 더 빠르게 로드됩니다. 그러나 실제 사용자에게는 이러한 이점이 실제로 어떻게 될까요? 그리고 이를 어떻게 측정하나요?

Google I/O 웹 앱 (줄여서 IOWA)은 서비스 워커가 제공하는 대부분의 새로운 기능을 활용하여 사용자에게 앱과 같은 풍부한 경험을 제공하는 프로그레시브 웹 앱입니다. 또한 Google 애널리틱스를 사용하여 대규모의 다양한 사용자층에서 핵심 실적 데이터와 사용 패턴을 수집했습니다.

이 케이스 스터디에서는 IOWA가 Google 애널리틱스를 사용하여 주요 성능 질문에 답하고 서비스 워커의 실제적인 영향을 보고한 방법을 살펴봅니다.

질문으로 시작하기

웹사이트 또는 애플리케이션에서 애널리틱스를 구현할 때는 먼저 수집할 데이터에서 답을 찾고자 하는 질문을 식별하는 것이 중요합니다.

답변해야 할 몇 가지 질문이 있지만 이 사례 연구의 목적에 맞게 더 흥미로운 두 가지 질문에 집중해 보겠습니다.

1. 모든 브라우저에서 사용할 수 있는 기존 HTTP 캐싱 메커니즘보다 서비스 워커 캐싱 성능이 더 우수한가요?

브라우저가 요청을 캐시하고 재방문 시 즉시 페이지를 제공할 수 있기 때문에 재방문자의 페이지 로드 속도는 신규 방문자보다 빠를 것으로 예상됩니다.

서비스 워커는 개발자가 캐싱을 수행하는 대상과 방법에 대한 세밀한 제어를 제공하는 대체 캐싱 기능을 제공합니다. IOWA에서는 모든 자산이 캐시되어 재방문자들이 앱을 오프라인으로 이용할 수 있도록 서비스 워커 구현을 최적화했습니다.

하지만 이렇게 해도 브라우저가 기본적으로 실행하는 것보다 나을까요? 그렇다면 얼마나 더 잘 할 수 있을까요? 1

2. 서비스 워커는 사이트 로드 경험에 어떤 영향을 미치나요?

즉, 기존 페이지 로드 측정항목으로 측정한 실제 로드 시간과 관계없이 사이트가 로드되는 속도가 얼마나 빠른지 느껴지는지를 나타냅니다.

경험에 대한 감정에 관한 질문에 답하는 것은 분명히 쉬운 일이 아니며, 이러한 주관적인 감정을 완벽하게 나타내는 측정항목은 없습니다. 그렇지만 다른 측정항목보다 더 나은 측정항목도 있으므로 적절한 측정항목을 선택하는 것이 중요합니다.

적절한 측정항목 선택

Google 애널리틱스는 기본적으로 사이트 방문자의 1% 에 대해 Navigation Timing API를 통해 페이지 로드 시간을 추적하고 평균 페이지 로드 시간과 같은 측정항목을 통해 이 데이터를 제공합니다.

평균 페이지 로드 시간은 첫 번째 질문에 답하는 데 적합한 측정항목이지만 두 번째 질문에 답하는 데는 적합하지 않습니다. 우선 load 이벤트는 사용자가 실제로 앱과 상호작용할 수 있는 순간과 반드시 일치하지는 않습니다. 또한 로드 시간이 정확히 동일한 두 앱이 로드되는 방식이 매우 다르게 느껴질 수도 있습니다. 예를 들어 스플래시 화면이나 로딩 표시기가 있는 사이트는 빈 페이지를 몇 초만 표시하는 사이트보다 훨씬 빠르게 로드됩니다.

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 라이브러리를 비동기식으로 다운로드합니다.

가운데 부분에는 다음 두 줄이 포함됩니다.

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

이 두 가지 명령어는 사이트를 방문하는 사용자가 방문한 페이지를 추적하지만 그 이상은 추적하지 않습니다. 사용자 상호작용을 추가로 추적하려면 직접 추적해야 합니다.

IOWA의 경우 다음 두 가지를 추가로 추적하고자 했습니다.

  • 페이지 로드가 처음 시작된 시점부터 픽셀이 화면에 표시되는 시점까지 경과한 시간입니다.
  • 서비스 워커가 페이지를 제어하는지 여부입니다. 이 정보를 사용하여 보고서를 분류하여 서비스 워커 유무에 따른 결과를 비교할 수 있습니다.

첫 페인트까지의 시간 캡처

일부 브라우저는 첫 번째 픽셀이 화면에 그려지는 정확한 시간을 기록하고 개발자가 이 시간을 사용할 수 있도록 합니다. 이 값을 Navigation Timing API를 통해 노출되는 navigationStart 값과 비교하면 사용자가 페이지를 처음 요청한 시점과 처음으로 무언가를 보았을 때까지의 경과 시간을 매우 정확하게 파악할 수 있습니다.

앞서 언급했듯이 TTFP는 사용자가 사이트의 로드 속도를 처음 경험하는 지점이므로 측정해야 하는 중요한 측정항목입니다. 첫인상은 사용자가 처음 접하는 것이며, 좋은 첫인상은 나머지 사용자 환경에 긍정적인 영향을 미칠 수 있습니다.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 이벤트가 끝난 후에 연기했습니다. 사실, 요청이 다른 리소스의 로드와 경쟁하지 않도록 페이지가 로드된 이후까지 모든 분석 데이터 전송을 연기하기로 결정했습니다.

// 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번 보고하지만, 이것이 전부가 아닙니다. 여전히 서비스 워커 상태를 추적해야 했습니다. 그러지 않으면 서비스 워커가 제어하는 페이지와 제어되지 않는 페이지의 첫 번째 페인트 시간을 비교할 수 없습니다.

서비스 워커 상태 확인

서비스 워커의 현재 상태를 확인하기 위해 다음 세 가지 값 중 하나를 반환하는 유틸리티 함수를 만들었습니다.

  • managed: 서비스 워커가 페이지를 제어하고 있습니다. IOWA의 경우 모든 애셋이 캐시되었으며 페이지가 오프라인에서 작동한다는 의미이기도 합니다.
  • 지원됨: 브라우저가 서비스 워커를 지원하지만 서비스 워커가 아직 페이지를 제어하고 있지 않습니다. 신규 방문자에게 예상되는 상태입니다.
  • 지원되지 않음: 사용자의 브라우저가 서비스 워커를 지원하지 않습니다.
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();
});

앞서 언급했듯이 모든 조회와 함께 서비스 워커 상태 측정기준을 전송하면 측정항목을 보고할 때 이를 사용할 수 있습니다.

보시다시피 IOWA의 총 페이지 조회 중 거의 85% 가 서비스 워커를 지원하는 브라우저에서 발생했습니다.

결과: 질문에 대한 답변

질문에 답변하기 위한 데이터 수집을 시작하면 해당 데이터에 대해 보고하여 결과를 확인할 수 있습니다. (참고: 여기에 표시된 모든 Google 애널리틱스 데이터는 2016년 5월 16~22일에 IOWA 사이트에 발생한 실제 웹 트래픽을 나타냅니다.)

첫 번째 질문은 서비스 워커 캐싱이 모든 브라우저에서 사용할 수 있는 기존 HTTP 캐싱 메커니즘보다 성능이 우수한가?였습니다.

이 질문에 답하기 위해 다양한 측정기준에서 평균 페이지 로드 시간 측정항목을 살펴보는 맞춤 보고서를 만들었습니다. 이 측정항목은 모든 초기 리소스가 다운로드된 후에만 load 이벤트가 실행되므로 이 질문에 답하는 데 적합합니다. 따라서 사이트의 모든 중요한 리소스의 총 로드 시간이 직접 반영됩니다.5

선택한 측정기준은 다음과 같습니다.

  • 맞춤 서비스 워커 상태 측정기준
  • 사용자 유형: 사용자가 사이트를 처음 방문하는지 또는 재방문하는지 나타냅니다. 참고: 신규 방문자에게는 캐시된 리소스가 없으며 재방문자에게는 있을 수 있습니다.
  • 기기 카테고리: 모바일과 데스크톱 간의 결과를 비교할 수 있습니다.

서비스 워커와 관련 없는 요소가 로드 시간 결과를 왜곡할 가능성을 방지하기 위해 서비스 워커를 지원하는 브라우저만 포함하도록 쿼리를 제한했습니다.

보시다시피 서비스 워커로 제어된 앱 방문은 제어되지 않은 방문보다 훨씬 더 빠르게 로드되었습니다. 페이지 리소스의 대부분을 캐시했을 가능성이 높은 재방문자 방문도 마찬가지였습니다. 또한 서비스 워커가 있는 모바일 방문자가 신규 데스크톱 방문자보다 평균적으로 더 빠르게 로드되는 것도 흥미롭습니다.

"…서비스 워커에 의해 제어되는 앱 방문이 제어되지 않는 방문보다 훨씬 더 빠르게 로드되었습니다."

다음 두 표에서 자세한 내용을 확인하세요.

평균 페이지 로드 시간 (데스크톱)
서비스 워커 상태 사용자 유형 평균 페이지 로드 시간(밀리초) 샘플 크기
제어함 재방문자 2568 30860
지원됨 재방문자 3612 1289
지원됨 신규 방문자 4664 21991
평균 페이지 로드 시간 (모바일)
서비스 워커 상태 사용자 유형 평균 페이지 로드 시간(밀리초) 샘플 크기
제어함 재방문자 3760 8162
지원됨 재방문자 4843 676
지원됨 신규 방문자 6158 5779

브라우저에서 서비스 워커를 지원하는 재방문자가 제어되지 않은 상태일 수 있는 이유는 무엇일까요? 여기에는 다음과 같은 이유가 있을 수 있습니다.

  • 서비스 워커가 초기화를 완료하기 전에 사용자가 첫 방문에서 페이지를 떠났습니다.
  • 사용자가 개발자 툴을 통해 서비스 워커를 제거했습니다.

이러한 상황은 모두 비교적 드뭅니다. 데이터에서 네 번째 열의 페이지 로드 샘플 값을 보면 이를 확인할 수 있습니다. 가운데 행의 샘플 수가 다른 두 행보다 훨씬 적습니다.

두 번째 질문은 서비스 워커가 사이트 로드 환경에 어떤 영향을 미치나요?였습니다.

이 질문에 답하기 위해 평균 이벤트 가치 측정항목에 관한 또 다른 맞춤 보고서를 만들고 firstpaint 이벤트만 포함하도록 결과를 필터링했습니다. 측정기준인 기기 카테고리와 맞춤 서비스 워커 상태 측정기준을 사용했습니다.

예상과 달리 모바일의 서비스 워커는 전체 페이지 로드에 미치는 영향보다 첫 번째 페인트 시간에 미치는 영향이 훨씬 적었습니다.

"…모바일의 서비스 워커는 전체 페이지 로드에 미치는 영향보다 첫 번째 페인트 시간에 미치는 영향이 훨씬 적었습니다."

그 이유를 알아내려면 데이터를 더 깊이 들여다봐야 합니다. 평균은 일반적인 개요와 대략적인 흐름을 파악하는 데 유용하지만, 다양한 사용자를 대상으로 이러한 수치가 어떻게 분류되는지 정확하게 파악하려면 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 요청 구성은 제어되지 않는 서비스 워커가 있는 데스크톱에서 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 값이 5ms인 이벤트가 2개 있습니다.
  • firstpaint 값이 6ms인 이벤트가 10개 있었습니다.
  • firstpaint 값이 7ms인 이벤트가 8개 있었습니다.
  • firstpaint value이 8ms인 이벤트가 10개 있었습니다.

이러한 결과를 바탕으로 모든 이벤트의 firstpaint 값을 추정하고 분포의 히스토그램을 만들 수 있습니다. 실행한 각 쿼리에 대해 이 작업을 수행했습니다.

제어되지 않지만 지원되는 서비스 워커가 있는 데스크톱에서 배포가 어떻게 표시되는지 다음을 참고하세요.

데스크톱의 첫 페인트 시간 분포 (지원됨)

위 분포의 중앙값 firstpaint 시간은 912ms입니다.

이 곡선의 모양은 로드 시간 분포에서 매우 일반적입니다. 서비스 워커가 페이지를 제어한 방문의 첫 번째 페인트 이벤트 분포를 보여주는 아래 히스토그램과 비교해 보세요.

데스크톱의 첫 페인트 분포까지의 시간 (제어됨)

서비스 워커가 페이지를 제어할 때 많은 방문자가 거의 즉각적인 첫 번째 페인트를 경험했으며, 이때의 중앙값은 583ms였습니다.

"…서비스 워커가 페이지를 제어할 때 많은 방문자가 거의 즉각적인 첫 번째 페인트를 경험했습니다."

두 분포가 서로 어떻게 다른지 더 잘 이해할 수 있도록 다음 차트에는 두 분포가 병합된 보기가 나와 있습니다. 제어되지 않은 서비스 워커 방문을 보여주는 히스토그램이 제어된 방문을 보여주는 히스토그램 위에 겹쳐 표시되고, 이 두 히스토그램은 모두 두 가지를 합친 히스토그램 위에 겹쳐 표시됩니다.

데스크톱의 첫 페인트까지의 시간 분포

이 결과에서 흥미로운 점은 제어된 서비스 워커가 있는 분포는 초기 급증 후에도 여전히 종 모양 곡선을 보였다는 것입니다. 처음에는 큰 급증이 발생한 후 점진적으로 감소할 것으로 예상했지만 곡선에 두 번째 최대치가 나타날 것으로는 예상하지 못했습니다.

이 문제의 원인을 조사한 결과, 서비스 워커가 페이지를 제어할 수 있지만 스레드가 비활성 상태일 수 있다는 사실을 알게 되었습니다. 브라우저는 리소스를 절약하기 위해 이렇게 합니다. 방문한 적이 있는 모든 사이트의 모든 서비스 워커가 언제든지 활성화되고 준비되어 있을 필요는 없기 때문입니다. 이는 분포의 꼬리를 설명합니다. 일부 사용자의 경우 서비스 워커 스레드가 시작되는 동안 지연이 발생했습니다.

하지만 분포에서 볼 수 있듯이 이러한 초기 지연이 있더라도 서비스 워커가 있는 브라우저는 네트워크를 통과하는 브라우저보다 콘텐츠를 더 빠르게 전송했습니다.

모바일에서 표시되는 모습은 다음과 같습니다.

모바일의 첫 페인트까지의 시간 분포

거의 즉각적인 첫 번째 페인트 시간은 여전히 상당히 증가했지만, 그 뒤의 꼬리는 훨씬 더 크고 길었습니다. 이는 모바일에서 유휴 서비스 워커 스레드를 시작하는 데 데스크톱보다 시간이 더 걸리기 때문일 수 있습니다. 또한 평균 firstpaint 시간 간의 차이가 예상만큼 크지 않은 이유도 설명해 줍니다 (위에서 설명).

"…모바일에서는 유휴 서비스 워커 스레드를 시작하는 데 데스크톱보다 시간이 더 오래 걸립니다."

다음은 서비스 워커 상태별로 그룹화된 모바일 및 데스크톱에서 첫 페인트 시간 중앙값의 변동에 대한 분석입니다.

첫 페인트까지의 시간 중앙값 (밀리초)
서비스 워커 상태 데스크톱 모바일
제어함 583 1634
지원됨(제어 불가) 912 1933

이러한 분포 시각화를 빌드하는 데는 Google 애널리틱스에서 맞춤 보고서를 만드는 것보다 약간 더 많은 시간과 노력이 필요했지만, 평균만 사용했을 때보다 서비스 워커가 사이트 실적에 미치는 영향을 훨씬 더 잘 파악할 수 있었습니다.

서비스 워커의 기타 영향

서비스 워커는 성능에 미치는 영향 외에도 Google 애널리틱스로 측정할 수 있는 다른 여러 가지 방식으로 사용자 환경에 영향을 미칩니다.

오프라인 액세스

서비스 워커를 사용하면 사용자가 오프라인 상태에서도 사이트와 상호작용할 수 있습니다. 모든 프로그레시브 웹 앱에는 어느 정도의 오프라인 지원이 필요할 수 있지만, 얼마나 중요한지는 오프라인에서 발생하는 사용량에 따라 크게 달라집니다. 그렇다면 이를 어떻게 측정할 수 있을까요?

Google 애널리틱스로 데이터를 전송하려면 인터넷에 연결되어 있어야 하지만 상호작용이 발생한 정확한 시점에 데이터를 전송할 필요는 없습니다. Google 애널리틱스는 qt 매개변수를 통해 시간 오프셋을 지정하여 사후에 상호작용 데이터를 전송하는 기능을 지원합니다.

지난 2년 동안 IOWA는 사용자가 오프라인 상태일 때 Google 애널리틱스의 실패한 조회를 감지하고 나중에 qt 매개변수로 재생하는 서비스 워커 스크립트를 사용해 왔습니다.

사용자가 온라인 상태인지 오프라인 상태인지 추적하기 위해 Online이라는 맞춤 측정기준을 만들고 navigator.onLine 값으로 설정했습니다. 그런 다음 onlineoffline 이벤트를 리슨하고 그에 따라 측정기준을 업데이트했습니다.

또한 IOWA를 사용하는 동안 사용자가 오프라인 상태인 경우가 얼마나 흔한지 파악하기 위해 오프라인 상호작용이 하나 이상 있는 사용자를 타겟팅하는 세그먼트를 만들었습니다. 사용자의 거의 5%가 이 기능을 사용하고 있었습니다.

푸시 알림

서비스 워커를 통해 사용자는 푸시 알림 수신에 동의할 수 있습니다. 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에서 수집한 분석 데이터는 매우 소중했습니다. 하지만 지나간 후야 하는 실수는 항상 미처 깨닫고 나중에는 개선할 수 있는 기회로 이어집니다. 올해 분석을 마친 후 유사한 전략을 구현하려는 독자라면 다음과 같은 두 가지 사항을 다르게 적용했으면 좋겠습니다.

1. 로드 경험과 관련된 더 많은 이벤트 추적

기술 측정항목 (예: HTMLImportsLoaded, WebComponentsReady 등)에 해당하는 여러 이벤트를 추적했지만 많은 양의 로드가 비동기식으로 수행되었기 때문에 이러한 이벤트가 실행된 시점이 전체 로드 경험의 특정 시점과 일치하지 않을 수 있습니다.

추적하지 않았지만 추적하고 싶은 기본 로드 관련 이벤트는 스플래시 화면이 사라지고 사용자가 페이지 콘텐츠를 볼 수 있는 지점입니다.

2. IndexedDB에 애널리틱스 클라이언트 ID 저장

기본적으로 analytics.js는 클라이언트 ID 필드를 브라우저의 쿠키에 저장합니다. 안타깝게도 서비스 워커 스크립트는 쿠키에 액세스할 수 없습니다.

이로 인해 알림 추적을 구현하려고 할 때 문제가 발생했습니다. 사용자에게 알림이 전송될 때마다 측정 프로토콜을 통해 서비스 워커에서 이벤트를 전송한 다음, 사용자가 알림을 클릭하여 앱으로 돌아오는 경우 해당 알림의 재참여 성공을 추적하려고 했습니다.

utm_source 캠페인 매개변수를 통해 일반적으로 알림의 성공 여부를 추적할 수는 있었지만 특정 재참여 세션을 특정 사용자에게 연결할 수는 없었습니다.

이 제한을 해결하려면 추적 코드에 IndexedDB를 통해 클라이언트 ID를 저장하면 서비스 워커 스크립트에서 이 값에 액세스할 수 있었습니다.

3. 서비스 워커가 온라인/오프라인 상태를 보고하도록 허용

navigator.onLine를 검사하면 브라우저가 라우터나 근거리 통신망에 연결할 수 있는지는 알 수 있지만 사용자가 실제로 연결되어 있는지는 알 수 없습니다. 그리고 오프라인 분석 서비스 워커 스크립트는 (수정하거나 실패로 표시하지 않고) 단순히 실패한 적중을 재생했을 뿐이므로 오프라인 사용량을 실제보다 적게 보고했을 것입니다.

향후 navigator.onLine의 상태와 초기 네트워크 오류로 인해 서비스 워커가 조회를 재생했는지 여부를 모두 추적해야 합니다. 이를 통해 실제 오프라인 사용량을 더 정확하게 파악할 수 있습니다.

요약

이 사례 연구에서는 서비스 워커를 사용하면 다양한 브라우저, 네트워크, 기기에서 Google I/O 웹 앱의 로드 성능이 실제로 개선된다는 것을 보여줍니다. 또한 광범위한 브라우저, 네트워크 및 장치에 걸친 로드 데이터의 분포를 살펴보면 이 기술이 실제 시나리오를 어떻게 처리하는지에 대해 훨씬 더 많은 통찰력을 얻을 수 있고, 예상하지 못했던 성능 특성을 발견할 수 있음을 보여주었습니다.

IOWA 연구에서 얻은 주요 사항은 다음과 같습니다.

  • 신규 방문자와 재방문자 모두 서비스 워커가 페이지를 제어할 때 페이지가 서비스 워커 없이 페이지를 제어할 때보다 평균적으로 훨씬 더 빠르게 로드되었습니다.
  • 서비스 워커가 제어하는 페이지 방문이 많은 사용자에게 거의 즉시 로드되었습니다.
  • 서비스 워커는 비활성 상태일 때 시작하는 데 약간의 시간이 걸렸습니다. 그러나 비활성 서비스 워커가 서비스 워커가 없는 것보다 여전히 더 나은 성능을 보였습니다.
  • 비활성 서비스 워커의 시작 시간이 모바일에서 데스크톱보다 더 길었습니다.

특정 애플리케이션에서 관찰된 성능 향상은 일반적으로 더 큰 개발자 커뮤니티에 보고하는 데 유용하지만, 이러한 결과는 IOWA의 사이트 유형 (이벤트 사이트) 및 IOWA의 잠재고객 유형 (주로 개발자)에 따라 다르다는 점에 유의해야 합니다.

애플리케이션에서 서비스 워커를 구현하는 경우 자체 성능을 평가하고 향후 회귀를 방지할 수 있도록 자체 측정 전략을 구현하는 것이 중요합니다. 그렇다면 모든 사용자가 혜택을 누릴 수 있도록 결과를 공유해 주세요.

각주

  1. 서비스 워커 캐시 구현의 성능을 HTTP 캐시만 사용하는 사이트의 성능과 비교하는 것은 완전히 공정하지 않습니다. 서비스 워커용으로 IOWA를 최적화하고 있었기 때문에 HTTP 캐시 최적화에 많은 시간을 들이지 않았습니다. 그렇게 했다면 결과가 달라졌을 것입니다. HTTP 캐시를 위해 사이트를 최적화하는 방법을 자세히 알아보려면 콘텐츠를 효율적으로 최적화를 참고하세요.
  2. 사이트에서 스타일과 콘텐츠를 로드하는 방식에 따라 브라우저가 콘텐츠나 스타일이 사용 가능해지기 전에 페인트할 수 있습니다. 이 경우 firstpaint은 빈 흰색 화면에 해당할 수 있습니다. firstpaint를 사용하는 경우 사이트 리소스 로드 시 의미 있는 지점과 일치하는지 확인하는 것이 중요합니다.
  3. 기술적으로는 이벤트 대신 타임스탬프 히트를 전송하여 이 정보를 캡처할 수 있습니다 (기본적으로 비상호작용임). 실제로 타이밍 히트는 이러한 로드 측정항목을 추적하기 위해 특히 Google 애널리틱스에 추가되었습니다. 하지만 타이밍 히트는 처리 시점에 과도하게 샘플링되며 값은 세그먼트에서 사용할 수 없습니다. 이러한 현재의 한계를 감안할 때 비상호작용 이벤트가 더 적합합니다.
  4. Google 애널리틱스에서 맞춤 측정기준에 제공할 범위를 자세히 알아보려면 애널리틱스 고객센터의 맞춤 측정기준 섹션을 참고하세요. 사용자, 세션, 상호작용 (히트)으로 구성된 Google 애널리틱스 데이터 모델을 이해하는 것도 중요합니다. 자세한 내용은 Google 애널리틱스 데이터 모델에 관한 애널리틱스 아카데미 강의를 시청하세요.
  5. 로드 이벤트 후에 지연 로드된 리소스는 고려되지 않습니다.