안녕하세요! Web Builder를 개발하고 있는 프론트엔드 개발자 Kyungsle입니다. 제가 제작하고 있는 웹 빌더는, drag & drop만으로 웹사이트가 만들어지는 프로그램입니다.
사이트 : https://qshop.ai/
회고 : web builder를 만든 주니어 프엔 개발자의 회고
열심히 개발을 하던 중 drag 시 성능 저하를 발견했습니다. 이 글에서는 이를 측정하고, 원인을 분석하여 해결한 방법을 적어볼까 합니다. 측정 방식이 누군가에게 도움이 되면 좋겠습니다.😊
1. 성능 저하의 원인
상위 컴포넌트는 하위 컴포넌트의 리렌더링 발생시킵니다. Memo를 하지 않으면 하위 컴포넌트는 상위 컴포넌트 리렌더링 시 무조건 리렌더링 됩니다. drog & drop 시 하위 컴포넌트를 조작하는데 상위 컴포넌트도 리렌더링 되게 하여 성능 비효율이 발생했습니다.
2. 측정 환경
2-1. 측정 도구
제가 성능 개선을 위해서 사용할 도구들입니다.
- Console (콘솔) : 웹 페이지의 실시간 로그를 볼 수 있습니다.
- Performance (성능) : 웹 애플리케이션의 런타임 성능을 기록하고 분석할 수 있는 탭입니다.
- Profiler (프로파일러) : React 개발자 도구의 일부로, React 컴포넌트의 렌더링 성능을 프로파일링합니다.
console과 profiler를 통해 컴포넌트 렌더링 횟수와 성능(ms)을 측정하고, performance로 메인 스레드 사용량을 알 수 있습니다. 해당 글을 읽으시는 분들은 대부분 개발자라고 생각하여 console에 대한 설명은 생략하겠습니다.
2-2. performance tab 측정 방법
탭에 대한 전반적인 사용 방법은 아래의 문서를 참고하시면 됩니다.
https://developer.chrome.com/docs/devtools/performance?hl=ko
이 탭은 웹페이지의 로딩 성능, 렌더링 성능, 스크립트 실행 성능 등을 측정하고 분석하는데 사용됩니다. 네트워크, CPU, FPS 등의 측정 항목 중에서 저는 Main Timeline
을 사용할 것입니다.
Main
은 주로 웹사이트의 메인 스레드를 가리킵니다. 이 메인 스레드에서는 JavaScript의 실행, 스타일 계산, 레이아웃 생성 등 웹사이트의 주요 동작들이 처리됩니다. Main 영역에서는 이런 메인 스레드의 동작들이 얼마나 시간을 소요하고 있는지를 볼 수 있습니다.
개발자 도구에서 Main 영역에서 소요되는 시간이 50ms를 초과한다면 긴 시간이 걸리는 작업으로 판단하여 빨간색 태그를 붙여 보여줍니다. 사진에서는 50.7ms 가 측정되며 빨간 태그가 붙여진 것이 보이지요. 저는 drag & drop 구간을 약 200ms 정도 범위로 제한하여 성능을 분석할 예정입니다.
2-3. profiler tab 측정 방법
해당 탭의 사용 방법은 아래의 글에서 잘 설명해주시고 계십니다.
https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html
+) 커밋 단계는 React가 변경 사항을 적용하는 단계입니다. React가 DOM 노드를 삽입, 업데이트, 제거하는 경우입니다.
1번 과정을 통해 여러 commit 중 렌더링 시간이 가장 오래 걸리는 commit을 찾아 정보를 가져올 것입니다.
빨간색 테두리 내부의 막대들은 해당 commit에서 각 컴포넌트들의 렌더링 시간을 나타냅니다. 렌더링 시간에 대한 내림차순입니다.
- 노란색 막대 : 더 많은 시간이 걸립니다.
- 초록색 막대 : 더 적은 시간이 걸립니다.
- 회색 막대 : 현재 commit에서 렌더링하지 않습니다.
빨간색 박스의 막대를 클릭하면, 파란색 박스에서 렌더링에 걸린 시간을 확인할 수 있습니다.
2-4. 작업 대상 설명
이 글에서 주로 사용하는 용어에는 섹션
과 컨텐츠
가 있습니다. 저는 이 섹션과 컨텐츠 사이에서 발생하는 리렌더링의 비효율을, 섹션 -> 컨텐츠로 이어지는 상태값을 분리하여 해소했습니다.
Drag & Drop 시 컨텐츠가 움직이는 정보를 섹션에서 관리하고 있었습니다. 컨텐츠가 움직이면 섹션이 전체 리렌더링 되고 있던 상황이었죠.
성능 비교를 진행하기 때문에 모든 환경을 통제하고 설정하지는 않았습니다. 다만, 개선 전-후 환경은 코드 로직을 제외하고 모두 동일합니다. local 환경에서, 섹션 위에 104개의 컨텐츠를 얹어 확인해보았습니다.
3. 측정 / 개선 전 상황
3-1. console
console에서 빨갛게 보이는 것이 섹션의 리렌더링이며, 초록색은 컨텐츠 리렌더링입니다.
- 드래그를 조금이라도 할 때마다 컨텐츠가 104번 리렌더링이 되고 있음이 보입니다
- 104번의 렌더링과 더불어 섹션도 리렌더링 됩니다.
- drag & drop 애니메이션도 버벅거리고 있습니다.
3-2. profiler
react profiler를 체크해보면 다음과 같습니다.
- 100ms가 나온 시점은 mouse up (drop) 시점으로 보입니다.
- 그 전에는 20ms ~ 50ms 사이를 오가며, 드래그 시 렌더링 시간을 나타냅니다.
- 주된 렌더링 원인은 Slime이라는 컴포넌트이며 이는
컨텐츠
를 의미합니다.
3-3. performance tab
performance tab에서는 세 부분에서 비효율이 발견되었습니다.
순서대로 컨텐츠 drag ➡️ 컨텐츠 drop ➡️ drop 후 전체 페이지
입니다.
3-3-1. 컨텐츠 drag 시
첫 번째로 확인할 스레드 작업은 Slime 컴포넌트에서 발생하는 것이며 이는 컨텐츠
를 의미합니다. 62.99ms의 total time을 보여주며 그 중 13.8%는 Slime 컴포넌트의 렌더링에서 발생하고 있음이 보입니다. renderWithHook
라는 함수가 발생되고 있음이 보입니다. React에서는 useState, useEffect, useContext 등의 내장 훅이 있습니다.
renderWithHook
은 이러한 훅 중 하나가 컴포넌트와 함께 렌더링되는 과정을 지칭할 수 있습니다. 드래그 시 컨텐츠의 위치를 state로 설정해두었는데 이 것이 변하면서 렌더링이 발생하고 있다고 생각됩니다. 드래그를 조금만 하더라도 104번의 컴포넌트 리렌더링을 발생시키고 있으니 무거운 작업이 아니라면 더 이상합니다🤔
3-3-2. 컨텐츠 drop 시
두 번째는 mouse up시 발생하는 함수이며 drag & drop의 drop을 의미합니다. 이 때에는 드래그로 인해서 발생한 컨텐츠 위치 변화 정보를 적용하기 위해, 스타일을 다시 계산하는 과정을 거칩니다. 이 부분은 130ms나 드는 거대한 작업이지만, 지금 하려는 개선 방식으로는 잡을 수 없는 부분입니다. 추후 성능 개선을 다시 해야 합니다.😅
3-3-3. 섹션 리렌더링
긴 작업 소요 시간인 89ms가 걸리는 것으로 확인됩니다. 이 중 약 20%는 Grid라는 컴포넌트로 이는 섹션
을 의미합니다. drop 후 섹션이 리렌더링되는 과정에서 너무 많은 작업을 할당 받은 것이지요.
4. 측정 / 개선 후 상황
4-1. console
초록색 콘솔이 컨텐츠 리렌더링을 의미하고, 빨간색 콘솔이 섹션 리렌더링을 의미합니다. 기존에는 작은 움직임에도 104번의 컨텐츠 리렌더링과 한 번의 섹션 리렌더링이 같이 발생했습니다.
개선 이후에는, 컨텐츠의 리렌더링만이 움직인 정도로 발생하고 있음이 보입니다.
4-2. profiler
- 45.2ms가 나온 시점은 mouse up (drop) 시점입니다.
- 그 전에는 2ms ~ 5ms 사이를 오가고 있으며, 드래그 시 렌더링 시간입니다.
- 기존에는 17.9ms를 차지하던, Slime 컴포넌트에서 발생한 리렌더링이 보이지 않습니다.🙌
4-3. performance
앞서 컨텐츠 drag ➡️ 컨텐츠 drop ➡️ drop 후 전체 페이지
의 순서로 성능 저하가 발생했다고 했습니다. drop 시 스타일을 다시 계산하면서 발생하는 성능 저하를 제외하고 잡았습니다. 50ms 를 초과하지 않기 때문에 빨간색으로 경고하지 않습니다.
5. 개선 결과 및 소감
- performance tab 기준 최대 130ms 측정되던 main thread 작업 시간을 50ms 이하로 최소 60% 이상 단축했습니다.
- profiler 기준 20ms
50ms로 측정되던 렌더링 시간을 **2ms5ms** 범위로 축소시켰습니다. - drop 시 스타일 다시 계산에서 메인 스레드 작업이 오버되는데, 추후의 태스크로 잡아야 합니다.
성능 개선을 하면서 크롬에서 제공하는 다양한 도구들을 사용해보았습니다. Web Builder는 웹사이트를 만들어주는 프로그램이기 때문에 언제나 성능 개선에 신경써야 한다고 생각합니다. 성능 저하를 발견하고, 개선 방식을 탐구하고, 결과를 측정했던 경험을 공유하여 다른 팀원에게 도움이 되었으면 좋겠습니다. 🏊
'Develop' 카테고리의 다른 글
lazy loading으로 Entry Point File 크기 줄이는 방법 연구 (0) | 2024.07.20 |
---|---|
React context 대신 Event bus를 사용해 렌더링 개선하는 방법 조사 (0) | 2024.07.20 |
drag event 성능 개선을 위한 JS web worker 사전 조사 (0) | 2024.07.20 |
쿠키로 마음을 전하고 싶으면 도메인부터 맞추자 🍪 (0) | 2024.07.20 |
친구 프로그램 만들어주기 (with C) (0) | 2024.07.20 |