[성능 최적화] 무한 스크롤 - 가상 리스트

kimhayeon·2024년 8월 31일
0

성능 최적화

목록 보기
1/1

배경

MOMOO 프로젝트에서 무한히 많은 게시물을 보여주기 위해 무한 스크롤을 사용했습니다. 콘텐츠가 몇 천 개, 몇 만 개 이상으로 무한히 추가될 수 있는 무한 스크롤 UI 특성상 스크롤을 할수록 성능 저하가 일어날 수 있습니다. 따라서 무한 스크롤 성능 최적화를 위해 가상 리스트 기술을 적용했습니다.

성능 비교

Chrome DevTools Perfomence를 사용하여 성능을 측정했습니다.
성능 테스트는 CPU나 네트워크 자원 등의 제한하지 않은 맥북 에어 환경에서 진행했습니다.
가상 리스트 적용 후 약 3.6배의 성능 향상을 확인할 수 있었습니다.

CPU 차트

0부터 약 500번째까지 콘텐츠를 추가하며 성능을 측정했습니다.

  • 가상 리스트 적용 후, 병목 현상이 사라졌습니다. (빨간색 선)
  • 가상 리스트 적용 전에는 콘텐츠가 추가될수록 CPU 사용률이 지속적으로 증가했지만, 가상 리스트 적용 후에는 CPU 사용률이 일정하게 유지되고 있습니다.

가상 리스트 적용 전

가상 리스트 적용 후

Network 타임라인 & Main & GPU

1169 ~ 1245번째 콘텐츠까지 5페이지를 추가하며 성능을 측정했습니다.

getFeeds ~ GPU 작업 완료, fetch 완료 ~ GPU 작업 완료 / getFeeds ~ fetch 완료

가상 리스트 적용 전

  1. 610.4ms, 877.5ms / 267.1ms
  2. 626.5ms, 885.6ms / 259.1ms
  3. 634.7ms, 839.9ms / 205.2ms
  4. 644.2ms, 907.5ms / 263.3ms
  5. 724ms, 946.4ms / 222.4ms

콘텐츠가 추가될 때마다 렌더링에 소요되는 시간이 증가하고 있음을 확인할 수 있습니다.

가상 리스트 적용 후

  1. 8ms, 350ms / 342ms
  2. 16.8ms, 234ms / 217.2ms
  3. 6.6ms, 251.8ms / 245.2ms
  4. 15.5ms, 200.1ms / 184.6ms
  5. 4.7ms 216.4ms / 211.7ms

데이터를 불러오는 속도에는 유의미한 차이는 없으며,
렌더링 속도에서 큰 개선이 있었습니다.
가상 리스트 적용 후, 콘텐츠 추가 성능이 약 3.6배 향상되었습니다. (데이터 요청 시부터 렌더링 완료 시까지 / 1169 ~ 1245 콘텐츠 기준)

구현 과정

1. 가상 리스트 구현

Virtuoso 라이브러리를 사용하여 기존 무한 스크롤 코드에 가상 리스트를 적용했습니다.

<Virtuoso data={feedsData} itemContent={itemContent} />

리스트 요소에 스크롤이 생겼습니다.
이는 윈도우 스크롤로 게시물을 탐색하려는 의도와 다릅니다.
따라서 useWindowScroll 속성을 추가했습니다.

<Virtuoso useWindowScroll data={feedsData} itemContent={itemContent} />

2. 접근성 향상

<Virtuoso
  useWindowScroll
  data={feedsData}
  itemContent={itemContent}
  components={{
    List,
    Item
  }}
/>

접근성을 위해 List와 Item 태그를 각가 Ul, Li로 변경하려 했으나, 타입 에러가 발생했습니다.

const List = forwardRef<HTMLUListElement, HTMLProps<HTMLUListElement>>((props, ref) => (
  <ul ref={ref} {...props} />
));
const Item = forwardRef<HTMLLIElement, HTMLProps<HTMLLIElement>>((props, ref) => (
  <li ref={ref} {...props} />
));
'ForwardRefExoticComponent<Omit<HTMLProps<HTMLUListElement>, "ref"> & RefAttributes<HTMLUListElement>>' 형식은 'ComponentType<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style" | "children"> & { ...; } & RefAttributes<...> & { ...; }> | undefined' 형식에 할당할 수 없습니다.

차선책으로 ARIA role을 사용했습니다.

const List = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>((props, ref) => (
  <div role="list" ref={ref} {...props} />
));
const Item = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>((props, ref) => (
  <div role="listitem" ref={ref} {...props} />
));

앞으로의 개선 사항

  • 더 나은 접근성을 위해
    • ul, li를 사용할 수 있는 라이브러리로 변경하거나
    • 가상 리스트를 직접 구현하는 방향을 검토할 예정입니다.
  • 성능 측정 및 계산 자동화
    • 리소스를 절감
    • 더 많은 테스트를 통해 정확도 높은 데이터 추출
  • 실 서비스에 성능 모니터링 도입 고려

라이브러리

react-window

ul, li 태그를 사용하기 위해 조사한 라이브러리입니다.
VariableSizeList를 사용하는 경우에도 itemSize를 개발자가 직접 계산해야 합니다.
MOMOO 프로젝트 게시물의 경우, 게시물을 렌더링한 후 게시물의 높이를 알 수 있습니다.
이는 게시물이 많아질수록 저장해야 하는 값이 무한히 늘어날 수 있기에, 좋은 로직은 아니라고 생각합니다.

0개의 댓글