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

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

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

이 사례 연구에서는 IOWA가 Google 애널리틱스를 사용하여 주요 성과 관련 질문에 대한 해답을 찾고 서비스 작업자의 실제 영향에 대해 보고한 방법을 살펴봅니다.

질문으로 시작하기

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

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

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

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

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

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

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

즉, 기존의 페이지 로드 측정항목으로 측정되는 실제 로드 시간과 관계없이 사이트가 로드되는 느낌인가요?

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

적합한 측정항목 선택

기본적으로 Google 애널리틱스에서는 Navigation Timing API를 통해 사이트 방문자의 1% 에 대해 페이지 로드 시간을 추적하며, 평균 페이지 로드 시간

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

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

답변하고자 하는 질문을 결정하고 질문에 답하는 데 도움이 될 측정항목을 파악했으면 이제 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');

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

아이오와의 경우 추가로 2가지를 추적하고자 했습니다.

  • 페이지가 처음 로드되기 시작한 시점부터 화면에 픽셀이 나타나는 시점까지 경과한 시간입니다.
  • 서비스 워커가 페이지를 제어하는지 여부입니다. 이 정보를 사용하여 보고서를 세분화하여 서비스 워커를 사용할 때와 사용하지 않을 때의 결과를 비교할 수 있습니다.

첫 페인트까지의 시간 캡처

일부 브라우저는 첫 번째 픽셀이 화면에 그려지는 정확한 시간을 기록하여 개발자가 이 시간을 사용할 수 있도록 합니다. 이 값은 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() 호출을 연기했습니다. 사실, 요청이 다른 리소스의 로드와 경쟁하지 않도록 페이지가 로드된 이후까지 모든 분석 데이터 전송을 연기하기로 결정했습니다.

// 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회 보고하지만, 그것은 단지 절반에 불과합니다. 서비스 워커의 상태를 추적해야 했습니다. 그렇지 않으면 서비스 워커가 제어하는 페이지와 제어되지 않는 페이지의 첫 번째 페인트 시간을 비교할 수 없습니다.

서비스 워커 상태 확인

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

  • 제어: 서비스 워커가 페이지를 제어하고 있습니다. 또한 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 캐싱 메커니즘보다 성능이 우수한가요?였습니다.

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

선택한 크기는 다음과 같습니다.

  • 커스텀 서비스 워커 상태 측정기준.
  • 사용자 유형: 사용자의 사이트 첫 방문인지 아니면 재방문인지를 나타냅니다. (참고: 신규 방문자는 캐시된 리소스를 보유하지 않으며 재방문자는 캐시될 수 있습니다.)
  • 기기 카테고리: 모바일과 데스크톱의 결과를 비교할 수 있습니다.

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

보시다시피 서비스 워커가 제어할 때 앱 방문은 통제되지 않은 방문보다 훨씬 빠르게 로드됩니다. 이는 대부분의 페이지 리소스가 캐시된 재사용자의 방문인 경우에도 마찬가지입니다. 또한 평균적으로 서비스 워커를 사용하는 모바일 방문자는 새로운 데스크톱 방문자보다 로드 속도가 더 빠릅니다.

"...서비스 워커가 제어할 때 앱 방문이 통제되지 않은 방문보다 훨씬 빠르게 로드됩니다..."

자세한 내용은 다음 두 표에서 확인할 수 있습니다.

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

서비스 워커를 지원하는 브라우저를 사용하는 재방문 방문자가 어떻게 통제되지 않는 상태가 될 수 있는지 궁금할 수 있습니다. 여기에는 다음과 같은 몇 가지 이유가 있을 수 있습니다.

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

두 경우 모두 비교적 드문 경우입니다. 데이터에서 네 번째 열의 페이지 로드 샘플 값을 보면 이를 확인할 수 있습니다. 중간 행은 나머지 두 행보다 샘플이 훨씬 작습니다.

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

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

예상했던 것과는 달리, 모바일의 서비스 워커는 전체 페이지 로드에 비해 첫 페인트에 걸리는 시간에 훨씬 적은 영향을 미쳤습니다.

"...모바일의 서비스 워커는 전체 페이지 로드보다 첫 페인트까지의 시간에 훨씬 적은 영향을 미쳤습니다."

그 이유를 알아내려면 데이터를 더 깊이 들여다봐야 합니다. 평균은 일반적인 개요와 광범위한 획을 칠할 때 유용할 수 있지만, 이러한 숫자가 다양한 사용자에 걸쳐 어떻게 분류되는지 파악하려면 firstpaint배의 분포를 살펴봐야 합니다.

Google 애널리틱스에서 측정항목의 분포 가져오기

firstpaint 횟수의 분포를 가져오려면 각 이벤트의 개별 결과에 액세스해야 합니다. Google Analytics로는 이를 쉽게 해결할 수 없습니다.

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

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가 8ms였던 이벤트가 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. 이는 로드 이벤트 후 지연 로드되는 리소스는 고려하지 않습니다.