blog
대용량 리스트 최적화: 가상화와 페이지네이션, 언제 무엇을 쓸까
Front-end / React#가상화#페이지네이션#성능#TanStack Virtual
둘 다 "데이터가 많을 때"의 최적화지만, 줄이는 대상이 다르다.
- 가상화(Virtualization): 데이터는 많이 갖고 있되, 화면에 보이는 항목만 DOM으로 그린다. 렌더링 비용을 줄인다.
- 페이지네이션(Pagination): 데이터 자체를 나눠서 요청한다. 네트워크·메모리·서버 부하를 줄인다.
그래서 먼저 "문제가 API 응답량인가, DOM 렌더링인가"를 구분해야 한다.
가상화
장점
- 부드러운 스크롤: 수천~수만 개 항목이라도 실제 DOM은 수십 개만 유지하므로 스크롤 성능이 안정적이다.
- 끊김 없는 탐색: 페이지 이동 없이 하나의 긴 리스트로 탐색할 수 있어 피드·타임라인·로그 뷰에 어울린다.
- 리스트 안의 인터랙션: 드래그 앤 드롭, 인라인 편집, 섹션 접기처럼 리스트 안에서 많이 조작하는 UI와 잘 맞는다.
단점
- 구현 복잡도: 스크롤 위치, 항목 높이(특히 동적 높이), overscan, 계층 데이터 평탄화 등을 관리해야 한다. 라이브러리(TanStack Virtual, react-window, react-virtuoso)를 써도 동적 높이·무한 스크롤·그룹핑이 섞이면 디버깅이 어렵다.
- 데이터는 여전히 메모리에: 그리는 양만 줄일 뿐, 클라이언트가 들고 있는 데이터 양은 그대로다.
- 브라우저 기본 기능 손실: 화면 밖 항목은 DOM에 없으므로 브라우저 찾기(Ctrl+F)에 걸리지 않고, 스크린 리더가 전체 개수를 알기 어렵다(
aria-setsize,aria-posinset보완 필요). - 위치 공유·복원: "N번째 항목"을 URL로 공유하거나 뒤로 가기 시 정확한 스크롤 위치로 돌아오게 하려면 인덱스와 스크롤 위치를 직접 관리해야 한다.
페이지네이션
장점
- 데이터량 자체 감소: 한 번에 50~100개만 받으므로 응답이 가볍고 메모리 사용이 적다.
- 단순한 구현:
page,pageSize,total정도만 관리하면 된다. - 예측 가능한 UX와 공유 가능한 URL: "3 / 10페이지"처럼 위치가 명확하고,
?page=3으로 공유·북마크·검색 엔진 노출이 쉽다. - 서버 부하 제한: 요청마다 일정한 양만 조회한다. 데이터가 아주 크면 OFFSET 방식은 뒤쪽 페이지로 갈수록 느려지므로 커서 기반 페이지네이션을 고려한다.
단점
- 연속 탐색이 끊긴다: 피드형 UX에는 맞지 않는다.
- 페이지 이동마다 로딩: prefetch와 캐시로 줄일 수는 있지만 완전히 없애기 어렵다.
- 페이지를 넘나드는 인터랙션: "1페이지 항목을 3페이지로 드래그" 같은 동작은 페이지 간 상태 동기화가 필요해 복잡해진다.
무엇을 고를까
가상화가 유리한 경우
- 피드, 로그, 채팅, 백로그 보드처럼 한 화면에서 많이 스크롤하는 UI
- 데이터를 한 번에 받아도 되는 크기(수천~수만 건)이고, 문제의 핵심이 DOM 렌더링인 경우
- 저사양 기기에서도 스크롤이 부드러워야 하는 경우
페이지네이션이 유리한 경우
- 수십만 건 이상으로 계속 늘어나는 데이터(어드민 그리드 등)
- 사용자가 주로 앞쪽 일부나 검색 결과만 보는 경우
- URL 공유, SEO, 필터·정렬이 중요한 검색 결과·대시보드
- 서버 쿼리와 네트워크 비용이 병목인 경우
함께 쓰기
실무에서는 둘을 함께 쓰는 경우가 많다.
- 서버·네트워크·메모리는 페이지네이션(또는 무한 스크롤 단위 로딩)으로 1차 통제
- 한 번에 받은 묶음이 수백~수천 개라면 그 안의 DOM은 가상화로 2차 통제
무한 스크롤(TanStack Query의 useInfiniteQuery) + 가상화 조합이 대표적이다. 계층형 데이터라면 섹션 단위로 페이지네이션하고 각 섹션 내부 리스트에만 가상화를 적용하는 식으로 복잡도를 통제할 수 있다.
정리
| 항목 | 가상화 | 페이지네이션 |
|---|---|---|
| 줄이는 것 | DOM 렌더링 | 데이터·네트워크·서버 부하 |
| 탐색 경험 | 끊김 없는 스크롤 | 구간 단위 이동 |
| 구현 난이도 | 높음 | 낮음 |
| URL 공유·SEO | 어려움 | 쉬움 |
| 메모리 | 데이터만큼 사용 | 페이지 크기만큼 사용 |