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

적어도 성능 면에서 서비스 워커의 가장 중요한 이점 중 하나는 자산 캐싱을 사전에 제어할 수 있다는 것입니다. 필요한 모든 리소스를 캐시할 수 있는 웹 애플리케이션은 재방문자가 훨씬 더 빠르게 로드되어야 합니다. 그런데 이러한 이점이 실제 사용자에게 실제로 어떤 모습일까요? 이를 어떻게 측정할 수 있을까요?

Google I/O 웹 앱 (IOWA)은 서비스 워커가 제공하는 새로운 기능 대부분을 활용하여 사용자에게 풍부한 앱 같은 환경을 제공하는 프로그레시브 웹 앱입니다. 또한 Google 애널리틱스를 사용하여 크고 다양한 사용자 잠재고객으로부터 주요 실적 데이터 및 사용 패턴을 파악했습니다.

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

질문부터 시작하기

웹사이트 또는 애플리케이션에 애널리틱스를 구현할 때는 먼저 수집할 데이터를 바탕으로 답을 찾아야 합니다.

먼저 몇 가지 질문에 답하고 싶었지만 이 우수사례의 목적에 따라 가장 흥미로운 두 가지 질문에 초점을 맞춰 보겠습니다.

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

브라우저가 요청을 캐시하고 재방문 시 즉시 제공할 수 있으므로 이미 신규 방문자보다 재방문자의 페이지가 더 빠르게 로드될 것으로 예상됩니다.

서비스 워커는 개발자가 캐싱의 대상과 방법을 정밀하게 제어할 수 있는 대체 캐싱 기능을 제공합니다. IOWA에서는 모든 자산이 캐시될 수 있도록 서비스 워커 구현을 최적화했습니다. 따라서 재방문자는 앱을 완전히 오프라인으로 사용할 수 있습니다.

하지만 이러한 노력이 브라우저가 이미 기본적으로 실행하는 것보다 더 나을까요? 그렇다면 훨씬 더 나아졌을까요? 1

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

즉, 기존 페이지 로드 측정항목으로 측정한 실제 로드 시간과 관계없이 사이트가 로드되는 것 같다는 느낌이 느껴지는가?

어떤 경험이 어떻게 느껴지는지에 대한 질문에 답하는 것은 분명히 쉬운 일이 아니며, 어떤 측정항목도 이러한 주관적인 감정을 완벽하게 나타내지 않을 것입니다. 물론 다른 측정항목보다 실적이 뛰어난 측정항목도 있으므로 올바른 측정항목을 선택하는 것이 중요합니다.

적절한 측정항목 선택

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

평균 페이지 로드 시간은 첫 번째 질문에 답하는 데 유용한 측정항목이지만 두 번째 질문에 답변할 때는 그다지 좋은 측정항목은 아닙니다. 우선 load 이벤트가 사용자가 실제로 앱과 상호작용할 수 있는 순간과 항상 일치하지는 않습니다. 게다가 로드 시간이 정확히 동일한 두 앱은 로드가 훨씬 다른 것처럼 느릴 수 있습니다. 예를 들어 스플래시 화면이나 로딩 표시기가 있는 사이트는 몇 초 동안만 빈 페이지를 표시하는 사이트보다 훨씬 빠르게 로드되는 것처럼 느껴질 수 있습니다.

아이오와에서는 앱의 나머지 부분이 백그라운드에서 로드되는 동안 사용자에게 즐거움을 주는 스플래시 화면 카운트다운 애니메이션을 보여주었습니다. 따라서 인지된 로드 성능을 측정하는 방법으로 스플래시 화면이 표시되는 데 걸리는 시간을 추적하는 것이 훨씬 더 합리적입니다. 이 값을 얻기 위해 첫 페인트까지의 시간 측정항목을 선택했습니다.

답을 얻고 싶은 질문을 정하고 질문에 답하는 데 유용한 측정항목을 파악했으니, 이제 Google 애널리틱스를 구현하고 측정을 시작해야 했습니다.

애널리틱스 구현

Google 애널리틱스를 사용해 본 적이 있다면 권장되는 자바스크립트 추적 코드에 익숙할 것입니다. 상태 메시지가 표시됩니다.

<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 값과 비교하면 사용자가 페이지를 처음 요청한 시점과 처음 페이지를 본 시점 사이에 경과한 시간을 매우 정확하게 파악할 수 있습니다.

이미 언급했듯이 첫 페인트까지의 시간은 사용자가 사이트의 로드 속도를 경험하는 첫 번째 지점이므로 측정해야 할 중요한 측정항목입니다. 사용자에게 얻는 첫인상으로, 좋은 첫인상은 나머지 사용자 경험에 긍정적인 영향을 미칠 수 있습니다.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();

위의 코드가 실행되는 시점에 따라 픽셀이 화면에 이미 그려졌을 수도 있고 그렇지 않을 수도 있습니다. 첫 번째 페인트가 발생한 후 항상 이 코드를 실행할 수 있도록 load 이벤트 이후까지 sendTimeToFirstPaint() 호출을 연기했습니다. 실제로 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();
});

위 코드는 Google 애널리틱스에 firstpaint회 보고하지만, 그것이 전부에 불과합니다. 여전히 서비스 워커 상태를 추적해야 했습니다. 그렇지 않으면 서비스 워커가 제어하는 페이지와 제어되지 않는 페이지의 첫 페인트 시간을 비교할 수 없습니다.

서비스 워커 상태 확인

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

  • managed: 서비스 워커가 페이지를 제어하고 있습니다. IOWA의 경우 모든 자산이 캐시되었으며 페이지가 오프라인으로 작동함을 의미합니다.
  • supported: 브라우저가 서비스 워커를 지원하지만 서비스 워커가 아직 페이지를 제어하지 않습니다. 신규 방문자에게 예상되는 상태입니다.
  • unsupported: 사용자의 브라우저가 서비스 워커를 지원하지 않습니다.
function getServiceWorkerStatus() {
  if ('serviceWorker' in navigator) {
    return navigator.serviceWorker.controller ? 'controlled' : 'supported';
  } else {
    return 'unsupported';
  }
}

이 함수가 서비스 워커 상태를 얻었습니다. 다음 단계는 이 상태를 Google 애널리틱스로 전송 중인 데이터와 연결하는 것이었습니다.

맞춤 측정기준으로 맞춤 데이터 추적하기

기본적으로 Google 애널리틱스에서는 사용자, 세션 또는 상호작용의 속성에 따라 총 트래픽을 여러 그룹으로 세분화할 수 있는 다양한 방법을 제공합니다. 이러한 속성을 측정기준이라고 합니다. 웹 개발자가 관심을 갖는 일반적인 측정기준은 브라우저, 운영체제, 기기 카테고리 등입니다.

서비스 워커의 상태는 Google 애널리틱스에서 기본적으로 제공하는 측정기준이 아닙니다. 하지만 Google 애널리틱스에서는 자체 맞춤 측정기준을 만들고 원하는 대로 정의할 수 있습니다.

IOWA의 경우 서비스 워커 상태라는 맞춤 측정기준을 만들고 범위를 조회 (즉, 상호작용별)로 설정했습니다.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()
});

이 경우 작동하지만 서비스 워커의 상태를 이 특정 이벤트와만 연결합니다. 서비스 워커 상태는 상호작용 시 알아두면 유용할 수 있으므로 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 캐싱 메커니즘보다 더 뛰어난가?였습니다.

Google은 이 질문에 대한 답을 찾기 위해 다양한 측정기준에서 평균 페이지 로드 시간 측정항목을 분석한 맞춤 보고서를 만들었습니다. 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 차트 라이브러리를 사용하면 원시 결과를 쿼리한 다음 직접 히스토그램을 구성할 수 있습니다.

예를 들어 제어되지 않는 서비스 워커를 사용하여 데스크톱에서 firstpaint 값의 배포를 가져오는 데 다음 API 요청 구성이 사용되었습니다.

{
  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 요청은 다음과 같은 값의 배열을 반환합니다 (참고: 처음 다섯 개의 결과일 뿐). 결과가 작은 것부터 큰 것 순으로 정렬되므로 이러한 행은 가장 빠른 시간을 나타냅니다.

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 값을 추정하고 분포의 히스토그램을 만들 수 있습니다. 실행한 각 쿼리에 대해 이 작업을 수행했습니다.

다음은 제어되지는 않지만 지원되는 서비스 워커를 사용하는 데스크톱의 배포 모습입니다.

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

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

이 곡선의 모양은 로드 시간 분포에서 매우 일반적입니다. 아래 히스토그램은 서비스 워커가 페이지를 제어하고 있는 방문에서 첫 페인트 이벤트의 분포를 보여주는 히스토그램과 대조됩니다.

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

서비스 워커가 페이지를 제어하고 있을 때 많은 방문자가 첫 페인트를 거의 즉각적으로 경험했으며, 중앙값은 583ms입니다.

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

두 분포가 서로 어떻게 비교되는지 더 잘 이해할 수 있도록 다음 차트는 두 분포를 병합한 뷰를 보여줍니다. 통제되지 않은 서비스 워커 방문을 보여주는 히스토그램은 통제된 방문을 보여주는 히스토그램 위에 오버레이되며, 두 가지 모두 결합된 것을 보여주는 히스토그램 위에 두 가지 모두 오버레이됩니다.

데스크톱의 첫 페인트 배포 시간

이러한 결과에서 흥미로운 한 가지는 제어된 서비스 작업자를 통한 분산이 초기 급증 후에도 여전히 종 모양 곡선을 보인다는 점입니다. 초반에 급격한 상승이 이어지다가 점진적인 궤도를 돌릴 것으로 예상했으나, 곡선에서 두 번째 피크가 오르리라 예상하지는 않았습니다.

이 문제의 원인을 조사한 결과 서비스 워커가 페이지를 제어할 수 있더라도 스레드가 비활성 상태일 수 있다는 사실을 알게 되었습니다. 브라우저는 이를 통해 리소스를 절약합니다. 방문한 모든 사이트의 모든 서비스 워커가 즉시 활성화되어 준비되어야 하는 것은 아닙니다. 이는 분포의 꼬리를 설명할 수 있습니다. 일부 사용자의 경우, 서비스 워커 스레드가 시작되는 동안 지연이 발생했습니다.

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

모바일에서는 다음과 같이 표시됩니다.

모바일에서의 첫 페인트 배포 시간

첫 페인트 시기가 거의 즉각적으로 증가했지만, 꼬리 부분은 상당히 크고 길었습니다. 모바일에서는 유휴 서비스 워커 스레드를 시작하는 데 데스크톱보다 시간이 더 오래 걸리기 때문일 수 있습니다. 또한 평균 firstpaint 시간 간의 차이가 제가 기대한 만큼 크지 않은 이유도 설명합니다 (위 설명 참조).

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

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

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

이러한 분포 시각화를 구축하는 것은 Google 애널리틱스에서 맞춤 보고서를 만드는 것보다 시간과 노력이 좀 더 많이 들었지만, 이를 통해 서비스 작업자가 사이트 성능에 어떤 영향을 미치는지 더 잘 이해할 수 있습니다.

서비스 워커의 기타 영향

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

오프라인 사용

서비스 워커를 사용하면 사용자가 오프라인 상태에서 사이트와 상호작용할 수 있고, 일종의 오프라인 지원은 프로그레시브 웹 앱에 중요할 수 있습니다. 이 경우 그 중요도는 오프라인에서 발생하는 사용량의 양에 크게 좌우됩니다. 하지만 그것은 어떻게 측정할까요?

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

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

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

또한 IOWA를 사용하는 동안 사용자가 오프라인 상태인 것이 얼마나 흔한지 파악하기 위해 하나 이상의 오프라인 상호작용이 있는 사용자를 타겟팅하는 세그먼트를 만들었습니다. 그 결과, 사용자의 약 5% 에 달했습니다.

푸시 알림

사용자는 서비스 워커에서 푸시 알림을 수신하도록 선택할 수 있습니다. 아이오와에서는 일정에 있는 세션이 시작될 때가 되면 사용자에게 알림이 전달되었습니다.

모든 형태의 알림과 마찬가지로 사용자에게 가치를 제공하는 것과 짜증나게 하는 것 사이의 균형을 찾는 것이 중요합니다. 어떤 일이 발생하는지 더 잘 파악하려면 사용자가 이러한 알림 수신에 동의했는지, 도착했을 때 알림을 보내고 있는지, 이전에 수신 동의한 사용자가 환경설정을 변경하고 수신 거부했는지 여부를 추적하는 것이 중요합니다.

아이오와에서는 로그인한 사용자만 만들 수 있는, 사용자의 맞춤설정된 일정과 관련된 알림만을 보냈습니다. 이로 인해 브라우저에서 푸시 알림을 지원 (알림 권한이라는 다른 맞춤 측정기준을 통해 추적)한 로그인한 사용자 (로그인됨이라는 맞춤 측정기준을 통해 추적됨)에게 알림을 받을 수 있는 사용자 수가 제한되었습니다.

다음 보고서는 사용자 측정항목과 알림 권한 맞춤 측정기준을 기반으로 하며 특정 시점에 로그인한 사용자 및 푸시 알림을 지원하는 브라우저를 사용하는 사용자를 기준으로 분류됩니다.

로그인한 사용자의 절반 이상이 푸시 알림을 수신하는 데 동의하셨다니 반갑습니다.

앱 설치 배너

진행률 웹 앱이 기준을 충족하고 사용자가 자주 사용하는 경우 해당 사용자에게 앱을 홈 화면에 추가하라는 앱 설치 배너가 표시될 수 있습니다.

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. 여기에는 로드 이벤트 후 지연 로드된 리소스는 고려되지 않습니다.