웹 앱 이해하기
고성능 웹 애플리케이션은 뛰어난 사용자 환경을 제공하는 데 필수적입니다. 웹 애플리케이션이 점점 복잡해짐에 따라 매력적인 환경을 만들려면 성능 영향을 이해하는 것이 중요합니다. 지난 몇 년 동안 네트워크 성능, 로드 시간 등을 분석하는 데 도움이 되는 다양한 API가 브라우저에 많았지만, 이들이 애플리케이션 속도를 저하시키는 요인을 충분히 유연하게 찾을 수 있을 정도로 세밀한 세부 정보를 제공하지는 않습니다. User Timing API를 입력하세요. 이 API는 웹 애플리케이션을 계측하여 애플리케이션에서 시간을 보내는 위치를 파악하는 데 사용할 수 있는 메커니즘을 제공합니다. 이 도움말에서는 API와 사용 방법의 예를 살펴봅니다.
측정할 수 없는 것은 최적화할 수 없음
느린 웹 애플리케이션의 속도를 높이기 위한 첫 번째 단계는 시간을 어디에 소비하는지 알아내는 것입니다. JavaScript 코드 영역의 시간 영향을 측정하는 것은 핫스팟을 식별하는 이상적인 방법이며, 이는 성능 개선 방법을 찾는 첫 번째 단계입니다. 다행히 User Timing API는 JavaScript의 여러 부분에 API 호출을 삽입한 다음 최적화에 도움이 되는 자세한 타이밍 데이터를 추출할 수 있는 방법을 제공합니다.
고해상도 시간 및 now()
정확한 시간 측정의 핵심은 정밀도입니다. 예전에는 밀리초 단위로 측정한 타이밍을 기준으로 하는 것이 괜찮았지만, 버벅거림이 없는 60FPS 사이트를 빌드하려면 각 프레임을 16ms 이내에 그려야 했습니다. 따라서 정확도가 밀리초 수준일 경우 좋은 분석에 필요한 정밀도는 부족합니다. 최신 브라우저에 ��장된 새로운 시간 유형인 고해상도 시간을 입력합니다. 고해상도 시간은 부동 소수점 타임 스탬프를 제공하며, 이는 마이크로초 단위의 해상도로 이전보다 1,000배 이상 향상되었습니다.
웹 애플리케이션에서 현재 시간을 가져오려면 성능 인터페이스의 확장 프로그램을 구성하는 now()
메서드를 호출합니다. 다음 코드는 그 방법을 보여줍니다.
var myTime = window.performance.now();
PerformanceTiming이라는 다른 인터페이스가 있습니다. 이 인터페이스는 웹 애플리케이션이 로드되는 방식과 관련된 여러 시간을 제공합니다. now()
메서드는 PerformanceTiming의 navigationStart
시간이 발생한 시점부터 경과된 시간을 반환합니다.
DOMHighResTimeStamp 유형
과거의 웹 애플리케이션 시간을 측정하려면 DOMTimeStamp를 반환하는 Date.now()
와 같은 함수를 사용했습니다. DOMTimeStamp는 밀리초 단위의 정수 값을 값으로 반환합니다. 고해상도 시간에 필요한 정확성을 높이기 위해 DOMHighResTimeStamp라는 새로운 유형이 도입되었습니다. 이 유형은 시간을 밀리초 단위로 반환하는 부동 소수점 값입니다. 하지만 부동 소수점이므로 값은 밀리초 단위까지 표시할 수 있으므로 1,000분의 1의 정확도를 얻을 수 있습니다.
사용자 시간 인터페이스
이제 고해상도 타임스탬프가 준비되었으므로 User Timing 인터페이스를 사용하여 타이밍 정보를 추출해 보겠습니다.
User Timing 인터페이스는 애플리케이션의 여러 위치에서 메서드를 호출할 수 있는 함수를 제공합니다. 이 함수는 헨젤과 그레텔 스타일의 탐색경로 트레일을 제공하여 시간이 어디에서 소비되는지 추적할 수 있습니다.
mark()
사용
mark()
메서드는 타이밍 분석 도구 키트의 기본 도구입니다. mark()
는 타임스탬프를 저장합니다. mark()
의 매우 유용한 점은 타임스탬프 이름을 지정할 수 있다는 것입니다. 그러면 API가 이름과 타임스탬프를 단일 단위로 기억합니다.
애플리케이션의 여러 위치에서 mark()
를 호출하면 웹 애플리케이션에서 해당 '표시'에 도달하는 데 걸린 시간을 파악할 수 있습니다.
이 사양은 흥미로울 수 있고 설명이 필요 없는 표시에 여러 가지 추천 이름을 제시합니다(예: mark_fully_loaded
, mark_fully_visible
, mark_above_the_fold
).
예를 들어, 다음 코드를 사용하여 애플리케이션이 완전히 로드되는 시점을 표시할 수 있습니다.
window.performance.mark('mark_fully_loaded');
웹 애플리케이션 전체에 명명된 마크를 설정하면 수많은 타이밍 데이터를 수집하고 시간이 날 때 이를 분석하여 애플리케이션이 무엇을 언제 수행하는지 파악할 수 있습니다.
measure()
로 측정값 계산
여러 개의 타이밍 표시를 설정했다면 타이밍 표시 사이에 경과된 시간을 확인할 수 있습니다. 이를 위해 measure()
메서드를 사용합니다.
measure()
메서드는 표시 사이의 경과 시간을 계산하며, 표시와 PerformanceTiming 인터페이스에서 잘 알려진 이벤트 이름 사이의 시간을 측정할 수도 있습니다.
예를 들어 다음과 같은 코드를 사용하여 DOM이 완료된 시점부터 애플리케이션 상태가 완전히 로드될 때까지의 시간을 계산할 수 있습니다.
window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');
measure()
를 호출하면 설정한 표시와 별개로 결과가 저장되므로 나중에 검색할 수 있습니다. 애플리케이션이 실행되는 동안 남은 시간을 저장함으로써 애플리케이션의 응답성을 유지하고 애플리케이션이 일부 작업을 완료한 후에 모든 데이터를 덤프하여 나중에 분석할 수 있습니다.
clearMarks()
로 표시 삭제
설정해 놓은 여러 표시를 제거하는 것이 유용한 경우도 있습니다. 예를 들어 웹 애플리케이션에서 일괄 실행을 수행하여 각 실행을 새로 시작할 수 있습니다.
clearMarks()
를 호출하면 설정한 표시를 쉽게 삭제할 수 있습니다.
따라서 아래 예시 코드에서는 기존의 모든 표시를 날려 버립니다. 따라서 원하는 경우 타이밍 실행을 다시 설정할 수 있습니다.
window.performance.clearMarks();
물론 모든 표시를 지우고 싶지 않은 시나리오도 있습니다. 따라서 특정 표시를 제거하려면 삭제하려는 표시의 이름을 전달하면 됩니다. 예를 들어 아래 코드는 다음과 같습니다.
window.peformance.clearMarks('mark_fully_loaded');
첫 번째 예에서 설정한 표시를 제거하고 설정한 다른 표시는 변경하지 않고 그��로 둡니다.
직접 실행한 측정도 삭제하는 것이 좋습니다. 이 경우 clearMeasures()
라는 메서드가 있습니다. clearMarks()
와 정확히 동일하게 작동하지만 대신 사용자가 측정한 모든 작업을 수행합니다. 예를 들면 다음과 같습니다.
window.performance.clearMeasures('measure_load_from_dom');
위 measure()
예에서 실행한 측정을 삭제합니다. 모든 측정을 삭제하려면 인수 없이 clearMeasures()
를 호출하기만 하면 된다는 점에서 clearMarks()
와 동일하게 작동합니다.
타임아웃 데이터 가져오기
표시를 설정하고 간격을 측정하는 것이 좋지만 언젠가는 분석하기 위해 해당 타이밍 데이터를 얻고 싶을 수 있습니다. 이 작업도 정말 간단합니다. PerformanceTimeline
인터페이스만 사용하면 됩니다.
예를 들어 getEntriesByType()
메서드를 사용하면 모든 표시 시간 또는 모든 측정 타임아웃을 목록으로 가져와 반복하고 데이터를 다이제스트할 수 있습니다. 좋은 점은 목록이 시간순으로 반환되므로 웹 애플리케이션에서 조회된 순서대로 표시를 볼 수 있다는 것입니다.
아래 코드는 다음과 같습니다.
var items = window.performance.getEntriesByType('mark');
는 웹 애플리케이션에서 적중된 모든 마크의 목록을 반환합니다. 단, 코드는 다음과 같습니다.
var items = window.performance.getEntriesByType('measure');
우리가 취한 모든 조치의 목록이 반환됩니다.
지정한 특정 이름을 사용하여 항목 목록을 다시 가져올 수도 있습니다. 예를 들어 코드는 다음과 같습니다.
var items = window.performance.getEntriesByName('mark_fully_loaded');
그러면 startTime
속성에 'mark_wide_loaded' 타임스탬프가 포함된 항목이 하나 있는 목록이 반환됩니다.
XHR 요청 타이밍 (예)
이제 User Timing API를 제대로 파악했으니 모든 XMLHttpRequests가 웹 애플리케이션에서 걸리는 시간을 분석하는 데 사용할 수 있습니다.
먼저 모든 send()
요청을 수정하여 표시를 설정하는 함수 호출을 실행하는 동시에 또 다른 표시를 설정한 다음 요청에 걸린 시간에 대한 측정값을 생성하는 함수 호출로 성공 콜백을 변경합니다.
따라서 일반적으로 XMLHttpRequest는 다음과 같습니다.
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
do_something(e.responseText);
}
myReq.send();
이 예에서는 요청 수를 추적하고 각 요청에 대한 측정값을 저장하는 데 사용하기 위해 전역 카운터를 추가합니다. 이를 위한 코드는 다음과 같습니다.
var reqCnt = 0;
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
window.performance.mark('mark_end_xhr');
reqCnt++;
window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();
위의 코드는 전송하는 모든 XMLHttpRequest에 대해 고유한 이름 값을 가진 측정을 생성합니다. 요청이 순서대로 실행된다고 가정합니다. 병�� 요청의 코드는 잘못된 순서로 반환되는 요청을 처리하기 위해 조금 더 복잡해야 하므로 독자를 위한 연습으로 남겨두겠습니다.
웹 애플리케이션이 여러 요청을 수행하면 아래 코드를 사용하여 모든 요청을 콘솔로 덤프할 수 있습니다.
var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
var req = items[i];
console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}
결론
User Timing API는 웹 애플리케이션의 모든 측면에 적용할 수 있는 훌륭한 도구를 많이 제공합니다. 웹 애플리케이션 전체에 API 호출을 분산하고 생성된 타이밍 데이터를 후처리하여 시간이 어디에 쓰이는지 명확하게 파악함으로써 애플리케이션의 핫스팟을 쉽게 좁힐 수 있습니다. 브라우저가 이 API를 지원하지 않으면 어떻게 해야 할까요? 문제 없습니다. API를 아주 잘 에뮬레이션하고 webpagetest.org와도 원활하게 작동하는 우수한 polyfill을 여기에서 확인할 수 있습니다. 무얼 기다리시나요? 지금 애플리케이션에서 User Timing API를 사용해 보세요. 속도를 높이는 방법을 알게 될 것입니다. 사용자는 사용 환경을 크게 개선해준 것에 대해 감사할 것입니다.