[프론트엔드 성능 최적화] (2)

yongkini ·2023년 1월 25일
0

개발자 도구 사용법

network 탭

: 전부터 알고 있었지만 network 탭을 이용하면 어떤 데이터가 다운받아지고 있고, 그 속도는 어떻게되며 파일의 크기는 어떻게 되는지 등을 알 수 있다. 추가로 backend api를 호출한 다음에 request, response 등이 어떻게 되는지도 network 탭을 통해 확인할 수 있다.
: 위의 이미지는 책에 나오는 블로그 서비스를 pull 하고 첫 랜딩 페이지에서 network 탭을 켜봤을 때의 화면을 캡처한 것이다. 위의 화면을 보면 network 탭에선 받아온 이미지들과 chunk.js 등의 번들링된 파일들(HTML, CSS, JS 등이 번들링된), 호출된 백엔드 API(articles라고 써있는 것) 등이 있다. 이처럼 network 탭을 이용하면 어떤 부분에서(서버로부터 다운로드 받을 때) 버퍼가 생기는지 등을 알 수 있고 이를 통해 로딩 성능 개선을 할수도 있다.

LightHouse 탭

: 사실 이 책을 읽으면서 처음 써보게 된 탭인데 상당히 유용한 것 같다. 구글에서 제공해주는 최적화 툴이라고도 할 수 있는데(정확히는 구글 크롬) 이 툴을 사용하면(기본 내장 툴) 현재 내가 구현중인 서비스가 어떤 부분에서 최적화가 필요하고, 그럴려면 어떤 부분을 건드려야할지 등을 알게 해준다.
: 위와 같은 형태로 돼있고, Analyze Page Load 버튼을 누르면 새로고침되면서 해당 페이지의 로딩 성능을 파악한다. 이 때, 위에서 category 등을 다르게(체크 박스) 설정하면 SEO 등도 테스팅해볼 수 있다. 실제로 위의 버튼을 눌러보면
위와 같이 나온다. 맨위부터 보면 퍼포먼스 점수가 88인걸 볼 수 있는데, 지금은 이미 최적화를 어느정도 해놨기에 저정도 점수가 나온다. 그 아래에 있는 METRICS 파트(=Web Vitals라고도 한다)에 있는 요소들을 설명해보면,

  • First Contentful Paint : 페이지가 로드될 때 브라우저가 DOM 콘텐츠의 첫번째 부분을 렌더링하는데 걸리는 시간에 관한 지표(10% 가중치)
  • SI(Speed Index) : 페이지 로드 중에 콘텐츠가 시각적으로 표시되는 속도를 나타내는 지표(10% 가중치)
  • Largest Contentful Paint(LCP) : 페이지가 로드될 때 화면 내에 있는 가장 큰 이미지나 텍스트 요소가 렌더링되기까지 걸리는 시간을 나타내는 지표다. ⇒ 25%의 가중치로 평가됨
  • Time To Interactive(TTI) : 사용자가 페이지와 상호 작용이 가능한 시점까지 걸리는 시간을 측정한 지표(10% 가중치)
  • Total Blocking Time(TBT) : 페이지가 클릭, 키보드 입력 등의 사용자 입력에 응답하지 않도록 차단된 시간을 종합한 지표로 FCP와 TTI 중간에 blocking(메인 스레드가 독점 당한 시간) 당한 시간이라고 할 수 있다. 30%의 가중치를 갖는 높은 평가요소.
  • Cumulative Layout Shift(CLS) : CSS 크기 등이 변하면서(페이지 로드 고정에서) 발생하는 예기치 못한 레이아웃 이동을 측정하는 지표이다. 이에 따라 화면상에서 요소의 위치나 크기가 순간적으로 변하는 것을 말한다(15% 가중치).

그리고 아래에 opportunity, diagnostics 는 전자는 말그대로 최적화할 수 있는 기회?를 말한다. 따라서 저기에 해당되는 리스트를 하나하나 해결하면 퍼포먼스 점수도 높아진다(최적화가 된다). 후자는 성능과 관련된 기타 정보를 보여준다. 결과적으로 방금 말한 두가지 지표를 바탕으로 최적화를 진행해서 performance를 높이고, 그에 따라 UX(유저 경험)를 더 좋게 만드는게 목표다. 여기서는(이번 포스팅) 로딩 성능 개선과 렌더링 개선에서 전자에 포커싱을 맞추고 진행해보고자 한다.

Performance 탭

: 그전에 Performance 탭을 먼저 보면 위에서 본 LightHouse 탭에서
위의 버튼을 누르면 performance 탭으로 넘어갈 수 있고, 따로 performance 탭을 눌러서 접근할 수 있다.
실제로 눌러보면 위의 화면이 뜬다. 대략적으로 몇가지 살펴보면,

  • CPU 지표 네트워크 차트 및 스크린샷

    : 상단부에는 CPU가 어떤 작업에 리소스를 사용하고 있는지의 비율을 보여준다. 색깔이 노란색, 보라색 등 다양한데 해당 부분에서 CPU가 어떤 부분에 가장 사용률이 많은지 등을 보여준다.
    추가로, 아래에 summary 탭을 보면 위에처럼 확인할 수 있다. 여기서 빨간색 부분이 있다면 병목이 발생하는 지점을 의미하므로 이 부분을 해결해야 최적화가 될 수 있다. 다음으로 네트워크 타임 라인은 network 패널과 유사하게 서비스 로드 과정에서의 네트워크 요청을 시간 순서에 따라 보여준다.
    이런식으로 표현이 되는데, 앞의 실선은 빌드업(?)과 같이 request를 하기 전 단계를 보여주고, 그 실선이 끝나면서 도형으로 이어지는 곳에 연한 파란색과 진한 파란색으로 칠해져 있는건 먼저, 전자는 요청을 보내고, 데이터를 처음 다운받기 시작하기 전단계를 의미하고, 후자는 실제 다운받는 과정을 보여준다. 마지막으로 도형 다음의 실선은 해당 요청에 대한 메인 스레드의 작업 시간을 말한다.

  • Main 부분 : 브라우저의 메인 스레드에서 실행되는 작업을 플레임 차트로 표현해서 보여준다. 이를 통해 특정 작업이 얼마나 오래 걸리는지 등을 파악해서 병목 코드 등을 최적화할 수 있다.
    위의 이미지에서 체크 표시한 코드에 써있는 removeSpecialCharacter 는 실제로 코드내에 있는 함수이다. 이 때, 저 함수의 실행이 너무 많은 시간이 걸린다면 이는 병목 코드이므로 해당 코드를 최적화할 필요가 있음을 보여준다(이 때, 아래에서 위의 순서로 코드가 실행된다고 생각하고 해석하면 된다.). 이처럼 main 부분을 통해 어떤 로직들이 스레드에서 어떤 순서들로 실행이 되고, 얼마나 시간이 걸리는지 알 수 있으므로 이를 통해 최적화가 가능하다.

실제 최적화 방법들(추후 시험처럼 다시 작성해보기)

이미지 사이즈 최적화

: 이미지 사이즈 최적화 측면에서 분석해보면, 프론트엔드에서는 이미지 크로핑 툴을 제공한 다음에 크로핑 된 이미지를 CDN 등의 서버에 저장하도록 하는 방법이 있다. 이미지 사이즈 최적화 이슈 자체가 예를 들어, 화면에는 240px x 240px 로 렌더링하는 이미지가 있을 때, 실제 이미지의 크기가 1280px x 1280px 이라고 해보자. 이 경우에 필요한 렌더링 부분의 크기에 비해 실제 이미지 크기가 커서 이를 request 해서 다운 받을 때 로딩 시간이 지연되는 문제가 있다. 이는 하나의 이미지를 받을 때도 문제고, 여러개의 이미지를 동시에 받을 때는 더더욱 문제가 된다. 이러한 케이스를 해결하기 위해서는

  • 앞서 말한 것처럼 FE 쪽에서 유저에게 크로핑 툴을 제공한다. 예를 들어, 인스타에서 이미지를 업로드할 때 특정 크기 만큼 강제 자르기를 시키는 것과 마찬가지이다.
  • CDN 쪽에서(우리쪽 백엔드가 될 수 있고 다른 호스팅 업체 일 수 있다) 이미지를 resize 해서 주는 방법(이 경우에는 request query parameter 등으로 width, height을 지정해줘야할 것)이 있다.

위와 같은 방법을 쓰면 아까 말한 렌더링되는 크기에 비해 쓸데없이 큰 이미지를 다운받기 위해 로딩 시간이 지연되는걸 막을 수 있다. lighthouse에서는 Cumulative Layout Shift라는 항목으로 이에 대해 평가를 해준다. 15%의 가중치를 가진만큼 유저의 사용자 경험을 보장해주는데 꽤나 큰 역할을 한다. 이미지 로딩이 늦어지면 -> 본래 기획했던 레이아웃이 완성되는데 오래걸리고 이는 유저의 사용자 경험성 저하로 이어지기 때문이다. 예를 들어, 모달창 안의 이미지가 로딩되지 않으면 모달이 작았다가 이미지가 다운완료되고 커지는 현상처럼 말이다. 물론 modal에 min-height을 주는 방법도 있다. 하지만 여기서 CLS 평가도 중요하지만, 유저가 이미지 자체를 늦게 보는 것도 하나의 사용자 경험 저하 요인이기에 최적화가 필요하다.

코드 분할

: Lazy , Suspense 와 관련이 있고, Webpack-bundle-analyzer 과도 관련이 있다. Webpack-bundle-analyzer를 통해 번들 크기를 크게 만드는 모듈 등을 추적하여 해당 모듈이 직접 쓰이는 부분을(페이지 단위라고 했을 때) 렌더링할 때 그 페이지를 import 해서 쓰는 식으로 할 수 있다. 예를 들어, A 페이지가 랜딩 페이지일 때, 번들의 크기를 키우는 모듈이 쓰이는 곳은 B 페이지라고 해보자. 그럼 굳이 A 페이지를 로딩할 때, 즉, 최초로 번들을 다운받을 때 해당 모듈을 다운 받으면 로딩 시간이 길어진다. 하지만 코드 스플리팅을 하면 즉, 각각의 페이지를 분리하면 A 페이지를 렌더링할 때는 해당 모듈을 다운받지 않다가 B 페이지를 렌더링할 때 해당 모듈을 다운받게 된다. 그래서 초기 로딩 속도를 더 빠르게 할 수 있다. 이 때 쓰이는게 앞서 말한 Lazy와 Suspense 이다.

  • 컴포넌트 지연 로딩 + 사전 로딩 : 위와 같은 방법을 써서 할 수 있는게 컴포넌트 자체도 분할하여 import 할 수 있다는 것이다. 예를 들어, 내가 carousel 을 특정 모듈을 가져다 구현을 해놨을 때, 그 캐러셀이 홈페이지가 렌더링 되자마자 쓰이는 것은 아닌 상황이 있다. 이런 상황에서는 페이지 자체를 분할하는 것보다는 해당 캐러셀 컴포넌트만 분할하는 방법이 더욱 유용하다. 이 때, 이 방법의 맹점이 있는데 이렇게 되면 초기 렌더링 시에는 해당 모듈을 다운받지 않기 때문에 효율적이지만, 결국 해당 모듈을 on 했을 때 다운을 받기 때문에 또다른 지연이 생긴다. 이에 따라 useEffect 상으로 componentDidMount 시점에 해당 모듈을 import 하던지 해당 캐러셀을 on 하기 위한 버튼에 mouse를 hover 했을 때 하던지 등의 방법으로 이러한 지연도 막을 수 있다. 이런 방법을 컴포넌트 사전 로딩이라고 생각하면 된다(앞서 말한건 컴포넌트 지연 로딩이다. 결론적으로 컴포넌트 지연 로딩 + 사전 로딩을 둘다 쓴 것).

위에 대한 체킹은

위와 같은 방법으로 가능하다.

텍스트 압축


위의 속성을 response header에서 본적이 있을 것이다. content-encoding 속성은 해당 response가 어떤 형태로 인코딩 됐는지를 보여준다. 이렇게 압축된 형태로 response를 보내주면 다운받는데 시간이 적게 들고, 이는 결국 로딩 성능 개선으로 이어지기 때문에 크기가 큰 텍스트 파일 등은 압축해서 보내는 것이 좋다. 이는 서버에서 해주는 역할이다.

병목 코드 최적화

: 사실 이건 모든 개발 분야에 똑같은 부분인데 시간 복잡도 측면에서 고려를 해볼만한 사항이다. 예를 들어, 특정 버튼을 누르면 시간 복잡도가 O(n**2)[n이 높다고 해보자] 이 소모되는 작업을 계속 수행한다고 해보자. 싱글 스레드로 작업하는 브라우저 엔진은 블로킹이 일어난다. 그에 따라 말그대로 다음 일처리를 하지 못하는 병목현상이 일어나게 되는 것이다. 그에 따라 최대한 이러한 시간 복잡도가 커지는 작업들은 효율화하는 것이 좋다(사실 이건 개발자로서 당연한 선택이다).

** FPS 를 체킹하면서 개발할 때 쓸만한 툴
ctrl + shift + p => Show frames

profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글