reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
시작하기
Playground
핵심 개념
핵심 개념
DataGrid
DataGrid 개요
편집형 데이터 입력
운영 모니터링
대규모 데이터
성능 비교
원격 연동 계약
마이그레이션 플레이북
앱 조합 가이드
컬럼 설계 플레이북
구축·운영
Skills
릴리즈 노트
레퍼런스
Hook 레퍼런스
타입 레퍼런스
Oopt-datagrid
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

DataGrid
  1. 문서
  2. /
  3. DataGrid
  4. /
  5. 성능 비교

DataGrid 성능 비교

100만 행 브라우저 workload에서 opt-datagrid와 TanStack Table v9 + TanStack Virtual을 직접 측정했습니다. 결과뿐 아니라 fixture, 측정 코드, 재현 명령과 한계까지 공개합니다.

reopt design · 업데이트 2026년 7월 14일

시작하기핵심 개념개요편집형 데이터 입력운영 모니터링대규모 데이터성능 비교원격 연동 계약마이그레이션 플레이북앱 조합 가이드컬럼 설계 플레이북Skills릴리즈 노트Hook 레퍼런스타입 레퍼런스

Reference snapshot · 2026-07-14

1,000,000 rows × 8 columns

동일한 데이터, viewport, overscan에서 retained heap과 보이는 행 업데이트를 측정했습니다.

JSON snapshot 보기

74.3% less

초기 retained heap

98.43 MB vs 383.53 MB

33.9× faster

visible-row update → paint

10.60 ms vs 359.10 ms

84.4% less

업데이트 후 retained heap

103.11 MB vs 660.72 MB

1. 직접 비교 결과

아래 값은 3회 측정의 중앙값입니다. retained heap은 강제 GC 뒤에도 살아 있는 JavaScript heap이며, update → paint는 마지막 보이는 행의 객체를 교체한 시점부터 변경된 셀 텍스트가 두 animation frame을 거쳐 화면에 반영될 때까지의 시간입니다.

opt-datagrid와 TanStack Table v9의 100만 행 성능 비교
측정 구간opt-datagridTanStack v9차이
초기 retained JS heap98.43 MB383.53 MB74.3% less
중간 스크롤 retained JS heap99.18 MB384.68 MB74.2% less
끝 스크롤 retained JS heap99.23 MB384.77 MB74.2% less
보이는 마지막 행 update → paint10.60 ms359.10 ms33.9× faster
업데이트 후 retained JS heap103.11 MB660.72 MB84.4% less

두 fixture 모두 끝 스크롤에서 row index 999,999를 렌더했습니다.

opt-datagrid window는 999,971–999,999, TanStack fixture는 999,970–999,999였습니다. 브라우저 layout height 한계로 중간 행에서 멈춘 결과를 성공으로 계산하지 않았습니다.

2. 왜 빠른가

차이는 단순한 DOM row 수보다 데이터 전체를 어떤 형태로 메모리에 materialize하고, rows 변경을 어디까지 전파하는지에서 크게 발생합니다. 다음 설명은 이 benchmark workload와 현재 구현을 기준으로 합니다.

전체 row model을 만들지 않는 fixed-height window

숫자형 rowHeight 경로는 rowIndex × rowHeight로 offset을 계산합니다. 100만 개 행마다 별도 model과 offset 객체를 만들 필요가 없습니다.

브라우저 높이 한계를 넘는 bounded scroll track

논리 높이가 커져도 native scroll track은 16,000,000px 이하로 유지하고 logical scroll 위치로 환산합니다. 마지막 행 999,999 렌더 검증이 이 경로를 함께 검사합니다.

현재 보이는 window만 업데이트

rows 배열이 교체되면 현재 startRow–endRow와 렌더된 column만 비교해 version을 올립니다. 화면 밖 99만여 행을 update fan-out에 포함하지 않습니다.

접근한 셀만 유지하는 lazy value cache

value cache는 실제 렌더 과정에서 읽은 셀만 생성합니다. stable row ID가 있으면 기존 entry의 저장된 index를 확인해 재사용합니다.

Row window 계산fixed-height O(1) offset 계산과 16,000,000px bounded scroll trackpackages/opt-datagrid/src/core/layout/row-window.ts
Visible-window invalidationrows 교체 시 현재 렌더된 행·열만 비교해 render version을 갱신packages/opt-datagrid/src/data-grid.tsx
Value cache접근한 셀만 lazy cache하고 stable row ID로 기존 entry를 재사용packages/opt-datagrid/src/core/value-cache.ts

3. 공정성을 위한 실험 설계

고정한 조건

  • 동일한 deterministic 1,000,000행 × 8열 데이터와 표시 문자열
  • 동일한 React 19.2.5, 1,440 × 900 viewport, overscan 5
  • 각 대상을 별도 production Vite bundle과 별도 preview server로 실행
  • manifest와 source map을 검사해 상대 grid 모듈 혼입 시 즉시 실패
  • 각 system을 별도 Chromium process에서 실행하고 heavy sample마다 재시작

sample마다 수행하는 단계

  1. fixture ready 후 두 animation frame을 기다립니다.
  2. CDP HeapProfiler.collectGarbage를 실행한 뒤 retained JS heap과 DOM counter를 읽습니다.
  3. initial, 50% scroll, 100% scroll 상태를 각각 측정합니다.
  4. 끝 스크롤에서 실제 row index 999,999가 DOM에 있는지 검증합니다.
  5. 현재 보이는 마지막 행 하나를 교체하고 새 텍스트가 paint될 때까지 측정합니다.
  6. 세 번의 sample 중앙값을 JSON, CSV, Markdown으로 출력합니다.

양쪽 fixture는 동일한 row 배열 복사와 row 객체 교체를 수행합니다. 따라서 100만 원소 outer-array 복사 비용은 두 결과에 모두 포함됩니다. opt fixture는 기본 34px row height, TanStack Virtual fixture는 33px estimate와 공통 CSS의 33px 최소 cell height를 사용합니다.

opt fixture는 getRowId, valueCache, valueCacheStrategy="row-id"와 165px row buffer를 사용합니다. 비교 fixture는 TanStack Table v9의 sizing, visibility, sorting feature와 TanStack Virtual의 overscan 5를 사용합니다. 두 설정은 동일 API를 흉내 내기보다 각 도구의 권장 virtualized-grid 구성을 명시적으로 고정한 것입니다.

4. 실제 측정 코드

표시용 pseudo benchmark가 아니라 이 저장소에서 결과를 만든 runner의 핵심 코드입니다. GC 뒤 CDP metric을 읽고, 마지막 실제 data row가 렌더되지 않으면 sample을 실패시킵니다.

benchmarks/datagrid-memory-runner/src/run.ts
// run.ts의 measure() 핵심 구간
async function measure(client, page, benchmarkCase) {
  await page.evaluate(() => new Promise(requestAnimationFrame));
  await page.evaluate(() => new Promise(requestAnimationFrame));

  const beforeGc = await client.send("Performance.getMetrics");
  await client.send("HeapProfiler.collectGarbage");
  await page.waitForTimeout(100);

  const [{ metrics }, domCounters, rendered] = await Promise.all([
    client.send("Performance.getMetrics"),
    client.send("Memory.getDOMCounters"),
    page.evaluate(() => {
      const renderedRowIndexes = Array.from(
        document.querySelectorAll<HTMLElement>("[data-row-index]"),
        (element) => Number(element.dataset.rowIndex),
      ).filter(Number.isFinite);

      return {
        maxRenderedRowIndex:
          renderedRowIndexes.length > 0
            ? Math.max(...renderedRowIndexes)
            : -1,
        minRenderedRowIndex:
          renderedRowIndexes.length > 0
            ? Math.min(...renderedRowIndexes)
            : -1,
      };
    }),
  ]);

  return { beforeGc, metrics, domCounters, rendered };
}

if (endMeasurement.maxRenderedRowIndex !== benchmarkCase.rows - 1) {
  throw new Error(
    `end-scroll stopped at row ${endMeasurement.maxRenderedRowIndex}`,
  );
}
benchmarks/datagrid-memory-runner/src/isolation.ts
// isolation.ts의 manifest / production bundle 검사
if ([...optDependencies].some((name) => name.startsWith("@tanstack/"))) {
  throw new Error("opt-datagrid fixture must not depend on TanStack modules");
}

if (tanstackDependencies.has("@reopt-ai/opt-datagrid")) {
  throw new Error(
    "TanStack fixture must not depend on @reopt-ai/opt-datagrid",
  );
}

const sources = sourceMapSources(fixtureDirectory(workspaceRoot, system));
const normalized = sources.join("\n").toLowerCase();

if (
  system === "opt-datagrid" &&
  (normalized.includes("@tanstack") || normalized.includes("tanstack/"))
) {
  throw new Error("opt-datagrid bundle contains TanStack source modules");
}

아래 경로는 이 페이지에 첨부한 코드의 repository 원본 위치입니다. 문서 무결성 테스트가 각 파일의 존재와 snapshot package version을 함께 검증합니다.

측정 runnerPlaywright/CDP 측정, 마지막 행 검증, visible-row update 시나리오benchmarks/datagrid-memory-runner/src/run.ts
격리 검증manifest와 production source map에서 비교 대상 번들 혼입 차단benchmarks/datagrid-memory-runner/src/isolation.ts
opt-datagrid fixtureopt-datagrid만 import하는 독립 production Vite fixturebenchmarks/datagrid-memory-opt/src/main.tsx
TanStack fixtureTanStack Table v9 + TanStack Virtual만 import하는 독립 fixturebenchmarks/datagrid-memory-tanstack/src/main.tsx
공통 데이터 계약양쪽이 공유하는 deterministic data와 브라우저 측정 계약benchmarks/datagrid-memory-shared/src/index.ts

5. 실행 환경과 범위

CPU / memoryApple M2 Max · 12 logical CPUs · 32 GB
Operating systemmacOS · Darwin 25.5.0
Bun / Node.jsBun 1.3.14 · Node.js 24.3.0
Browser driverPlaywright 1.58.2 · bundled Chromium
opt-datagrid1.4.2
ComparisonTanStack Table 9.0.0-beta.31 + TanStack Virtual 3.14.6

이 결과는 특정 workload의 reference snapshot입니다.

두 라이브러리의 feature set과 DOM 구조는 같지 않으며 TanStack Table 대상은 v9 beta입니다. 머신, 브라우저, dependency, fixture가 달라지면 절대값도 달라집니다. 보편적인 순위나 모든 앱의 성능 보증으로 해석하지 마세요.

6. 직접 재현하기

repository root에서 아래 명령을 실행합니다. 각 target은 독립적으로 build되고 결과는 runner의 results/ 아래 JSON, CSV, Markdown으로 생성됩니다.

Reproduce the benchmark
# 빠른 계약 검증
bun run --filter @reopt/benchmark-datagrid-memory-runner benchmark:smoke

# 이 페이지의 1,000,000 × 8 reference case 재현
bun run --filter @reopt/benchmark-datagrid-memory-runner benchmark --   --case rows-1000000x8 --caseTimeoutMs 60000

# 전체 row/column matrix, 각 case 3회
bun run --filter @reopt/benchmark-datagrid-memory-runner benchmark
공개 JSON snapshot대규모 데이터 튜닝 가이드
Previous대규모 데이터가상화, 캐시, 렌더링 제한을 중심으로 한 대량 데이터 전략DataGrid
대규모 데이터 페이지로 이동
Next원격 연동 계약viewport fetch, save batch, push invalidation 프로토콜 설계DataGrid