안녕하세요 웹 프론트엔드 팀 조강연입니다.
최근 사내에서 진행하는 프로젝트들에 대해서 웹 프론트엔드 팀이 성능을 관리하기 시작했습니다.
단순히 제품을 만들어내는 작업 외적으로도 성능을 측정하고, 개선하는 과정에서 유저에게 더 좋은 경험을 선사한다는 동기부여를 받기 때문인데요, 이번 글에서는 웹 프론트엔드 팀이 성능이라는 키워드에 관심을 가지게 된 배경과 그것을 팀의 한 문화로 안착시키기 위해 기울인 노력 등에 대해서 소개하려고 합니다.
성능에 관심을 갖게된 배경은 이렇습니다.
최근 사내에서 몇몇 핵심과제를 진행하면서, 앱내 일부 페이지를 웹뷰로 전환하는 프로젝트를 진행하였습니다.
앱 내 사용률이 높은 영역이다보니, 사용성 측면에서 유저의 경험이 필두로 중요시되는 영역이었으며, 해당 페이지는 Google, Naver와 같은 검색엔진을 통해서도 접근 가능한 반응형 웹의 형태로 개발이 진행되어야 했습니다.
제품을 개발하는 과정에서 요구사항을 구현하기 위해 처음부터 최적의 구현체를 찾는다는 것은 현실적으로 어려운 일이라고 판단하였고, Lighthouse 라는 검사도구를 사용하여 구현과정에서 피드백을 받고 best practice를 찾아가면 좋을 것 같다고 판단되어 성능 검사를 병행하며 개발을 진행하였습니다.
Lighthouse란
Lighthouse는 구글에서 만든 웹 성능 측정 도구입니다. 개발자 도구에서 사용 가능하며, 성능 측정 결과와 함께, 웹페이지가 접근성을 준수하여 구축되었는지, 검색 결과에 잘 노출되기 위한 최적화 권고들을 따랐는지 피드백을 주는 도구입니다.
Lighthouse 성능 측정 시 보고되는 6가지 주요 지표들
Largest Contentful Paint
웹 페이지가 처음될 로드 될 때, 뷰포트 내에 있는 가장 큰 이미지, 텍스트 블록이 렌더링되는 시간에 대한 지표.
First Contentul Paint
웹 페이지가 로드되기 시작하는 시점부터, 페이지의 일부 콘텐츠가 화면에 렌더링 될 때 까지의 시간에 대한 지표
Total Blocking Time
웹 페이지의 일부 콘텐츠가 렌더링 되는 시점(FCP) ~ 웹 페이지와 상호작용이 가능해지는 시점 사이의 총 시간에 대한 지표
Speed Index
웹 페이지가 로드 될 때, 콘텐츠가 시각적으로 표시되는 속도에 대한 지표
First Meaningful Paint
웹 페이지 내의 주요 콘텐츠가 사용자에게 표시되는 시기에 대한 지표
Cumulative Layout Shift
웹 페이지 렌더링 과정 중 특정 요소가 뒤늦게 화면에 나타나거나 또는 그로인해 발생하는 레이아웃 밀림 현상에 대한 지표
웹 성능을 측정하기 위한 지표들과 도구들은 매우 다양합니다. 이에 따라 사용자에게 전달되는 서비스의 품질을 이해하는 것은 비개발자들에게 선망의 대상이기도 했는데요. 이제는 성능에 대한 모든 지표들을 파악하지 않아도, 위 6가지 지표들을 통해 사용자에게 전달되는 경험의 품질을 간단하게 이해할 수 있습니다.
그리고 이는 실제로 Lighthouse 팀의 목적이기도 합니다.
Core Web Vitals만 알아도 사용자 경험의 품질이 어떤 지 이해할 수 있어요.
실제 성능 개선 과정
실제 프로젝트 개발 초기에 마주했던 피드백들입니다.
Serve images in next-gen formats
해당 코멘트는 차세대 형식의 이미지(webp, avif) 이외의 확장자를 사용할 때, 만날 수 있는 코멘트입니다. 해결 방식 자체는 매우 간단한데요, 기존 jpg, jpeg, png 등의 확장자를 사용하고 있는 이미지를 webp 형태로 전환해주기만 하면 됩니다.
그런데…
저희 팀은 이미 모든 이미지를 webp 형태로 사용중에 있었습니다. 진짜 원인을 파악하기 전까지는 Lighthouse가 거짓말을 치는 줄 알았어요..
네이버 지도 API를 사용중입니다.
모두가 아시다시피 네이버 지도는 이미지를 기반으로 제공됩니다. 그리고 이미지는 영역별로 Crop 되어 전송되는데요, 이 이미지들이 차세대 형태를 따르지 않고 있었고, Lighthouse는 아주 정직하게 할 일을 한 것으로 파악되었습니다.
하지만 네이버지도에서 제공되는 이미지를 차세대 형태로 바꿀 수도 없는 노릇이기에 저희팀은 다른 방법을 강구했습니다.
지도 영역에 스크롤 되었을 때, 이미지를 조회해옵니다!
지도 이미지들이 로딩되는 시점에 대해서 다시 생각하게 되었고, ‘보지 않을수도 있는 이미지를 초기에 전부 조회해오는 것은 굉장히 비효율적이다’ 라는 다소 원론적인 측면에서 문제에 다시 접근하게 되었고, ‘유저가 스크롤을 하여, 지도 영역에 다다랐을 때에만 이미지들을 조회해오자’ 라는 결과를 도출하게 되었습니다.
Intersection Observer를 사용하여 구현하였으며, 구현에 대한 자세한 설명은 글의 주제에 벗어난 내용이기에 생략합니다.
위의 방식을 통해 LCP와 FCP를 개선할 수 있었습니다.
Reduce unused JavaScript
사용하지 않는 자바스크립트 코드가 너무 많을 때 받을 수 있는 피드백입니다.
외부 라이브러리들에 의존도가 높은 프로젝트 일수록 조심해야 하는데요. 특정 라이브러리의 기능 한 개를 사용하겠다고, 의존성 라이브러리를 무지성으로 설치할 때, Lighthouse는 위와 같이 경고합니다.
Lighthouse 메뉴에서 성능 측정 후 [트리맵 보기] 버튼을 클릭하면, 빌드된 청크파일들 내에서 사용되지 않는 javascript 코드들이 얼마나 되는지 파악할 수 있습니다.
Code Splitting(코드 분할)을 통해 초기 페이지 로딩 시 사용되지 않는 코드들을 배제하여, 불필요한 비용을 줄일 수 있으며, 필요할 때만 불러오도록 처리할 수 있습니다.
- React: https://ko.reactjs.org/docs/code-splitting.html
- Next: https://nextjs.org/docs/advanced-features/dynamic-import
저희 프로젝트에서는 Naver Map API와 Kakao SDK를 사용중이며 Next Script 태그를 사용하여 외부 스크립트를 사용하고 있습니다.
외부 스크립트를 가져올 때는 렌더링이 중단(차단)되지 않도록 조심해야 합니다. FCP 지표에 큰 영향을 끼칠 수 있어요.
Pure JavaScript 환경에서는 script 태그에 defer, async 옵션을 주어 렌더링 차단을 방지합니다.
// 스크립트 로드 병렬 처리
// HTML 파싱 이후 스크립트 해석
<script defer src="..."></script>
// 스크립트 로드 병렬
// 스크립트 로드 완료 시 HTML 파싱 완료여부 상관없이 스크립트 해석
<script async src="..."></script>
Next.js를 사용한다면 이렇게 사용할 수 있어요.
// 기본값
// HTML 파싱 이후 바로
<Script src="..." strategy="afterInteractive" />
// HTML 파싱 이후 유휴 시간에
<Script src="..." strategy="lazyOnload" />
위의 방식들을 통해 FCP를 개선할 수 있었습니다.
다소 아쉬웠던 점은 기획 요구 상, 네이버 지도 API, Kakao 메시지 공유 기능 등을 사용해야 했는데요, 실제로 사용할 기능들에 비해서, 설치해야 할 리소스가 너무 컸기에 사용되지 않는 자바스크립트 코드가 기하급수적으로 늘어났습니다. 라이브러리가 일부 기능들만 사용할 수 있도록 부분 제공된다면 어떨까 하는 아쉬움이 남았습니다.
비약적인 성능 향상을 이뤄내진 못했지만, 이번 경험을 통해 성능을 신경써서 개발하는 문화가 본격적으로 자리를 잡기 시작하였습니다.
성능 모니터링 시스템 구축
웹 프론트엔드 팀에 존재하는 프로젝트들의 성능 변화 추이를 모니터링하기 위해 자체적으로 개발한 프로젝트입니다.
Lighthouse SDK를 사용하여 성능 측정 프로세스를 구축했고, Web Vitals 수치를 기반으로 지표들을 수집하고 있으며, 성능 측정 주기는 따로 존재하지 않고, 배포 시점에 항상 트리거 됩니다.
설계 및 개발까지 완벽하게 끝냈고.! 이제 성능 지표 데이터를 쌓을 일만 남았습니다.
그런데…
아주 큰 문제가 도사리고 있었습니다. 측정 결과가 굉장히 가변적입니다.
성능 추이를 관찰하기 위해서는 보다 일관성 있는 결과가 보장이 되어야 합니다. 근데 고작 몇 분 차이로 그것도 같은 환경(실제로 10시 44분에 배포하는 작업 내용은 간단한 텍스트 변경 건에 불과했습니다.)에서 측정한 성능이 이렇게나 큰 차이를 보이는게 비상식적이란 말이죠.
Lighthouse의 성능 측정 결과는 왜 가변적일까?
우리는 Lighthouse로 성능을 측정할 때, 결과가 매번 변한다는 것을 알고 있습니다. 당장 아무 페이지나 접속해서 성능 측정을 해봐도 두 번의 검사 결과가 다른 것을 확인할 수 있는데요, 실제로 Lighthouse 개발팀에서는 가변성을 줄이기 위해 많은 노력을 기울이고 있습니다.
lighthouse에서는 성능 측정 결과가 가변적인 이유에 대해서 총 8가지의 원인을 들어서 설명해주고 있는데요, 그중 우리가 밀접하게 느낄 수 있는 원인들을 추려서 가져와봤습니다.
로컬 네트워크 가변성
높음
네트워크 통신 과정 중 패킷 손실, 트래픽 우선 순위 지정 등의 원인으로 가변성이 생길 수 있습니다. 저렴한 라우터나 제한된 대역폭을 공유하는 장치를 사용하는 유저는 이에 가장 취약함
웹 서버 가변성
낮음
웹 서버는 많은 요청들을 동시다발적으로 처리하기 때문에 항상 동일한 지연으로 응답하지 않습니다.
클라이언트 하드웨어 가변성
높음
웹페이지가 로드되는 하드웨어 종류에 따라 성능 결과에 큰 영향을 미칠 수 있습니다.
클라이언트 리소스 경합
높음
lighthouse가 실행되는 동안 동일한 시스템에서 실행되는 프로세스들은 CPU, 메모리 및 네트워크 리소스에 대한 경합을 일으킬 수 있습니다.
페이지 비결정성
중간
페이지에는 사용자가 페이지를 경험하는 방식을 변경하는 비결정적 로직이 포함되어 있을 수 있습니다.
어떻게 개선을 할 수 있을까요?
성능 측정 횟수 늘리기
성능 측정을 최소 5번 이상 실행하라고 권고하고 있으며, 의미 있는 지표를 얻기 위해 5번 실행한 점수들 중에서 중앙값 혹은 최소값과 같은 집계값을 사용하길 권장하기도 합니다.
호스팅 랩 환경 사용하기
제 3자의 영향을 받지 않는 깨끗한 환경에서 테스트 해보는 것도 성능 지표를 안정적으로 만드는데 큰 도움이 될 수 있습니다. WebPageTest 와 같은 호스팅 랩 환경을 사용해도 좋고, PageSpeed Insight는 API로도 제공되니 사용해봐도 좋을 것 같습니다.
Lighthouse로부터 비결정성 로직 분리하기
최신 SPA 라이브러리로 만드는 웹앱들은, 특정 상태에 따라서 마크업이나 애니메이션 효과들을 분기처리 하는게 굉장히 쉬워졌습니다. Lighthouse가 측정될 때마다 이런 비결정성 로직들이 존재한다면 성능에도 큰 영향을 줄 수 있습니다.
성능 모니터링 시스템이 개선된 후의 지표들을 공유해드리지 못해서 너무 아쉽습니다. 모니터링 시스템은 현재 개선중인 관계로 추후 포스팅을 하게 된다면, 결과를 공유드리도록 하겠습니다.😭
마무리하며
사실 처음에는 성능 관리라는 도메인은 두렵기만한 존재였습니다..😂 항상 제품을 만들어내기만 하고, 만들어진 제품에 대해서 별도로 리뷰하는 시간을 갖지 않았는데, 내가 작성한 코드들이 성능적인 측면에서 어디가 모자란지 스스로 뜯어보는 일은 쉽지 않은 일이기 때문이죠..
개발 작업과 성능 관리를 병행하다보니, 성능적인 측면에서 우려가 되는 부분들이 즉각적으로 파악되는 점은 좋았지만, 그만큼 많은 시간을 할애해야 했습니다. 앞으로는 성능 관리와 개발 작업의 벨런스를 조금씩 찾아갈 수 있도록 노력해보려고 합니다.
이렇게 저희 팀은 성능 관리라는 도메인을 팀의 한 문화로 천천히 녹여나가고 있습니다! 이 글을 보시는 분들도 성능 관리를 조금씩 해보는 건 어떨까요?
감사합니다.😊