Files
2nd/10_Wiki/Topic_Programming/Frontend/대규모 데이터 렌더링 및 가상화 최적화.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

187 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: wiki-2026-0508-대규모-데이터-렌더링-및-가상화-최적화
title: 대규모 데이터 렌더링 및 가상화 최적화
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Virtualization, Windowing, Virtual List, 가상 스크롤]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, virtualization, performance, react, rendering]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react
---
# 대규모 데이터 렌더링 및 가상화 최적화
## 매 한 줄
> **"매 보이는 것만 그린다"**. 100k row를 한 번에 mount 하면 DOM이 죽는다. Windowing 으로 viewport 안 ~30개만 render → scroll 시 recycle. 2026 표준은 TanStack Virtual / react-virtuoso, content-visibility CSS, 그리고 React 19의 `<Activity>` (offscreen).
## 매 핵심
### 매 왜 가상화가 필수인가
- DOM node 1개 ≈ 1-5KB memory + layout/paint cost.
- 10k+ row mount → 수백 MB heap, scroll jank, INP > 500ms.
- React reconciliation도 O(n) — fiber tree가 거대해지면 commit이 느려진다.
### 매 windowing 전략
- **Fixed size**: row height 동일 → offset = index × height. 가장 빠름.
- **Variable size**: row 마다 다름 → measured cache + estimated. ResizeObserver 로 동적 측정.
- **Dynamic load**: scroll 끝 도달 시 fetch (infinite scroll) — virtualization 과 결합.
### 매 응용
1. Admin table 100k+ row.
2. Chat history (역방향 virtualization).
3. Image gallery / Pinterest grid.
4. Code editor (Monaco-style line virtualization).
## 💻 패턴
### TanStack Virtual (2026 표준)
```tsx
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
overscan: 5,
});
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(v => (
<div
key={v.key}
data-index={v.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${v.start}px)`,
}}
>
{items[v.index].name}
</div>
))}
</div>
</div>
);
}
```
### Variable height with measurement
```tsx
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // 매 best guess
measureElement: el => el.getBoundingClientRect().height, // 매 actual measure
overscan: 8,
});
```
### CSS content-visibility (zero-JS virtualization)
```css
.row {
content-visibility: auto;
contain-intrinsic-size: 0 48px; /* 매 placeholder size */
}
```
브라우저가 viewport 밖 element를 자동으로 skip layout/paint. React 없이도 효과.
### Infinite scroll + virtualization
```tsx
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['rows'],
queryFn: ({ pageParam = 0 }) => fetchPage(pageParam),
getNextPageParam: last => last.nextCursor,
});
const allRows = data?.pages.flatMap(p => p.items) ?? [];
const virtualizer = useVirtualizer({ count: allRows.length, /* ... */ });
useEffect(() => {
const last = virtualizer.getVirtualItems().at(-1);
if (last && last.index >= allRows.length - 5 && hasNextPage) {
fetchNextPage();
}
}, [virtualizer.getVirtualItems()]);
```
### Grid virtualization (2D)
```tsx
const rowVirtualizer = useVirtualizer({ count: rows, /* ... */ });
const colVirtualizer = useVirtualizer({
horizontal: true,
count: cols,
getScrollElement: () => parentRef.current,
estimateSize: () => 120,
});
// 매 nested loop: rows × cols 의 visible cells only.
```
### React 19 `<Activity>` for tab-like UI
```tsx
<Activity mode={isActive ? 'visible' : 'hidden'}>
<ExpensiveTab /> {/* 매 hidden 시 unmount X, but no render cost */}
</Activity>
```
### Server-side pagination + cursor
```ts
// 매 절대 client 에 100k row 보내지 마라.
GET /api/rows?cursor=abc123&limit=50
{ items: [...], nextCursor: 'def456' }
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| < 100 rows | 매 plain map. Virtualization X. |
| 100-1000 rows, fixed height | content-visibility CSS. |
| 1000+ rows | TanStack Virtual / react-virtuoso. |
| Variable height | measure + estimateSize. |
| Infinite stream | virtualization + cursor pagination. |
| Tab/accordion | React 19 `<Activity>`. |
**기본값**: TanStack Virtual + React Query infinite + content-visibility 보조.
## 🔗 Graph
- 부모: [[Frontend Performance]] · [[Rendering]]
- 변형: [[Infinite Scroll]] · [[Pagination]]
- 응용: [[Code Editor]]
- Adjacent: [[React Query]] · [[ResizeObserver]]
## 🤖 LLM 활용
**언제**: row count > 500, scroll 성능 issue, INP regression.
**언제 X**: static page, 매 작은 list (< 100), SEO-critical content (crawler 가 못 봄).
## ❌ 안티패턴
- **Mount all then hide**: `display: none` 으로 숨겨도 DOM 비용은 낸다.
- **Key as index**: row reorder 시 reconciliation 깨짐 — stable id 사용.
- **Inline measure on every render**: ResizeObserver + cache 필수.
- **No overscan**: scroll 시 빈 영역 보임. overscan 5-10.
- **Animation on virtualized rows**: 매 recycle 시 animation 초기화 — layout id 따로 관리.
## 🧪 검증 / 중복
- Verified (TanStack Virtual docs, Chrome DevTools, web.dev/content-visibility).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — virtualization + content-visibility + Activity 통합 |