탐색 및 리소스 타이밍 API를 사용하여 실제 환경에서 로드 성능을 평가하는 기본사항을 알아봅니다.
게시일: 2021년 10월 8일
브라우저의 개발자 도구 (또는 Chrome의 Lighthouse)의 네트워크 패널에서 연결 제한을 사용하여 로드 성능을 평가한 적이 있다면 이러한 도구가 성능 조정에 얼마나 편리한지 알 것입니다. 일관되고 안정적인 기준 연결 속도로 성능 최적화의 영향을 빠르게 측정할 수 있습니다. 유일한 문제는 합성 테스트이므로 필드 데이터가 아닌 실험실 데이터가 생성된다는 것입니다.
합성 테스트가 본질적으로 나쁜 것은 아니지만 실제 사용자의 웹사이트 로드 속도를 나타내지는 않습니다. 이를 위해서는 필드 데이터가 필요하며, Navigation Timing API와 Resource Timing API에서 수집할 수 있습니다.
필드에서 로드 성능을 평가하는 데 도움이 되는 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가 제공하는 정보의 양은 압도적일 수 있지만, 사용자가 웹사이트를 방문할 때 이러한 타이밍을 수집할 수 있으므로 필드에서 로드 성능을 측정하는 데 핵심적인 역할을 합니다.
네트워크 요청의 수명 및 타이밍
탐색 및 리소스 타이밍을 수집하고 분석하는 것은 사후에 네트워크 요청의 순간적인 수명을 재구성한다는 점에서 고고학과 비슷합니다. 때로는 개념을 시각화하는 것이 도움이 되며, 네트워크 요청과 관련해서는 브라우저의 개발자 도구가 도움이 될 수 있습니다.
네트워크 요청의 수명에는 DNS 조회, 연결 설정, TLS 협상, 기타 지연 시간 소스와 같은 명확한 단계가 있습니다. 이러한 타이밍은 DOMHighResTimestamp로 표시됩니다. 브라우저에 따라 타이밍의 세부사항이 마이크로초까지 내려갈 수도 있고 밀리초로 반올림될 수도 있습니다. 이러한 단계를 자세히 살펴보고 탐색 타이밍 및 리소스 타이밍과 어떤 관련이 있는지 확인하세요.
DNS 조회
사용자가 URL로 이동하면 도메인을 IP 주소로 변환하기 위해 도메인 이름 시스템 (DNS)이 쿼리됩니다. 이 프로세스에는 상당한 시간이 걸릴 수 있습니다. 현장에서 시간을 측정하는 것이 좋습니다. 탐색 타이밍과 리소스 타이밍은 다음과 같은 두 가지 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는 브라우저가 탐색 요청의 리소스(리소스 타이밍) 또는 문서를 가져오기 시작하는 시점을 표시합니다. 이는 실제 요청보다 먼저 발생하며 브라우저가 캐시 (예: 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;
측정할 수 있는 기타 항목
탐색 타이밍과 리소스 타이밍은 이전 예에 설명된 것보다 더 유용합니다. 다음은 관련 타이밍이 있는 다른 상황으로, 살펴볼 만합니다.
- 페이지 리디렉션: 리디렉션은 특히 리디렉션 체인의 경우 추가 지연 시간의 간과되는 원인입니다. 지연 시간은 HTTP에서 HTTPS로의 홉, 302/캐시되지 않은 301 리디렉션 등 다양한 방식으로 추가됩니다.
redirectStart,redirectEnd,redirectCount타이밍은 리디렉션 지연 시간을 평가하는 데 유용합니다. - 문서 언로드:
unload이벤트 핸들러에서 코드를 실행하는 페이지에서 브라우저는 다음 페이지로 이동하기 전에 해당 코드를 실행해야 합니다.unloadEventStart및unloadEventEnd는 문서 언로드를 측정합니다. - 문서 처리: 웹사이트에서 매우 큰 HTML 페이로드를 전송하지 않는 한 문서 처리 시간은 중요하지 않을 수 있습니다. 이러한 상황에 해당한다면
domInteractive,domContentLoadedEventStart,domContentLoadedEventEnd,domComplete타이밍이 유용할 수 있습니다.
코드에서 타이밍을 가져오는 방법
지금까지 표시된 모든 예에서는 performance.getEntriesByType를 사용했지만 performance.getEntriesByName 및 performance.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를 사용하거나 fetch에 keepalive 옵션을 설정하는 두 가지 방법이 있습니다. 두 메서드 모두 차단되지 않는 방식으로 지정된 엔드포인트에 요청을 전송하며, 필요한 경우 요청이 현재 페이지 세션보다 오래 지속되는 방식으로 대기열에 추가됩니다.
// 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번째 백분위수 이상의 환경을 우선시하면 가장 느린 환경에 집중할 수 있습니다.
이 가이드는 탐색 또는 리소스 타이밍에 관한 포괄적인 리소스가 아니라 시작점으로 사용해야 합니다. 다음은 도움이 될 만한 추가 리소스입니다.
이러한 API와 API에서 제공하는 데이터를 사용하면 실제 사용자가 로드 성능을 어떻게 경험하는지 더 잘 이해할 수 있으므로 현장에서 로드 성능 문제를 진단하고 해결하는 데 더 큰 확신을 가질 수 있습니다.