frontend

웹 성능 측정에 대한 주관이 많이 들어간 설명(feat.performance api)

하리하링웹 2024. 5. 8. 20:26

프론트엔드에서 측정해야하는 성능

 웹을 개발할 때 성능 관련하여 신경써야 하는 부분은 뭐가있을까?

아래의 3가지가 가장 중요한 3가지 요소라고 생각하고 있으며 위에서 부터 아래순으로 더욱 중요하다고 봐도 무방할 것 같다.

  • 로딩
  • 렌더링
  • 메모리

 

로딩

  • FCP(First Contentful Paint): 첫 요소가 로드 될 때까지 걸리는 시간
    * 로딩바, 텍스트 등
  • FMP(First Meaningful Paint): 의미 있는 첫 요소가 로드 될 때까지 걸리는 시간
    * 많은 크기를 차지하는 부분 (이미지) , deprecated 되었다고 생각해도 됨
  • LCP(Largest Contentful Paint): 주요 콘텐츠가 로드 될 때까지 걸리는 시간
    * 뷰포트 내에 있는 가장 큰 이미지, 텍스트의 렌더링 시간
    * FMP가 부정확해서 대안으로 나옴
    * 2.5초 이하를 권장하고 있다.
  • FID(First Input Delay): 사용자의 행동에 대해 이벤트 핸들러가 작동할 때 까지의 시간
  • CLS(Cumulative Layout Shift): 시각적 안정성을 측정(레이아웃의 변화)

 위에서 보이는 용어들로 웹의 성능을 지표로 확인할 수 있다. 내 생각에는 5가지중 압도적으로 중요한것은 LCP와 CLS라고 생각한다.

LCP

 먼저 LCP는 가장 큰 요소가 로드될 때 까지 걸리는 시간이다. 이는 사용자의 UX를 크게 해칠 수 있는 부분이며 개선하기 정말 까다로운 요소라고 말할 수 있다.

 웹 페이지에 처음 접근했을 때 제대로 된 화면이 보이기까지 10초가 걸린다면 개발자라면 “이 사이트 정말 못만들었네” 라는 생각이 먼저 들 것이다. 이는 단순하게 못만들었다는 생각을 떠나 해당 사이트의 신뢰도 자체를 엄청나게 잃게하는 부분이다.

 개발자가 아닌 사용자의 입장으로 봐도 기다리다 지쳐 페이지를 이탈하게 될 것이며 밤을 새워 만든 너무 편한 기가막힌 사이트인데도 첫 페이지 렌더만으로 절반 이상의 유저를 잃게 될 것이다. 따라서 어느 정도의 기준을 정해 놓고 직접 디버깅을 해가며 1순위로 개선하는것을 추천한다.

 더 이상 속도를 개선할 수 없다면 간단한 로딩, 스켈레톤 UI를 넣어놓아 사용자에게 기다리는 즐거움을 주는 방안도 아주 훌륭한 방법이다. 이러한 방식은 토스 앱을 참고하면 많은 도움이 될 것이다.

CLS

 CLS 역시 LCP 못지않게 중요한 요소이다.

 왜 중요한지 이해하기 쉽게 예를들어 설명해 보겠다. 핸드폰으로 정부 혹은 은행 관련된 업무가 있어 해당 웹 페이지에 접속하여 열심히 개인정보를 입력하고 핸드폰 인증도 완료한 상태이다. 페이지가 이동되고 드디어 최종 확인 버튼을 누르려고 했을 때 갑자기 위쪽에서 어떤 이미지가 렌더되며 버튼이 아래로 내려가고 이상한 광고가 눌러져서 다른 페이지로 이동되었다. 제발 이라고 기도하며 뒤로가기 버튼을 눌렀지만 예상했던대로 귀찮은 절차를 처음부터 다시 겪어야한다.

 아마도 대부분의 사람들이 위와 같은 경험을 해보았을 것이다. 뿐만 아니라 스크롤을 하면 드드드득 거리는 동작, 페이지 첫 접근시 수시로 화면이 변하는 동작 등 모두 CLS를 신경쓰지 않아 벌어지는 일들이다. 유저의 기분을 위해 반드시 CLS를 신경써서 개발하는 개발자가 되도록 하자! CLS를 챙기는건 그리 어려운 일이 아니다, 대부분의 경우 이미지, 비디오만 신경써주면 된다. 이미지와 비디오를 쓸 때는 반드시 width, height 값을 같이 받아 해당 공간을 미리 확보해주자. 개발 방법은 구글에 각종 방법으로 많이 적혀져 있을테니 그것을 참고하면 된다. 정말 단순하지 않은가? 위의 방법을 적용한 것만으로도 이미 대부분의 layout shift는 극복하였다고 말할 수 있다.

 

렌더링

  • 브라우저의 렌더링 과정이 얼마나 걸리는지
    * 16ms 아래가 가장 권장되지만 상황에 따라 다름

 렌더링 관리는 한마디로 말해 어렵다고 말할 수 있다. 깊이 들어가면 EventLoop의 자세한 동작, 현재 stack, queue의 관리 모두를 자세히 알아야 완벽하게 핸들링 할 수 있다. 아니 이를 다 알더라도 완벽하게 핸들링 하는 것은 불가능하다. 데이터가 커지면 커질수록 더더욱 어려워진다. 하지만 대부분의 경우 유저의 눈에 보일정도로 극심한 프레임 드랍은 일어나지 않으니 자바스크립트의 자세한 동작을 아직 잘 모른다면 크게 신경쓰지 않아도 된다.

 자바스크립트의 동작을 잘 모르는 사람이 엄청나게 많은 데이터를 렌더링까지 신경쓰며 개발할 일이 거의 없을 것이며 이러한 개발을 하게된다면 공부하지 않고는 극복할 수 없기 때문에 어쩔 수 없이 자바스크립트에 대해 자세하게 공부하게 될 것이다. 그리고 자바스크립트의 동작에 대해 잘 알게된다면 이러한 렌더링으로 인한 문제는 자연스럽게 해결 될 것이다. 그래도 팁을 주자면 task를 지연시키고 당겨주는 것을 적절하게 조율하는것만으로도 많은 문제가 해결 될 것이다. 정말이다.

 

메모리

  • 전역변수
  • 해제하지 않은 타이머, 콜백
  • 클로저
  • 돔 외부에서의 참조

 사실 메모리는 위의 2가지에 비해 중요도가 낮은 편이라고 말할 수 있다. 일단 메모리 관련하여 문제가 생기면 매우 높은 확률로 프로그램이 터지는 것을 확인할 수 있으며 이를 해결하기 위해 열심히 디버깅 할 것이기 때문이다. 그래도 해제하지 않은 타이머, 콜백은 꼭 신경써주자, 아주 천천히 브라우저가 멈추는 것을 보기 싫으면 말이다.

 

성능 어떻게 측정하나요?

크롬 개발자 도구

  • Lighthouse
  • Performance

  • Memory

  • Network

  • React Profiler

 

각종 모니터링 도구들

PageSpeed Insights

WebPageTest

etc…

 

Performance API

 위의 2가지 방식은 모두 e2e 테스트만을 위한 도구들이다. 하지만 performance API는 자바스크립트에 내장되어 성능 측정을 도와주는 개발자를 위한 도구이다.

사용법

Performance.now

 페이지가 로드된 이후 지난 ms를 반환해준다.

const t0 = performance.now()
for (let i = 0; i < array.length; i++) {
  // some code.......
}
const t1 = performance.now()
console.log(t1 - t0, 'milliseconds')

 

Date.now를 사용해도 괜찮지 않나요?

System time을 기반으로한 Date를 기준으로 실제 사용자를 모니터링하는 것은 적절치 않다. 대부분의 시스템은 정기적으로 시간을 동기화 하는 데몬을 실행한다. 그리고 그 시계는 15분 내지 20분 마다 몇 ms 씩 조정되는 것이 일반적이다. 따라서 그 속도에서 측정된 10 초간격의 1% 정도가 부정확할 것이다.

  • Performance 사용 시 크롬 디버깅을 통해 코드를 멈추거나 한 뒤 다시 실행할 경우 멈추어진 시간 만큼 딜레이가 발생하게 되니 착각하지 말고 주의해서 사용해야 한다.

 개인적인 생각이지만 정말 세세하게 성능을 측정해야 하는 상황이 아닌 이상 Date.nowPerformance.now나 비슷비슷한 결과를 가져 올 것이다. 하지만 대부분의 개발자들이 이를 불편해할테고 나 역시 마찬가지이기에 Performance.now를 놔두고 굳이 Date.now를 사용하지는 않을 것 같다.

 

Performance.mark , performance.measure

  • Performance.mark: 이름과 함께 호출하여 Performance Buffer에 측정 값을 마킹해주는 역할을 해준다.
  • Performance.measure: 마킹된 퍼포먼스를 기준으로 측정 값을 얻을 수 있는 함수이다.

 

예시)

  1. 페이지 로딩 성능 측정
// 페이지 로딩 시작 시 마크 설정
performance.mark('startLoad');

// 페이지 로딩이 완료됐다고 가정하고 마크 설정
performance.mark('endLoad');

// 시작과 종료 마크 사이의 시간 측정
performance.measure('pageLoad', 'startLoad', 'endLoad');

// 측정 결과 확인
console.log(performance.getEntriesByName('pageLoad')[0].duration);
  1. 동적 콘텐츠 로딩 측정
// 동적 콘텐츠 로드 시작 시
performance.mark('startDynamicContentLoad');

// 콘텐츠 로드 완료 후
performance.mark('endDynamicContentLoad');

// 측정하고 결과 출력
performance.measure('dynamicContentLoad', 'startDynamicContentLoad', 'endDynamicContentLoad');
console.log(performance.getEntriesByName('dynamicContentLoad')[0].duration);

 

 위 코드만 봐도 사용하기 까다롭고 귀찮다는 생각이 들 것이다. 이는 직접 써본적은 없지만 아마 규모가 어느정도 있는 코드의 단위 성능 측정이 필요할 때 사용할 것 같다. 물론 하드 코딩 하지 않고 유틸을 만들어 사용성을 더 높인 다음 말이다. (위의 코드는 너무 사용하기 불편해보이지 않은가?)

 

 

Performance entry buffer

 Performance api로 측정된 값들은 모두 위 버퍼에 저장이 되며 이는 아래 메서드들로 가져올 수 있다.

  • performance.getEntries(): performance entry buffer에 저장된 값의 리스트 반환
  • performance.getEntriesByName('name'): 마킹된 이름에 해당하는 리스트 반환
  • performance.getEntriesByType('type'): 특정 타입으로 저장된 리스트 반환  measure, mark만 가능

 위 설명에서 볼 수 있듯이 Performance API는 사용시에 Performance entry buffer에 저장되기 때문에 잘 관리하면서 사용해야 한다.

 라고 말을 했지만 잘 만들었다고 생각하는 notion의 performance entry buffer를 확인해보고나니 내 생각이 틀린가? 라는 의심이 조금 든다.

 

 

console.time, console.timeEnd

console.time으로 측정 시작하고 종료할 때 console.timeEnd 호출

console.time('test')
for (let i = 0; i < array.length; i++) {
  // some code
}
console.timeEnd('test')

 개인적으로는 간단하게 성능을 측정할 때 사용하기 가장 쉬운 방법이 아닌가 생각한다. 여기서 ‘test’는 key값으로 생각하면 되며 console.time 으로 ‘test’타이머의 시작을 알리고 console.timeEnd로 타이머의 종료를 알린다.

 

 

console.time 없이 console.timeEnd 만 호출한다면 undefined가 출력된다.

 

 

[참고 글]

https://yceffort.kr/2020/12/measuring-performance-of-javascript-functions