ghlee.dev
크롬 개발자 도구로 네이버의 성능 프로파일 훔쳐보기
2024. 3. 5.소위 Web3라고 불리는 블록체인 영역에서는 유려한 애니메이션 들어간 홈페이지가 인기를 끈다. 이전 회사는 블록체인 사업을 했었기 때문에 자연스럽게 그런 작업이 꽤 있었다.
유려한 홈페이지가 인상 깊은 아즈키 프로젝트의 홈페이지. 이더리움 NFT 역사에 한 획을 그었으며 희귀도가 높은 NFT는 한때 10억 원을 자랑했다.
애니메이션이 많은 작업을 하다 보면 느려지는 게 체감이 되면서 자연스럽게 개발자 도구의 성능 탭을 열게 되는데 이미 보라색으로 물든 수많은 레이아웃 시프트를 마주하고 뜨악하게 된다.
억대 거래가를 자랑하는 아즈키도 레이아웃 시프트는 피해 갈 수 없다.
이러다 보니 애니메이션 작업이 많을 때는 개발자 도구의 렌더링 옵션에서 Paint flashing
과 Layout Shift Regions
옵션도 켜두고 작업해왔던 거 같다.
두 옵션에 대해 잠깐 설명하면 Paint flashing은 브라우저 렌더링 과정 중 페인팅이 완료되어 화면에 그릴 때마다 초록색으로 변경되는 영역을 표시해 준다. 또한 Layout Shift Regions는 레이아웃 시프트가 발생되는 영역을 보라색으로 표시해 준다.
개발자 도구 우측 상단의 점자 메뉴의 More tools에서 Rendering을 클릭하면 옵션들이 나온다.
Paint flashing 옵션을 키고 네이버의 메뉴 확장 버튼을 클릭해 보면 메뉴 영역이 새롭게 페인팅 된다.
브라우저 렌더링을 신경 쓰기 시작하면서 성능 탭을 자주 열어보게 되었고 다행히 처음 성능 프로파일의 폭포수를 마주했을 때 느꼈던 당황 + 혼란스러움은 많이 사라졌다.
문득 내가 작업해온 홈페이지 말고 타사의 홈페이지를 프로파일링 해본 적은 없었다는 생각이 들었는데 구글을 제외하고 가장 많이 접속하는 네이버 홈페이지를 훔쳐보고 아는 만큼만 그 내용을 공유해 보고자 한다.
아는 만큼만 보는 네이버 프로파일링
Next.js 프레임워크
네이버 메인 페이지의 성능 탭에서 진입부터 시작하여 1000ms 가량의 기록을 가져왔다.
네이버 메인 페이지의 0ms ~ 1000ms 성능 프로파일
가장 먼저 눈에 보이는 게 있지 않은가? nextjs
의 hydration이 보인다. 역사가 오래된 포털 사이트이고 신경 써야 할 레거시가 많을 거 같은 네이버가 최신 웹 프레임워크를 사용하는 게 굉장히 의외이다.
프로파일만 보고 추측해 볼 수 있는 건 DCL(DOM Content Loaded)
이후에 hydration이 이루어졌다는 사실로 보아 메인 페이지가 nextjs는 아니고 iframe 같은 걸 이용해서 일부 영역만 사용했을 것으로 추측된다.
찾아보았더니 nextjs로 작업한 쇼핑 영역만 iframe으로 불러오고 있었다.
https://shopsquare.naver.com 이 링크로 들어가보면 쇼핑 영역을 볼 수 있다.
쇼핑 영역의 프로파일이다.
쇼핑 영역의 프로파일을 보면 hydration이 끝난 뒤 DCL이 찍혀있는 걸 확인할 수 있다.
이 과정을 통해 발견한 사실이 있는데 DCL의 타이밍은 iframe으로 가져온 컨텐츠와는 관계가 없다는 걸 알 수 있다. iframe으로 가져온 컨텐츠는 별도의 렌더러 프로세스에서 작동되기 때문으로 추측해 볼 수 있다. (항상 별도의 렌더러 프로세스가 할당되는 건 아니고 도메인따라 다르다.)
네트워크 프로세스
이번엔 네트워크를 펼쳐보자.
네트워크 프로세스의 요청 상태가 보인다.
크롬 브라우저에서 HTTP/1과 HTTP/1.1은 도메인 샤딩을 이용하여 최대 6개까지 요청이 가능하다. 그렇지만 네이버 메인 페이지에서는 보다시피 대부분이 사실상 요청 개수 제한이 없는 HTTP/2 이상에서 이루어지는 걸 볼 수 있다.
이것만 보고 HTTP/2와 HTTP/3를 구분하기는 어려우며 사실 개발자 도구의 네트워크 탭에서 보는 게 훨씬 직관적이고 편하다.
앞서 nextjs가 나온 김에 첨언하면 nextjs에서는 별도 세팅 없이도 프로덕션에서 HTTP/2를 지원한다. 다만 개발 환경에서는 TLS가 없어서 HTTP/1.1로 전환되므로 참고하길 바란다.
롱 태스크
네이버 메인 페이지의 첫 프로파일링 이미지를 다시 보면 Task
중에 빨간색 삼각형 마크가 붙어있는 영역을 볼 수 있다. 이는 Long Task
라고 불리며 50ms 이상 소요되는 태스크에 붙혀지는 상당히 불명예스러운(?) 마크이다.
이 영역을 확대해보면 다음과 같다.
확대한 Long Task 영역
자바스크립트 함수를 해석하는 중에 스타일을 계산하고 레이아웃 트리가 구성되는 걸 볼 수 있는데 사실 이는 성능 오버헤드의 주요 범인 중 하나이다. element.getBoundingClientRect()
같은 함수를 호출할 때 타겟 엘리먼트가 아직 구성되지 않은 레이아웃 트리에 있다면 급하게 레이아웃 트리를 구성하여 필요한 데이터를 전달한다고 생각하면 된다.
이렇게 만든 레이아웃 트리는 없애지 않고 추후 남은 레이아웃 과정을 진행할 때 캐싱처럼 도움이 되기도 해서 꼭 성능 오버헤드라고 볼 순 없지만 메인 스레드의 하나의 태스크가 너무 길어지는건 프레임 드랍의 주 원인이기 때문에 가급적 이러한 상황은 없는게 좋다.
항상 순서대로는 아니다.
Recalculate Style
이 있는 어느 구간을 확대해보았다.
해당 이미지는 Parse HTML
의 다음으로 Recalculate Style
을 진행하는 걸로 보인다. 얼핏 보면 둘은 연관 관계가 있는 작업처럼 보이지만 그렇지 않다. 이는 Recalculate Style을 클릭해 보면 알 수 있다.
클릭했더니 화살표가 생겼다. 이번엔 클릭된 상태로 줌 아웃하여 화면을 넓게 살펴보겠다.
그렇다. 우리가 보았던 Recalculate Style은 사실 옛날 옛적에 있었던 Parse HTML 과정 중에 태스크 큐에 스케줄 된 작업이었다. 참고로 브라우저의 태스크 큐는 일반적인 선입선출(FIFO)이 아니며 휴리스틱 기반의 스케쥴링으로 태스크의 순서가 바뀐다.
브라우저가 항상 일관된 순서로 작업되면 좋겠지만 그렇지 않다. 예를 들어 브라우저는 렌더링의 시작인 Parse HTML 과정에서 한 번의 태스크에 모든 태그를 파싱 하지 않는다. 내부 알고리듬에 의해 일부만 파싱하고 다른 우선해야 할 작업을 진행한다.
이런 식으로 모든 작업은 다양한 요인에 의해 순서가 뒤바뀌며 실행됨을 알고 있으면 성능 프로파일을 살펴보는 마음가짐이 좀 더 편하지 않을까... (아마도...?)
스크롤링
네이버 메인 페이지의 최상단에서 휠을 한 번만 돌려보았다.
메인 스레드에서 초록색 작업은 화면에 그리는 작업을 의미한다고 생각하면 되는데 운영체제에서 받는 VSync 신호에 맞춰서 하나의 프레임마다 약 16.7ms 내외로 스크롤 중인 화면의 렌더링을 끝마치는 걸 알 수 있다. 프레임 드롭 없는 이상적인 기록으로 볼 수 있다. (짝짝)
웹 사이트가 과거 웹 문서로서의 역할에서 대화형 웹 애플리케이션으로 진화하면서 페이지 진입 시 초기 렌더링만큼 대두되는 것이 유저의 인풋 이벤트 이후 렌더링이다.
인풋 이벤트는 자바스크립트를 통해 이루어지기 때문에 흔히 보았던 페이지 진입 시 HTML 파싱으로 시작하는 렌더링 과정에서 살짝 달라진다.
트레이싱 도구로 보는 초기 렌더링
크롬 개발자 도구의 성능 탭은 일반적인 웹 개발에 충분한 정보를 제공해 주고 있다고 생각한다. 하지만 크롬 브라우저는 성능 탭보다 더 자세히 볼 수 있는 트레이싱 도구를 제공하는데 주소창에 chrome://tracing
으로 접속하면 볼 수 있다.
굉장히 투박하게 생겼다. 성능을 자세히 파헤쳐줄거 같은 포스를 지녔다.
크롬 개발자 도구의 성능 탭에서는 성능 프로파일을 JSON 파일로 다운로드할 수 있는데 이를 트레이싱 도구에 업로드해 보았다.
좌측 패널에 평소 접하지 못한 용어가 많이 나온다.
낯선 용어가 잔뜩이지만 CPU usage는 누구나 알 법 하다. 그래프에서 하얀색 영역으로 좁혀놓은 CPU 사용량이 유독 많은 구간을 선택해서 확대해 보자.
확대한 구간의 렌더러 프로세스 정보
확대한 구간에서 렌더러 프로세스의 메인 스레드와 워커 스레드를 볼 수 있는데 7개의 워커 스레드에서 V8.GC_MC_BACKGROUND_MARKING
와 CppGC.ConcurrentMark
라고 하는 작업이 활발하게 진행 중이었다.
흔히 가비지 컬렉터를 줄여서 GC라 표현하며 MARKING 혹은 Mark는 말 그대로 가비지 컬렉션 대상을 찾아 표시해두는 걸 의미한다.
현재 이미지에서는 보이지 않지만 마킹한 뒤 다음 태스크로 V8.GC_MC_BACKGROUND_SWEEPING
와 CppGC.ConcurrentSweep
가 실행되는데 마킹 해둔 메모리를 청소하는 걸로 보인다.
이를 통해 가비지 컬렉터가 작동할 때는 CPU 사용량이 제법 높은 걸 알 수 있다.
참고로 크롬 브라우저에는 흔히 아는 자바스크립트 가비지 컬렉터(GC)뿐 아니라 Oilpan
이라고 하는 C++를 위한 가비지 컬렉터도 존재한다. 이는 V8의 자체적으로 만든 C++ 힙(Heap)에서 작동하는 가비지 컬렉터이며, V8의 격리(Isolate)된 인스턴스에서 사용하거나 아예 Oilpan만 독립적인 사용이 가능하다. 마치 V8의 파생 라이브러리처럼 관리되고 있으며 Oilpan에 대한 내용은 V8 블로그를 참고하거나 이 링크를 통해 자세히 알 수 있다.
마치며
개발자 도구의 성능 탭을 이용하여 네이버의 성능 프로파일을 가볍게 살펴보았다. 브라우저에는 휴리스틱 알고리듬 기반으로 처리되는 작업이 많고 다양한 요인으로 인해 성능 프로파일의 결과는 항상 동일할 수 없다. 성능에 문제가 있을 때는 동일한 시나리오를 여러 번 기록해 보길 바란다. 개발자 도구에 있는 스로틀링도 걸어보면 결과가 꽤나 다르다. (충격받을 지도...?)
성능 탭을 제대로 이용하기 위해서는 브라우저의 렌더링 과정은 필수로 알아야 하며 궁금하다면 RenderingNG
라는 키워드로 검색해 보길 바란다.