성능 API 소개
게시 됨: 2022-08-10Performance API는 실제 사용자 장치 및 네트워크 연결에서 라이브 웹 애플리케이션의 응답성을 측정합니다. 다음을 통해 클라이언트 측 및 서버 측 코드에서 병목 현상을 식별하는 데 도움이 될 수 있습니다.
- 사용자 타이밍: 클라이언트 측 JavaScript 기능 성능의 사용자 정의 측정
- 페인트 타이밍: 브라우저 렌더링 메트릭
- 리소스 타이밍: 자산 및 Ajax 호출의 로드 성능
- 탐색 타이밍: 리디렉션, DNS 조회, DOM 준비 상태 등을 포함한 페이지 로딩 메트릭
API는 일반적인 성능 평가와 관련된 몇 가지 문제를 해결합니다.
- 개발자는 종종 고속 네트워크에 연결된 고급 PC에서 응용 프로그램을 테스트합니다. DevTools는 느린 장치를 에뮬레이트할 수 있지만 대부분의 클라이언트가 공항 WiFi에 연결된 2년 된 모바일을 실행하는 경우 실제 문제를 항상 강조하지는 않습니다.
- Google Analytics와 같은 타사 옵션은 종종 차단되어 왜곡된 결과와 가정으로 이어집니다. 일부 국가에서는 개인 정보 보호 문제가 발생할 수도 있습니다.
- Performance API는
Date()
와 같은 메소드보다 다양한 메트릭을 정확하게 측정할 수 있습니다.
다음 섹션에서는 Performance API를 사용할 수 있는 방법을 설명합니다. JavaScript 및 페이지 로딩 측정항목에 대한 지식이 있는 것이 좋습니다.
성능 API 가용성
대부분의 최신 브라우저는 IE10 및 IE11을 포함하여 성능 API를 지원합니다(IE9도 지원이 제한적임). 다음을 사용하여 API의 존재를 감지할 수 있습니다.
if ('performance' in window) { // use Performance API }
API를 완전히 폴리필하는 것은 불가능하므로 누락된 브라우저에 주의하십시오. 사용자의 90%가 Internet Explorer 8을 사용하여 만족스럽게 탐색하고 있다면 더 많은 기능을 갖춘 응용 프로그램을 사용하는 클라이언트의 10%만 측정하게 됩니다.
API는 브라우저 작업을 중단하지 않고 백그라운드 스레드에서 복잡한 계산을 실행하는 방법을 제공하는 웹 작업자에서 사용할 수 있습니다.
대부분의 API 메소드는 표준 perf_hooks 모듈과 함께 서버 측 Node.js에서 사용할 수 있습니다.
// Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );
Deno는 표준 성능 API를 제공합니다.
// Deno performance console.log( performance.now() );
고해상도 시간 측정을 활성화하려면 --allow-hrtime
권한으로 스크립트를 실행해야 합니다.
deno run --allow-hrtime index.js
서버 측 성능은 로드, CPU, RAM, 하드 디스크 및 클라우드 서비스 제한에 의존하기 때문에 일반적으로 평가하고 관리하기가 더 쉽습니다. PM2, 클러스터링 및 Kubernetes와 같은 하드웨어 업그레이드 또는 프로세스 관리 옵션은 코드를 리팩토링하는 것보다 더 효과적일 수 있습니다.
다음 섹션에서는 이러한 이유로 클라이언트 측 성능에 중점을 둡니다.
맞춤형 성능 측정
Performance API는 애플리케이션 기능의 실행 속도를 측정하는 데 사용할 수 있습니다. Date()
를 사용하여 타이밍 함수를 사용했거나 접했을 수 있습니다.
const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
Performance API는 두 가지 주요 이점을 제공합니다.
- 정확도 향상:
Date()
는 가장 가까운 밀리초 단위로 측정하지만 성능 API는 브라우저에 따라 밀리초 단위로 측정할 수 있습니다. - 더 나은 안정성: 사용자 또는 OS가 시스템 시간을 변경할 수 있으므로
Date()
기반 메트릭이 항상 정확하지는 않습니다. 이것은 시계가 앞으로 나아갈 때 기능이 특히 느리게 나타날 수 있음을 의미합니다!
Date()
는 문서 생성을 담당하는 프로세스가 시작될 때(페이지가 로드됨) 0으로 설정되는 고해상도 타임스탬프를 반환하는 performance.now()
에 해당합니다.
const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
비표준 performance.timeOrigin
속성은 1970년 1월 1일의 타임스탬프를 반환할 수도 있지만 IE와 Deno에서는 사용할 수 없습니다.
performance.now()
는 몇 번 이상 측정할 때 비실용적입니다. Performance API는 레이블 이름을 performance.mark()
에 전달하여 나중에 분석할 수 있도록 이벤트를 기록할 수 있는 버퍼를 제공합니다.
performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');
성능 버퍼에 있는 모든 마크 개체의 배열은 다음을 사용하여 추출할 수 있습니다.
const mark = performance.getEntriesByType('mark');
예시 결과:
[ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]
performance.measure()
메서드는 두 표시 사이의 시간을 계산하고 성능 버퍼에도 저장합니다. 새 측정값 이름, 시작 표시 이름(또는 페이지 로드에서 측정하려면 null) 및 종료 표시 이름(또는 현재 시간까지 측정하려면 null)을 전달합니다.
performance.measure('init', 'start:init', 'end:init');
PerformanceMeasure 개체는 계산된 기간과 함께 버퍼에 추가됩니다. 이 값을 얻으려면 모든 측정값의 배열을 요청할 수 있습니다.
const measure = performance.getEntriesByType('measure');
또는 이름으로 조치를 요청하십시오.
performance.getEntriesByName('init');
예시 결과:
[ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]
성능 버퍼 사용
표시 및 측정값과 함께 성능 버퍼는 탐색 타이밍, 리소스 타이밍 및 페인트 타이밍(나중에 설명함)을 자동으로 기록하는 데 사용됩니다. 버퍼에 있는 모든 항목의 배열을 얻을 수 있습니다.
performance.getEntries();
기본적으로 대부분의 브라우저는 최대 150개의 리소스 메트릭을 저장하는 버퍼를 제공합니다. 이것은 대부분의 평가에 충분해야 하지만 필요한 경우 버퍼 제한을 늘리거나 줄일 수 있습니다.
// record 500 metrics performance.setResourceTimingBufferSize(500);
마크는 이름으로 지우거나 빈 값을 지정하여 모든 마크를 지울 수 있습니다.
performance.clearMarks('start:init');
마찬가지로 측정값은 이름이나 빈 값으로 지워서 모두 지울 수 있습니다.
performance.clearMeasures();
성능 버퍼 업데이트 모니터링
PerformanceObserver 는 성능 버퍼의 변경 사항을 모니터링하고 특정 이벤트가 발생할 때 기능을 실행할 수 있습니다. MutationObserver 를 사용하여 DOM 업데이트에 응답하거나 IntersectionObserver 를 사용하여 요소가 뷰포트로 스크롤되는 시점을 감지한 경우 구문이 익숙할 것입니다.
두 개의 매개변수를 사용하여 관찰자 함수를 정의해야 합니다.
- 감지된 관찰자 항목의 배열 및
- 관찰자 개체. 필요한 경우
disconnect()
메서드를 호출하여 관찰자를 중지할 수 있습니다.
function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }
함수는 새 PerformanceObserver 개체에 전달됩니다. 관찰할 성능 버퍼 entryType의 배열이 observe()
메서드에 전달됩니다.
let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });
이 예에서 새 표시 또는 측정값을 추가하면 performanceCallback()
함수가 실행됩니다. 여기에 메시지만 기록하지만 데이터 업로드를 트리거하거나 추가 계산을 수행하는 데 사용할 수 있습니다.
페인트 성능 측정
Paint Timing API는 클라이언트 측 JavaScript에서만 사용할 수 있으며 Core Web Vitals에 중요한 두 가지 측정항목을 자동으로 기록합니다.
- first-paint: 브라우저가 페이지를 그리기 시작했습니다.
- first-contentful-paint: 브라우저가 제목이나 이미지와 같은 DOM 콘텐츠의 첫 번째 중요한 항목을 그렸습니다.
성능 버퍼에서 어레이로 추출할 수 있습니다.
const paintTimes = performance.getEntriesByType('paint');
페이지가 완전히 로드되기 전에 이것을 실행하는 것에 주의하십시오. 값이 준비되지 않습니다. window.load
이벤트를 기다리거나 PerformanceObserver
를 사용하여 paint
entryType을 모니터링하십시오.
예시 결과:
[ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]
느린 첫 번째 페인트는 종종 렌더링 차단 CSS 또는 JavaScript로 인해 발생합니다. 브라우저가 큰 이미지를 다운로드하거나 복잡한 요소를 렌더링해야 하는 경우 첫 번째 콘텐츠가 포함된 페인트와의 간격이 클 수 있습니다.
자원 성과 측정
이미지, 스타일시트 및 JavaScript 파일과 같은 리소스에 대한 네트워크 타이밍은 성능 버퍼에 자동으로 기록됩니다. 네트워크 속도 문제를 해결하기 위해 할 수 있는 일은 거의 없지만(파일 크기를 줄이는 것 외에는) 더 큰 자산, 느린 Ajax 응답 또는 성능이 좋지 않은 타사 스크립트와 관련된 문제를 강조하는 데 도움이 될 수 있습니다.
PerformanceResourceTiming 메트릭 배열은 다음을 사용하여 버퍼에서 추출할 수 있습니다.
const resources = performance.getEntriesByType('resource');
또는 전체 URL을 전달하여 자산에 대한 메트릭을 가져올 수 있습니다.
const resource = performance.getEntriesByName('https://test.com/script.js');
예시 결과:
[ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]
다음 속성을 검사할 수 있습니다.
- 이름 : 리소스 URL
- entryType : "리소스"
- initiatorType : "스크립트" 또는 "링크"와 같은 리소스가 시작된 방법
- serverTiming : HTTP Server-Timing 헤더에서 서버에 의해 전달된
PerformanceServerTiming
객체의 배열(서버 측 애플리케이션은 추가 분석을 위해 클라이언트에 메트릭을 보낼 수 있음) - startTime : 가져오기가 시작된 타임스탬프
- nextHopProtocol : 사용된 네트워크 프로토콜
- workerStart : 프로그레시브 웹 앱 서비스 워커를 시작하기 전 타임스탬프(서비스 워커가 요청을 가로채지 않은 경우 0)
- redirectStart : 리디렉션이 시작된 타임스탬프
- redirectEnd : 마지막 리디렉션 응답의 마지막 바이트 이후의 타임스탬프
- fetchStart : 리소스 가져오기 전 타임스탬프
- domainLookupStart : DNS 조회 전 타임스탬프
- domainLookupEnd : DNS 조회 후 타임스탬프
- connectStart : 서버 연결을 설정하기 전 타임스탬프
- connectEnd : 서버 연결 설정 후 타임스탬프
- secureConnectionStart : SSL 핸드셰이크 전 타임스탬프
- requestStart : 브라우저가 리소스를 요청하기 전 타임스탬프
- responseStart : 브라우저가 데이터의 첫 번째 바이트를 수신할 때의 타임스탬프
- responseEnd : 마지막 바이트를 수신하거나 연결을 닫은 후의 타임스탬프
- duration : startTime과 responseEnd의 차이
- transferSize : 헤더와 압축된 본문을 포함한 리소스 크기(바이트)
- encodeBodySize : 압축을 풀기 전의 리소스 본문(바이트)
- decodedBodySize : 압축 해제 후 리소스 본문(바이트)
이 예제 스크립트는 Fetch API에 의해 시작된 모든 Ajax 요청을 검색하고 총 전송 크기와 기간을 반환합니다.
const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );
내비게이션 성능 측정
이전 페이지를 언로드하고 현재 페이지를 로드하기 위한 네트워크 타이밍은 단일 PerformanceNavigationTiming
개체로 성능 버퍼에 자동으로 기록됩니다.
다음을 사용하여 배열로 추출합니다.
const pageTime = performance.getEntriesByType('navigation');
...또는 페이지 URL을 .getEntriesByName()
에 전달:
const pageTiming = performance.getEntriesByName(window.location);
측정항목은 리소스에 대한 측정항목과 동일하지만 페이지별 값도 포함합니다.
- entryType : 예 "내비게이션"
- type : "navigate", "reload", "back_forward" 또는 "prerender" 중 하나
- redirectCount : 리디렉션 수
- unloadEventStart : 이전 문서의 언로드 이벤트 전 타임스탬프
- unloadEventEnd : 이전 문서의 언로드 이벤트 이후의 타임스탬프
- domInteractive : 브라우저가 HTML을 구문 분석하고 DOM을 구성했을 때의 타임스탬프
- domContentLoadedEventStart : 문서의 DOMContentLoaded 이벤트가 발생하기 전의 타임스탬프
- domContentLoadedEventEnd : 문서의 DOMContentLoaded 이벤트가 완료된 후의 타임스탬프
- domComplete : DOM 생성 및 DOMContentLoaded 이벤트가 완료된 후 타임스탬프
- loadEventStart : 페이지 로드 이벤트가 발생하기 전의 타임스탬프
- loadEventEnd : 페이지 로드 이벤트 및 모든 자산을 사용할 수 있는 후 타임스탬프
일반적인 문제는 다음과 같습니다.
- unloadEventEnd 와 domInteractive 사이의 긴 지연. 이것은 느린 서버 응답을 나타낼 수 있습니다.
- domContentLoadedEventStart 와 domComplete 사이의 긴 지연. 이는 페이지 시작 스크립트가 너무 느리다는 것을 나타낼 수 있습니다.
- domComplete 와 loadEventEnd 사이의 긴 지연. 이는 페이지에 너무 많은 자산이 있거나 여러 자산을 로드하는 데 너무 오래 걸린다는 것을 나타낼 수 있습니다.
성과 기록 및 분석
Performance API를 사용하면 실제 사용 데이터를 수집하고 추가 분석을 위해 서버에 업로드할 수 있습니다. Google Analytics와 같은 타사 서비스 를 사용하여 데이터를 저장할 수 있지만 타사 스크립트가 차단되거나 새로운 성능 문제가 발생할 위험이 있습니다. 모니터링이 다른 기능에 영향을 미치지 않도록 고유한 솔루션을 요구 사항에 맞게 사용자 지정할 수 있습니다.
통계를 확인할 수 없는 상황에 주의하십시오. 사용자가 이전 브라우저를 사용 중이거나 JavaScript를 차단하거나 회사 프록시 뒤에 있기 때문일 수 있습니다. 불완전한 정보를 기반으로 가정하는 것보다 누락된 데이터를 이해하는 것이 더 효과적일 수 있습니다.
이상적으로는 분석 스크립트가 복잡한 계산을 실행하거나 대량의 데이터를 업로드하여 성능에 부정적인 영향을 미치지 않습니다. 웹 작업자를 활용하고 동기식 localStorage 호출 사용을 최소화하는 것을 고려하십시오. 나중에 원시 데이터를 일괄 처리하는 것은 항상 가능합니다.
마지막으로, 통계에 부정적인 영향을 미치는 매우 빠르거나 매우 느린 장치 및 연결과 같은 이상값에 주의하십시오. 예를 들어 9명의 사용자가 2초 안에 페이지를 로드하지만 10번째 사용자가 60초 다운로드를 경험한다면 평균 지연 시간은 거의 8초가 됩니다. 보다 현실적인 지표는 중앙값(2초) 또는 90번째 백분위수(사용자 10명 중 9명이 2초 이하의 로드 시간을 경험함)입니다.
요약
웹 성능은 개발자에게 여전히 중요한 요소입니다. 사용자는 사이트와 애플리케이션이 대부분의 장치에서 반응하기를 기대합니다. Google에서 느린 사이트가 다운그레이드되면 검색 엔진 최적화도 영향을 받을 수 있습니다.
있습니다.
성능 모니터링 도구는 많이 있지만 대부분은 서버 측 실행 속도를 평가하거나 제한된 수의 클라이언트를 사용하여 브라우저 렌더링을 판단합니다. Performance API는 다른 방법으로는 계산할 수 없는 실제 사용자 메트릭을 조합하는 방법을 제공합니다.