블로그

2026년 9월 2일

55만 개 행에서 만난 브라우저 스크롤 한계. 100만 개를 달성한 방법.

AXISJ

며칠 전, 우리는 Apache-2.0 라이선스로 오픈소스 React + TypeScript 데이터 그리드인 BGrid(BeautifulGrid) 를 릴리스했습니다.

우리가 집착했던 것 중 하나는 바로 대용량 데이터셋이었습니다.

우리의 첫 번째 공개 데모에서 처리한 데이터는 다음과 같았습니다:

550,000 행

솔직히 말해서, 55만 개의 행은 이미 충분해 보였습니다.

하지만 개발자라면 무언가 제대로 작동할 때 어떤 일이 일어나는지 잘 알고 있습니다.

우리는 즉시 이렇게 묻습니다.

"좋아... 근데 어디까지 밀어붙일 수 있을까?"

그래서 우리는 더 나아가보기로 했습니다.

60만 개.

70만 개.

100만 개.

그러자 이상한 일이 발생했습니다.

단순히 그리드가 느려진 것이 아니었습니다.

레이아웃이 깨지기 시작했습니다.

Image description

React가 다운된 것도 아니었습니다.

브라우저의 메모리가 부족한 것도 아니었습니다.

우리가 70만 개의 DOM 노드를 렌더링하고 있던 것도 아니었습니다.

도대체 무슨 일이 일어나고 있었던 걸까요?

알고 보니, 우리는 완전히 다른 종류의 한계에 부딪혔던 것입니다.


문제는 React가 아니었습니다

BGrid는 가상 스크롤(Virtual Scrolling)을 사용합니다.

즉, 55만 개의 행이 있다고 해서 55만 개의

요소를 생성하지 않는다는 것을 의미합니다.

현재 뷰포트(Viewport)에 보이는 행들만 실제로 렌더링됩니다.

데이터셋
─────────────────────
행 1
행 2
행 3
...
행 549,998
행 549,999
행 550,000
─────────────────────

실제 DOM

행 549,989
행 549,990
행 549,991
...
행 550,000

따라서 렌더링되는 DOM 노드의 수는 적게 유지됩니다.

훌륭합니다.

문제가 해결된 거겠죠?

음...

그렇지 않았습니다.


보이지 않는 거대한

비록 모든 행을 렌더링하지는 않았지만, 스크롤바는 여전히 전체 데이터셋을 나타내야 했습니다.

우리 행의 높이는 대략 29px이었습니다.

그래서 55만 행의 경우:

550,000 × 29px = 15,950,000px

네.

거의 1,600만 픽셀입니다.

기본적으로 우리는 브라우저 안에 보이지 않는 마천루를 만들었던 것입니다.

그리고 브라우저는 정중하게 우리에게 이렇게 말했습니다:

"안 돼."

😅

우리가 목표로 한 브라우저들에서 대략 이 정도 범위에 도달하자, 거대한 요소 크기에 대한 실질적인 CSS/레이아웃 한계에 부딪히기 시작했습니다.

물리적인 스크롤 영역이 더 이상 안정적으로 커질 수 없었습니다.

그때 우리는 중요한 사실을 깨달았습니다.

가상화된 행들이 문제가 아니었습니다.
문제는 가상화된 스크롤바였습니다.

왜 하필 55만 행이었을까요?

이것은 왜 우리의 원래 데모가 하필 55만 개라는 애매한 숫자에서 멈췄는지도 설명해 줍니다.

550,001번째 행에서 BGrid가 갑자기 너무 느려졌기 때문이 아니었습니다.

기본적으로 이런 이유였습니다:

행 높이: 29px

550,000 × 29px ≈ 1,595만 px

우리는 브라우저에서 허용하는 실질적인 물리적 높이 한계에 접근하고 있었습니다.

그래서 처음에는 데모의 한도를 그쯤으로 제한했습니다.

그리고 한동안은 그것이 합리적으로 보였습니다.

어쨌든...

브라우저에서 55만 개 이상의 행이 필요한 사람이 있을까요?

그러다 개발자의 본능이 발동했습니다:

"하지만 만약 1,600만 픽셀짜리 div가 아예 필요 없다면 어떨까?"

그리고 이 생각이 모든 것을 바꾸었습니다.


물리적 스크롤

이전 모델은 간단했습니다.

행의 수가 물리적인 스크롤 높이를 직접적으로 결정했습니다.

이전 모델

550,000 행

× 29px

15,950,000px

물리적 스크롤 영역

브라우저: "그만해"

이 방식은 정말 잘 작동합니다...

작동하지 않게 되기 전까지는요.

그래서 BGrid v1.0.3 에서는 아키텍처를 변경했습니다.

브라우저의 물리적 스크롤 위치를 실제 데이터셋 위치로 취급하는 대신, 이 둘을 분리했습니다.

우리는 다음 방식에서:

물리적 스크롤 (Physical scrolling)

다음 방식으로 변경했습니다:

논리적 스크롤 (Logical scrolling)

물리적 스크롤 ≠ 논리적 스크롤

새로운 아이디어는 놀라울 정도로 간단합니다.

브라우저는 그저 안전한 물리적 범위 내에서만 스크롤하면 됩니다.

그런 다음 BGrid는 그 물리적 스크롤 위치를 훨씬 더 큰 논리적 데이터셋 범위로 매핑합니다.

개념적으로 설명하면 다음과 같습니다:

새로운 모델

1,000,000+ 행

논리적 스크롤 범위

제한된 물리적 스크롤 범위에 매핑

논리적 행 위치 계산

보이는 행만 렌더링

따라서 이전처럼:

1 행 = 29 물리적 픽셀

이라고 전체 데이터셋에 대해 말하는 대신, 이렇게 생각할 수 있습니다:

물리적 스크롤바 위치
        ↓
논리적 위치
        ↓
대상 행 인덱스
        ↓
화면에 보이는 행

브라우저는 이제 100만 개의 행이 물리적으로 얼마나 높을지 알 필요가 없습니다.

그리고 그것은 엄청난 차이를 만듭니다.


안녕, 100만행 👋

{% embed https://youtu.be/BBq4hPsNDNI %}

전체 물리적 스크롤 높이에 대한 의존성을 제거하자, 기존의 55만이라는 한계가 사라졌습니다.

그래서 자연스럽게 시도해 보았습니다:

1,000,000 행

그리고 성공했습니다.

이것이 현재 BGrid v1.0.3의 대용량 데이터 예제입니다.

중요한 부분은 단순히 그리드가 100만 개의 행을 표시할 수 있다는 것이 아닙니다.

이제는 다음 과 같은 것이 필요 없다는 것입니다:

1,000,000 × 29px = 29,000,000px

이를 물리적 DOM 높이로 만들 필요가 없어졌습니다.

대신:

1,000,000 행

논리적 스크롤 공간

제한된 물리적 스크롤바

가상화된 뷰포트

보이는 행만 렌더링

데이터셋이 커져도 DOM 요소의 높이가 동일한 비율로 커질 필요 없이 무한히 증가할 수 있습니다.


그래서... 1,000만 개는요?

다음에 무슨 일이 일어났을지 정확히 아실 겁니다.

100만 개가 성공했습니다.

그래서 당연히 '0'을 하나 더 입력해 보았습니다.

😂

10,000,000 행

그리고 네.

BGrid는 그것을 표시할 수 있었습니다.

자, 누군가 해커뉴스(Hacker News)를 열고 키보드를 날카롭게 다듬기 전에 한 말씀 드리겠습니다:

아니요, 1,000만 개의 데이터베이스 레코드를 브라우저 메모리에 로드하라고 제안하는 것이 아닙니다.

제발 그러지 마세요. 😄

그 정도 규모라면 일반적으로 서버 측 처리, 페이지네이션, 점진적 로딩 또는 기타 데이터 전략이 훨씬 더 합리적일 것입니다.

1,000만 개 테스트의 목적은 다음을 주장하기 위한 것이 아니었습니다:

"모두가 React에 1,000만 개의 행을 넣어야 합니다!"

그것은 다른 질문에 답하기 위함이었습니다:

그리드 자체가 여전히 브라우저의 최대 물리적 스크롤 높이에 의해 근본적으로 제한되는가?

논리적 스크롤로 전환한 후의 대답은 다음과 같습니다:

아니요.

그리고 그것이 바로 우리가 정말로 알고 싶었던 것입니다.


물론 여전히 한계는 있습니다

논리적 스크롤이 브라우저에 마법처럼 무한한 리소스를 제공하는 것은 아닙니다.

매우 큰 데이터셋 크기에서는 여전히 다음 사항들을 고려해야 합니다:

  • Memory usage (메모리 사용량)
  • Data generation (데이터 생성)
  • Sorting (정렬)
  • Filtering (필터링)
  • Searching (검색)
  • Serialization (직렬화)
  • Network transfer (네트워크 전송)
  • Application state management (애플리케이션 상태 관리)

만약 여러분이 1,000만 개의 거대한 JavaScript 객체를 메모리에 로드하고 노트북에서 드론 소리가 나기 시작한다면...

그건 아마도 그리드의 잘못이 아닐 것입니다. 😅

논리적 스크롤이 해결하는 것은 다음과 같은 한 가지 특정한 아키텍처적 한계입니다:

최대 데이터셋 크기가 더 이상 DOM 요소의 최대 물리적 높이에 직접적으로 묶여 있지 않습니다.

그리고 이것은 대용량 데이터셋을 다루기 위한 훨씬 더 나은 기반을 제공합니다.


BGrid는 단순히 100만 행 데모용이 아닙니다

성능에 대해 이야기하는 것은 재밌습니다. 왜냐하면...

글쎄요...

1,000,000 ROWS 는 헤드라인으로 보기에 아주 멋지니까요. 😎

하지만 BGrid는 단순한 벤치마크 장난감을 목적으로 만들어진 것이 아닙니다.

우리는 실제 비즈니스 애플리케이션을 위해 이를 구축하고 있습니다.

현재 다음과 같은 기능들을 포함하고 있습니다:

  • Virtual scrolling (가상 스크롤)
  • Cell editing (셀 편집)
  • Cell merging (셀 병합)
  • Sorting (정렬)
  • Filtering (필터링)
  • Summary (요약)
  • Pivot (피벗)
  • Frozen rows (행 고정)
  • Frozen columns (열 고정)
  • Custom editors (커스텀 에디터)
  • Keyboard navigation (키보드 네비게이션)

이는 React + TypeScript로 개발되었으며 Apache-2.0 라이선스로 배포되었습니다.

오픈소스 기능에 대해서는 상용 라이선스가 필요하지 않습니다.


왜 또 다른 React 데이터 그리드를 만들었을까요?

타당한 질문입니다.

React 생태계에는 이미 몇 가지 훌륭한 데이터 그리드들이 존재합니다.

하지만 우리는 오랜 시간 동안 비즈니스 애플리케이션과 그리드를 구축해 왔습니다.

우리가 원했던 것은 다음과 같습니다:

  • Open source (오픈 소스)
  • Business-application focused (비즈니스 애플리케이션 중심)
  • Strongly typed (강력한 타입 시스템)
  • React-friendly (React 친화적)
  • Easy for developers to customize (개발자가 쉽게 커스터마이징 가능)
  • Easy for AI coding agents to understand and use (AI 코딩 에이전트가 이해하고 사용하기 쉬움)

특히 마지막 항목이 최근 우리에게 점점 더 흥미로워지고 있습니다.

개발자들이 AI와 함께 더 많은 소프트웨어를 작성하는 세상에서, 좋은 타입, 예측 가능한 API, 예제 및 문서는 이제 단순한 개발자 경험(Developer Experience)에 그치지 않습니다.

그것들은 또한 AI 개발자 경험(AI Developer Experience)이기도 합니다.

이것이 우리가 BGrid에서 적극적으로 실험하고 있는 부분입니다.


한번 망가뜨려 보세요

공개 데모는 현재 1,000,000 행을 사용하고 있습니다.

솔직히 말해, 사람들이 이를 망가뜨려 보려고 시도해 주었으면 합니다.

👉 Demo: https://bgrid.axisj.com/

👉 GitHub: https://github.com/axisj/beautiful-grid

React 애플리케이션을 구축하고 있다면, 한번 시도해 보세요.

만약 무언가 망가진다면, 이슈를 열어주세요.

아키텍처가 잘못되었다고 생각한다면, 우리에게 알려주세요.

스크롤 문제를 해결할 더 나은 방법을 알고 있다면, 정말로 듣고 싶습니다.

그리고 이 프로젝트가 마음에 드신다면, GitHub ⭐ 별점은 젊은 오픈소스 프로젝트에 큰 도움이 됩니다.


다른 프론트엔드 개발자들에게 드리는 질문:

브라우저에 표시해 본 가장 큰 데이터셋은 어느 정도였나요?

그리고 더 중요한 건...

가장 먼저 무엇이 고장났나요? 😄

(주)액시스제이

서울 영등포구 선유로49길 4, 604호 (우.07208)

© AXISJ Inc. All rights reserved.