[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-10 22:08:15 +09:00
parent 21ac3ed255
commit 504fd5fb42
3011 changed files with 380280 additions and 206977 deletions
@@ -2,108 +2,185 @@
id: wiki-2026-0508-대규모-데이터-렌더링-및-가상화-최적화
title: 대규모 데이터 렌더링 및 가상화 최적화
category: 10_Wiki/Topics
status: needs_review
status: verified
canonical_id: self
aliases: [P-Reinforce-AUTO-BB908E]
aliases: [Virtualization, Windowing, Virtual List, 가상 스크롤]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
tags: [auto-reinforced]
verification_status: applied
tags: [frontend, virtualization, performance, react, rendering]
raw_sources: []
last_reinforced: 2026-04-20
github_commit: "[P-Reinforce] Continuous Worker - 대규모 데이터 렌더링 및 가상화 최적화"
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: unspecified
framework: unspecified
language: typescript
framework: react
---
# [[대규모 데이터 렌더링 및 가상화 최적화|대규모 데이터 렌더링 및 가상화 최적화]]
# 대규모 데이터 렌더링 및 가상화 최적화
## 📌 한 줄 통찰 (The Karpathy Summary)
> 대규모 리스트나 테이블을 렌더링할 때 발생하는 DOM 노드 폭증과 메모리 부족, 프레임 저하 현상을 방지하기 위해, 전체 데이터 중 현재 화면(Viewport)에 보이는 항목과 약간의 여분(Buffer)만을 동적으로 마운트하여 렌더링하는 성능 최적화 기법입니다.
## 한 줄
> **"매 보이는 것만 그린다"**. 100k row를 한 번에 mount 하면 DOM이 죽는다. Windowing 으로 viewport 안 ~30개만 render → scroll 시 recycle. 2026 표준은 TanStack Virtual / react-virtuoso, content-visibility CSS, 그리고 React 19의 `<Activity>` (offscreen).
## 📖 구조화된 지식 (Synthesized Content)
**1. 대규모 렌더링의 성능 병목 원인** 수천, 수만 개의 항목(예: 10,000개 이상의 리스트)을 한 번에 렌더링하면 DOM 노드가 기하급수적으로 늘어나 브라우저가 멈추거나(Freezing), 레이아웃 계산 및 페인팅 작업이 매우 느려집니다. 또한 React의 재조정([[Reconciliation|Reconciliation]]) 알고리즘이 모든 항목을 비교하려 하므로 CPU 오버헤드와 심각한 프레임 드랍이 발생하게 됩니다.
## 매 핵심
**2. 가상화(Windowing/Virtualization)의 작동 원리 및 효과** 가상화는 전체 배열 중 현재 화면에 노출되는 항목만을 DOM에 마운트(렌더링)하고, 스크롤할 때마다 새롭게 보이는 항목을 생성하며 화면을 벗어난 항목은 언마운트하는 방식입니다. 이를 적용하면 10,000개의 항목이 있어도 실제 DOM에는 수십 개만 존재하게 되어 렌더링 소요 시간을 약 1,200ms에서 50ms 수준으로 단축하고 메모리 사용량을 최대 97%까지 절감할 수 있습니다.
### 매 왜 가상화가 필수인가
- 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이 느려진다.
**3. 안정적인 식별자(Key)의 중요성** 대규모 리스트를 최적화할 때는 항목의 추가, 삭제, 재정렬 시 불필요한 재생성을 막는 것이 핵심입니다. 배열의 인덱스(index)를 `key`로 사용하면 항목 순서가 바뀔 때 React가 항목을 잘못 식별하여 대규모 리렌더링이나 미세한 버그가 발생할 수 있으므로, 반드시 고유하고 안정적인 `key`를 부여해야 합니다.
### 매 windowing 전략
- **Fixed size**: row height 동일 → offset = index × height. 가장 빠름.
- **Variable size**: row 마다 다름 → measured cache + estimated. ResizeObserver 로 동적 측정.
- **Dynamic load**: scroll 끝 도달 시 fetch (infinite scroll) — virtualization 과 결합.
**4. 주요 가상화 라이브러리 도입** 이러한 복잡한 로직을 직접 구현하기보다는 검증된 라이브러리를 사용하는 것이 권장됩니다.
### 매 응용
1. Admin table 100k+ row.
2. Chat history (역방향 virtualization).
3. Image gallery / Pinterest grid.
4. Code editor (Monaco-style line virtualization).
- **`react-window`**: 가볍고 성능이 뛰어난 가장 보편적인 가상화 라이브러리입니다.
- **`react-virtualized`**: 테이블이나 그리드 등 보다 고급 기능이 포함되어 있으나 상대적으로 무겁습니다.
- **`TanStack Virtual`**: 프레임워크에 구애받지 않는 최신 API 구조를 제공합니다.
## 💻 패턴
**5. 적용 시 주의사항 및 한계점** 가상화는 만능이 아니며 피해야 할 상황이 존재합니다.
### TanStack Virtual (2026 표준)
```tsx
import { useVirtualizer } from '@tanstack/react-virtual';
- 리스트 항목이 20개 미만인 소규모 리스트에서는 가상화 연산 오버헤드가 더 클 수 있으므로 도입하지 않는 것이 좋습니다.
- 브라우저의 기본 검색(Ctrl+F) 기능으로 모든 콘텐츠를 찾아야 하거나, SEO가 중요한 정적 콘텐츠, 인쇄용 페이지에는 가상화된 항목이 DOM에 존재하지 않아 작동하지 않으므로 적용을 피해야 합니다.
function VirtualList({ items }: { items: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
overscan: 5,
});
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
- **과거 데이터와의 충돌:** 자동화 엔진에 의해 매핑된 지식으로, 추후 정밀 검증 필요.
- **정책 변화:** Programming & Language 분야의 자동 자산화 수행.
## 🔗 지식 연결 (Graph)
- **Related Topics:** [[React Performance Optimization|React Performance Optimization]], 재조정 (Reconciliation), [[불필요한 리렌더링 방지|불필요한 리렌더링 방지]]
- **Projects/Contexts:** [[장기 실행되는 실시간 데이터 대시보드 최적화|장기 실행되는 실시간 데이터 대시보드 최적화]], 대규모 로그 뷰어 및 데이터 테이블 구현, 이커머스 무한 스크롤 상품 리스트
- **Contradictions/Notes:** 50~100개 이상의 대규모 리스트나 무한 스크롤 환경에서는 가상화가 필수적이지만, 항목의 높이가 제각각인 가변 높이 항목(Variable height items)을 렌더링할 때는 렌더링 전 각 항목의 크기를 측정(Measurement)하는 단계가 필요하여 구현이 훨씬 복잡해집니다. 따라서 가급적 리스트 항목의 높이를 일정하게 설계하는 것이 성능과 구현 측면에서 훨씬 유리합니다.
---
_Last updated: 2026-04-15_
---
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
**언제 이 지식을 쓰는가:**
- *(TODO)*
**언제 쓰면 안 되는가:**
- *(TODO)*
## 🧪 검증 상태 (Validation)
- **정보 상태:** needs_review
- **출처 신뢰도:** A
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
## 🧬 중복 검사 (Duplicate Check)
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
- **처리 방식:** UPDATE (자동 정규화)
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
## 🕓 변경 이력 (Changelog)
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|------|-----------|-----------|--------|
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
## 💻 코드 패턴 (Code Patterns)
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
```text
# TODO
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>
);
}
```
## 🤔 의사결정 기준 (Decision Criteria)
### 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,
});
```
**선택 A를 써야 할 때:**
- *(TODO)*
### CSS content-visibility (zero-JS virtualization)
```css
.row {
content-visibility: auto;
contain-intrinsic-size: 0 48px; /* 매 placeholder size */
}
```
브라우저가 viewport 밖 element를 자동으로 skip layout/paint. React 없이도 효과.
**선택 B를 써야 할 때:**
- *(TODO)*
### Infinite scroll + virtualization
```tsx
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['rows'],
queryFn: ({ pageParam = 0 }) => fetchPage(pageParam),
getNextPageParam: last => last.nextCursor,
});
**기본값:**
> *(TODO)*
const allRows = data?.pages.flatMap(p => p.items) ?? [];
const virtualizer = useVirtualizer({ count: allRows.length, /* ... */ });
## ❌ 안티패턴 (Anti-Patterns)
useEffect(() => {
const last = virtualizer.getVirtualItems().at(-1);
if (last && last.index >= allRows.length - 5 && hasNextPage) {
fetchNextPage();
}
}, [virtualizer.getVirtualItems()]);
```
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
### 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]]
- 응용: [[Admin Dashboard]] · [[Chat UI]] · [[Code Editor]]
- Adjacent: [[React Query]] · [[ResizeObserver]] · [[content-visibility]]
## 🤖 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 통합 |