Navigation Timing 및 Resource Timing을 통해 필드의 로드 성능을 평가하는 방법

탐색 및 Resource Timing API를 사용하여 현장에서 로드 성능을 평가하는 방법을 알아보세요.

게시일: 2021년 10월 8일

브라우저의 개발자 도구 (또는 Chrome의 Lighthouse)에 있는 네트워크 패널에서 연결 제한을 사용하여 로드 성능을 평가해 본 적이 있다면 이러한 도구가 성능 조정에 얼마나 편리한지 잘 알고 계실 것입니다. 일관되고 안정적인 기준 연결 속도로 성능 최적화의 영향을 빠르게 측정할 수 있습니다. 유일한 문제는 합성 테스트이므로 현장 데이터가 아닌 실험실 데이터가 생성된다는 점입니다.

합성 테스트는 본질적으로 나쁘지 않지만 실제 사용자의 웹사이트 로드 속도를 대표하지는 않습니다. 이를 위해서는 Navigation Timing API 및 Resource Timing API에서 수집할 수 있는 현장 데이터가 필요합니다.

탐색 시간과 리소스 시간은 두 가지 서로 다른 항목을 측정하는 유사한 API로, 상당히 겹칩니다.

  • 탐색 시간은 HTML 문서 요청 속도 (즉, 탐색 요청)를 측정합니다.
  • 리소스 타이밍은 CSS, JavaScript, 이미지, 기타 리소스 유형과 같은 문서 종속 리소스에 대한 요청 속도를 측정합니다.

이러한 API는 JavaScript로 브라우저에서 액세스할 수 있는 성능 입력 버퍼에 데이터를 노출합니다. 성능 버퍼를 쿼리하는 방법에는 여러 가지가 있지만 일반적인 방법은 performance.getEntriesByType를 사용하는 것입니다.

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType는 성능 항목 버퍼에서 검색하려는 항목 유형을 설명하는 문자열을 허용합니다. 'navigation''resource'는 각각 Navigation Timing API 및 Resource Timing API의 타이밍을 가져옵니다.

이러한 API가 제공하는 정보의 양은 압도적일 수 있지만, 사용자가 웹사이트를 방문할 때 소요 시간을 수집할 수 있으므로 현장에서 로드 성능을 측정하는 데 핵심적인 역할을 합니다.

네트워크 요청의 수명 및 타이밍

탐색 및 리소스 타이밍을 수집하고 분석하는 것은 일종의 고고학과 같습니다. 사후에 네트워크 요청의 짧은 수명을 재구성하는 것이기 때문입니다. 개념을 시각화하는 것이 도움이 되는 경우도 있으며, 네트워크 요청이 우려되는 경우 브라우저의 개발자 도구가 도움이 될 수 있습니다.

네트워크 타이밍(Chrome의 DevTools에 표시된 대로) 표시된 시간은 요청 큐잉, 연결 협상, 요청 자체, 응답을 나타내며 색상 코드가 지정된 막대로 표시됩니다.
Chrome DevTools의 네트워크 패널에 있는 네트워크 요청의 시각화

네트워크 요청의 수명에는 DNS 조회, 연결 설정, TLS 협상, 기타 지연 소스와 같은 고유한 단계가 있습니다. 이러한 타이밍은 DOMHighResTimestamp로 표시됩니다. 브라우저에 따라 타이밍의 세부사항이 마이크로초까지 내려가거나 밀리초로 반올림될 수 있습니다. 이러한 단계를 자세히 살펴보고, Navigation Timing 및 Resource Timing의 관계와 어떤 관련이 있는지 알아보겠습니다.

DNS 조회

사용자가 URL로 이동하면 도메인 이름 시스템 (DNS)이 쿼리되어 도메인을 IP 주소로 변환합니다. 이 프로세스는 상당한 시간이 걸릴 수 있으며, 이 시간은 현장에서 측정하는 데 사용해야 할 수도 있습니다. Navigation Timing 및 Resource Timing은 두 가지 DNS 관련 타이밍을 노출합니다.

  • domainLookupStart는 DNS 조회가 시작되는 시점입니다.
  • domainLookupEnd는 DNS 조회가 종료되는 시점입니다.

총 DNS 조회 시간은 시작 측정항목에서 종료 측정항목을 빼면 계산할 수 있습니다.

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

연결 협상

로드 성능에 영향을 미치는 또 다른 요소는 연결 협상입니다. 연결 협상은 웹 서버에 연결할 때 발생하는 지연 시간입니다. HTTPS가 포함된 경우 이 프로세스에는 TLS 협상 시간도 포함됩니다. 연결 단계는 세 가지 타이밍으로 구성됩니다.

  • connectStart는 브라우저가 웹 서버에 대한 연결을 열기 시작하는 시점입니다.
  • secureConnectionStart은 클라이언트가 TLS 협상을 시작하는 시점을 표시합니다.
  • connectEnd는 웹 서버에 연결된 경우입니다.

총 연결 시간을 측정하는 방법은 총 DNS 조회 시간을 측정하는 방법과 비슷합니다. 시작 시간을 종료 시간에서 빼면 됩니다. 그러나 HTTPS가 사용되지 않거나 연결이 영구적인 경우 0일 수 있는 추가 secureConnectionStart 속성이 있습니다. TLS 협상 시간을 측정하려면 다음 사항에 유의해야 합니다.

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

DNS 조회 및 연결 협상이 끝나면 문서 및 종속 리소스 가져오기와 관련된 타이밍이 적용됩니다.

요청 및 응답

로드 성능은 다음 두 가지 유형의 요인에 영향을 받습니다.

  • 외부 요인: 지연 시간 및 대역폭과 같은 요인입니다. 호스팅 회사와 CDN을 선택하는 것 외에는 사용자가 어디서나 웹에 액세스할 수 있으므로 Google에서 제어할 수 있는 부분이 거의 없습니다.
  • 내재적 요인: 서버 및 클라이언트 측 아키텍처, 리소스 크기, 이러한 항목에 맞춰 최적화하는 능력 등은 Google에서 제어할 수 있습니다.

두 유형의 요소 모두 로드 성능에 영향을 미칩니다. 이러한 요인과 관련된 시점은 리소스를 다운로드하는 데 걸리는 시간을 설명하기 때문에 매우 중요합니다. Navigation Timing과 Resource Timing은 모두 다음 측정항목으로 로드 성능을 설명합니다.

  • fetchStart는 브라우저가 탐색 요청 (Navigation Timing)을 위한 리소스 (Resource Timing) 또는 문서를 가져오기 시작하는 시점을 표시합니다. 이는 실제 요청보다 먼저 발생하며 브라우저가 캐시 (예: HTTP 및 Cache 인스턴스)를 확인하는 지점입니다.
  • workerStart는 서비스 워커의 fetch 이벤트 핸들러 내에서 요청이 처리되기 시작하는 시점을 표시합니다. 현재 페이지를 제어하는 서비스 워커가 없으면 0입니다.
  • requestStart는 브라우저가 요청을 보낼 때입니다.
  • responseStart은 응답의 첫 번째 바이트가 도착한 시간입니다.
  • responseEnd는 응답의 마지막 바이트가 도착한 시점입니다.

이러한 타이밍을 사용하면 서비스 워커 내 캐시 조회 다운로드 시간과 같은 로드 성능의 여러 측면을 측정할 수 있습니다.

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

요청 및 응답 지연 시간의 다른 측면도 측정할 수 있습니다.

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

측정할 수 있는 기타 항목

Navigation Timing과 Resource Timing은 이전 예시에서 설명한 것보다 더 많은 경우에 유용합니다. 다음은 살펴볼 만한 관련 타이밍이 있는 다른 상황입니다.

  • 페이지 리디렉션: 리디렉션은 지연 시간 증가를 유발하는 간과된 원인이며, 특히 리디렉션 체인에 영향을 미칩니다. 지연 시간은 HTTP-HTTPs 홉, 302/캐시되지 않은 301 리디렉션과 같은 다양한 방법으로 추가됩니다. redirectStart, redirectEnd, redirectCount 타이밍은 리디렉션 지연 시간을 평가하는 데 도움이 됩니다.
  • 문서 언로드: unload 이벤트 핸들러에서 코드를 실행하는 페이지에서 브라우저가 해당 코드를 실행해야 다음 페이지로 이동할 수 있습니다. unloadEventStartunloadEventEnd는 문서 언로드를 측정합니다.
  • 문서 처리: 웹사이트에서 매우 큰 HTML 페이로드를 전송하는 경우가 아니라면 문서 처리 시간이 중요하지 않을 수 있습니다. 이 설명이 사용자의 상황에 해당하는 경우 domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, domComplete 타이밍을 확인해 보세요.

코드에서 타이밍을 가져오는 방법

지금까지 표시된 모든 예에서는 performance.getEntriesByType를 사용하지만 performance.getEntriesByNameperformance.getEntries와 같이 성능 항목 버퍼를 쿼리하는 다른 방법도 있습니다. 이러한 메서드는 가벼운 분석만 필요한 경우에는 괜찮습니다. 하지만 다른 경우에는 다수의 항목을 반복하거나 성능 버퍼를 반복적으로 폴링하여 새 항목을 찾는 등 과도한 기본 스레드 작업을 유발할 수 있습니다.

성능 항목 버퍼에서 항목을 수집하는 데 권장되는 방법은 PerformanceObserver를 사용하는 것입니다. PerformanceObserver는 실적 항목을 리슨하고 버퍼에 추가될 때 이를 제공합니다.

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

이 타이밍 수집 방법은 성능 항목 버퍼에 직접 액세스하는 것과 비교할 때 어색하게 느껴질 수 있지만, 중요한 사용자 대상 목적을 수행하지 않는 작업으로 기본 스레드를 연결하는 것이 좋습니다.

집에 전화하는 방법

필요한 모든 타이밍을 수집한 후 추가 분석을 위해 엔드포인트로 전송할 수 있습니다. 이를 수행하는 방법에는 navigator.sendBeacon 또는 keepalive 옵션이 설정된 fetch가 있습니다. 두 메서드 모두 비차단 방식으로 지정된 엔드포인트에 요청을 전송하며, 필요한 경우 요청이 현재 페이지 세션보다 오래 지속되도록 큐에 추가됩니다.

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

이 예시에서 JSON 문자열은 POST 페이로드로 도착하며, 이 페이로드는 필요에 따라 디코딩, 처리, 애플리케이션 백엔드에 저장할 수 있습니다.

결론

측정항목을 수집한 후에는 필드 데이터를 분석하는 방법을 알아야 합니다. 현장 데이터를 분석할 때 의미 있는 결론을 도출하려면 다음과 같은 몇 가지 일반적인 규칙을 따라야 합니다.

  • 평균은 특정 사용자의 경험을 대표하지 않으며 외부값으로 인해 왜곡될 수 있으므로 평균을 사용하지 마세요.
  • 백분위수를 사용하세요. 시간 기반 실적 측정항목의 데이터 세트에서는 값이 낮을수록 좋습니다. 즉, 낮은 백분위수에 우선순위를 두는 경우 가장 빠른 경험에만 주의를 기울이게 됩니다.
  • 가치의 롱테일을 우선시합니다. 75번째 백분위수 이상의 환경에 우선순위를 두면 가장 느린 환경에 집중할 수 있습니다.

이 가이드는 Navigation 또는 Resource Timing에 대한 완전한 리소스가 아닌 시작점입니다. 다음은 도움이 될 만한 추가 리소스입니다.

이러한 API와 API에서 제공하는 데이터를 사용하면 실제 사용자가 로드 성능을 어떻게 경험하는지 더 잘 이해할 수 있으므로 현장에서 로드 성능 문제를 진단하고 해결하는 데 더 자신감을 가질 수 있습니다.