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 - 대규모 데이터

5,000행 이상 시나리오에서 row identity, value recomputation, viewport buffer를 관리하는 패턴입니다.

reopt design · 업데이트 2026년 6월 26일

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

1. 라이브 예제

stable row identity, cached value access, pixel-buffer tuning을 함께 쓰는 대량 데이터 preset입니다.

Scale preset: getRowId + valueCache + rowBufferPx

Last commit: -

1
Member 1
Owner
Seoul
2
Member 2
Editor
Tokyo
3
Member 3
Viewer
Singapore
4
Member 4
Owner
Berlin
5
Member 5
Editor
Seoul
6
Member 6
Viewer
Tokyo
7
Member 7
Owner
Singapore
8
Member 8
Editor
Berlin
9
Member 9
Viewer
Seoul
10
Member 10
Owner
Tokyo
11
Member 11
Editor
Singapore
12
Member 12
Viewer
Berlin
13
Member 13
Owner
Seoul
14
Member 14
Editor
Tokyo
15
Member 15
Viewer
Singapore
16
Member 16
Owner
Berlin
17
Member 17
Editor
Seoul
18
Member 18
Viewer
Tokyo
19
Member 19
Owner
Singapore
20
Member 20
Editor
Berlin
21
Member 21
Viewer
Seoul
22
Member 22
Owner
Tokyo
23
Member 23
Editor
Singapore
24
Member 24
Viewer
Berlin
25
Member 25
Owner
Seoul
26
Member 26
Editor
Tokyo
27
Member 27
Viewer
Singapore

2. 권장 튜닝 순서

- `getRowId`로 row identity를 먼저 고정합니다.

- `valueCache`를 켜서 interaction-only update의 `getValue` 재계산을 줄입니다.

- sort/reorder가 잦다면 `valueCacheStrategy="row-id"`를 기본으로 둡니다.

- dynamic row height에서는 `rowBufferPx`로 render buffer를 잡습니다.

- `maxRenderedRows`로 예외적인 render window 확장을 제한합니다.

- `onVisibleRegionChanged`는 외부 로딩/동기화가 필요할 때만 연결합니다.

3. 각 설정이 제어하는 비용

getRowId

row reorder와 sort 이후에도 key churn, cache churn, subtree churn을 줄입니다.

valueCache

active move, selection, search refine 같은 interaction 경로에서 반복 `getValue` 비용을 줄입니다.

rowBufferPx

dynamic row height에서 viewport buffer를 pixel 단위로 안정적으로 제어합니다.

maxRenderedRows

예외적인 scroll/window 계산이 발생해도 렌더 범위가 과도하게 커지는 것을 막습니다.

rendererRefreshMode

mount 비용이 큰 셀에서 remount 대신 renderer reuse 정책을 선택할 수 있습니다.

4. 권장 시작 설정

큰 데이터셋에서는 먼저 stable row identity와 cached value access를 고정하는 편이 좋습니다. 기본 시작점은 `rowBufferPx` 중심의 pixel buffer 조합으로 맞춥니다.

tsx
import { DataGrid } from "@reopt-ai/opt-datagrid";

<DataGrid
  rows={rows}
  columns={columns}
  getRowId={(row) => String(row.id)}
  valueCache
  valueCacheStrategy="row-id"
  rowBufferPx={480}
  maxRenderedRows={600}
  height={440}
  ariaLabel="Scale grid"
/>;

5. 뷰포트 기반 지연 로딩

`useDataGridRemoteDataSource`는 viewport window fetch와 batch save를 하나의 headless 계약으로 묶어줍니다. 이때도 `getRowId`, `valueCache`, `rowBufferPx`를 같이 두면 remote sync 비용과 render cost를 분리해서 다루기 쉽습니다. 백엔드 payload shape과 이벤트 계약은 원격 연동 계약 문서에 정리돼 있습니다.

tsx
import * as React from "react";
import {
  DataGrid,
  type DataGridColumn,
  useDataGridRemoteDataSource,
} from "@reopt-ai/opt-datagrid";

interface Row {
  id: number;
  name: string;
}

const columns: DataGridColumn<Row>[] = [
  {
    id: "id",
    title: "ID",
    width: 100,
    align: "right",
    getValue: (row) => String(row.id),
  },
  { id: "name", title: "Name", width: 220, getValue: (row) => row.name },
];

function PaginatedScaleGrid() {
  const remote = useDataGridRemoteDataSource<Row>({
    rowCount: 100000,
    pageSize: 200,
    preloadPages: 1,
    getVisibleColumnIds: (region) =>
      columns.slice(region.startCol, region.endCol + 1).map((column) => column.id),
    openView: async ({ signal }) => {
      const response = await fetch("/api/grid/views/open", {
        method: "POST",
        signal,
      });
      const payload = await response.json();
      return {
        viewId: payload.viewId,
        rowCount: payload.totalRowCount,
        snapshotVersion: payload.snapshotVersion,
      };
    },
    subscribeToInvalidations: ({ viewId, onInvalidate }) => {
      const events = new EventSource(`/api/grid/views/${viewId}/events`);
      events.addEventListener("invalidate", (event) => {
        const payload = JSON.parse((event as MessageEvent<string>).data);
        onInvalidate({
          rows: payload.rows,
          rowCount: payload.totalRowCount,
          snapshotVersion: payload.snapshotVersion,
          movedRowIds: payload.movedRowIds,
          invalidateRanges: payload.invalidateRanges,
        });
      });
      return () => events.close();
    },
    makePlaceholderRow: (rowIndex) => ({
      id: rowIndex + 1,
      name: "Loading...",
    }),
    loadRows: async ({ start, end, viewId, visibleColumnIds, signal }) => {
      const params = new URLSearchParams({
        start: String(start),
        end: String(end),
      });
      if (visibleColumnIds?.length) {
        params.set("columns", visibleColumnIds.join(","));
      }
      const response = await fetch(
        `/api/grid/views/${viewId}/window?${params.toString()}`,
        { signal },
      );
      const payload = await response.json();
      return {
        rows: payload.rows,
        rowCount: payload.totalRowCount,
        snapshotVersion: payload.snapshotVersion,
      };
    },
    saveEdits: async ({ edits, viewId, snapshotVersion, signal }) => {
      const response = await fetch(`/api/grid/views/${viewId}/edits`, {
        method: "POST",
        signal,
        body: JSON.stringify({ edits, snapshotVersion }),
      });
      const payload = await response.json();
      return {
        rows: payload.rows,
        snapshotVersion: payload.snapshotVersion,
        movedRowIds: payload.movedRowIds,
        invalidateRanges: payload.invalidateRanges,
        rejectedEdits: payload.rejectedEdits,
      };
    },
    revalidateAfterSave: "affected-pages",
  });

  React.useEffect(() => {
    if (remote.rejectedEdits.length > 0) {
      console.warn(remote.rejectedEdits);
    }
  }, [remote.rejectedEdits]);

  return (
    <DataGrid
      rows={remote.rows}
      columns={columns}
      height={460}
      rowHeight={34}
      getRowId={(row) => String(row.id)}
      valueCache
      valueCacheStrategy="row-id"
      rowBufferPx={320}
      maxRenderedRows={500}
      onVisibleRegionChanged={remote.onVisibleRegionChanged}
      onCellsEdited={remote.onCellsEdited}
    />
  );
}

6. 측정 기반 검증

성능은 추측보다 baseline과 diff로 보는 편이 정확합니다. 아래 명령으로 현재 상태를 저장하고, 이후 변경과 compare/history로 회귀를 추적할 수 있습니다.

bash
bun run --filter @reopt-ai/opt-datagrid benchmark
bun run --filter @reopt-ai/opt-datagrid benchmark:save-baseline
bun run --filter @reopt-ai/opt-datagrid benchmark:compare
bun run --filter @reopt-ai/opt-datagrid benchmark:history

- `duration`: 시나리오 전체 wall-clock 비용

- `getValue`: 값 계산 hot path 압력

- `renderCell`: rerender fan-out을 간접적으로 보는 지표

7. 전체 코드 예제

tsx
import * as React from "react";
import { DataGrid, type DataGridColumn } from "@reopt-ai/opt-datagrid";

interface EventRow {
  id: number;
  name: string;
  role: string;
  region: string;
}

const columns: DataGridColumn<EventRow>[] = [
  { id: "id", title: "ID", width: 100, align: "right", editable: false, getValue: (row) => String(row.id) },
  { id: "name", title: "Name", width: 220, editable: true, getValue: (row) => row.name, setValue: (row, value) => ({ ...row, name: value }) },
  { id: "role", title: "Role", width: 140, editable: true, getValue: (row) => row.role, setValue: (row, value) => ({ ...row, role: value }) },
  { id: "region", title: "Region", width: 140, editable: true, getValue: (row) => row.region, setValue: (row, value) => ({ ...row, region: value }) },
];

export function ScaleGridExample() {
  const rows = React.useMemo(
    () =>
      Array.from({ length: 5000 }, (_, index) => ({
        id: index + 1,
        name: `Member-${index + 1}`,
        role: index % 3 === 0 ? "Owner" : index % 3 === 1 ? "Editor" : "Viewer",
        region: ["Seoul", "Tokyo", "Singapore", "Berlin"][index % 4]!,
      })),
    [],
  );

  return (
    <DataGrid
      rows={rows}
      columns={columns}
      getRowId={(row) => String(row.id)}
      valueCache
      valueCacheStrategy="row-id"
      rowBufferPx={480}
      maxRenderedRows={600}
      height={440}
      ariaLabel="Scale grid"
    />
  );
}
Previous운영 모니터링실시간 운영 화면에 맞는 고정 헤더, 이벤트 로그, 선택 패턴DataGrid
운영 모니터링 페이지로 이동
Next원격 연동 계약viewport fetch, save batch, push invalidation 프로토콜 설계DataGrid