행·열 가상화를 적용해 대용량 편집 테이블의 프리징을 완화하고(셀 DOM 3600 → 408, 모달 오픈 Scripting 약 15.4s → 0.4s), 실측 근거를 바탕으로 실사용 처리 한도를 2000건에서 3000건으로 상향했습니다.
여러 항목을 한 화면에서 일괄 편집하는, 행이 많은 화면에서는 항상 같은 문제가 반복됐습니다.
공식적으로는 대략 2,000건까지 처리 가능하다는 정책이 있었지만
실제 테이블 UI로는 그 근처에도 가기 전에 스크롤·입력·모달 조작 중 브라우저가 멈추는 현상이 발생했습니다
결국 사용자들은 엑셀로 우회하거나, 인프라/배치 작업으로 문제를 떠넘기는 경우가 많았습니다
"이론상 가능하다"와 "테이블로 실제 작업할 수 있다" 사이의 간극을 좁히는 것, 그것이 이번 작업의 목표였습니다.
데이터를 많이 들고 있는 것 자체는 문제의 본질이 아니었습니다. 진짜 병목은 다음 두 가지였습니다.
최초 렌더링 비용 — 큰 테이블 트리를 한 번에 그리는 데 드는 비용
불필요한 DOM 유지 비용 — 화면에 보이지 않는 행·열까지 DOM에 이미 올라가 있는 상태에서 스크롤·입력·모달 이벤트가 발생하는 비용
여기에 더해 구조적인 제약도 있었습니다. 일괄 편집 특성상 서버 페이지네이션이 불가능했습니다. 사용자가 전체 데이터를 한 화면에서 편집·검증·제출해야 했기 때문에, 데이터는 반드시 클라이언트에 통째로 유지되어야 했습니다.
즉 선택할 수 있는 지점은 하나로 좁혀졌습니다.
구분 | 방향 |
|---|---|
데이터 | 클라이언트에 전량 유지 (필수, 변경 불가) |
렌더링 | 페이지 단위 + 가시 영역의 행·열만 DOM에 마운트 |
가상화는 데이터 보관량을 줄이는 기술이 아니라, 렌더링·업데이트 범위를 줄여 프리징을 막는 기술이라는 점이 이번 작업의 핵심 전제였습니다.
적용한 것은 세 가지입니다.
Row virtualization: 뷰포트(+overscan) 영역의 행만 마운트
Column virtualization: 가로로 긴 편집 테이블에서 가시 열만 마운트
클라이언트 페이지네이션: 한 페이지당 렌더 대상 행 수 상한 설정 (예: 300행)
여기서 중요한 포인트는, 페이지네이션만으로는 부족하다는 것이었습니다. 페이지당 300행 × 컬럼 12개 구조에서 비가상화 상태라면, 화면에 보이는 건 수십 행뿐이어도 셀은 이미 3,600개까지 DOM에 올라가 있는 상태였습니다. 페이지를 나누는 것과, 그 페이지 안에서도 보이는 부분만 그리는 것은 완전히 다른 문제였습니다.
개선을 주장하려면 수치가 필요했습니다. 측정은 4가지 시나리오로 나눠 진행했습니다: DOM 개수, 스크롤, 모달 오픈, 셀 타이핑.
가상화 테이블은 div 기반이라,
td등 특정 태그 자손 카운트로는 ON/OFF를 공정하게 비교할 수 없어 실제 마운트된 행·셀 개수를 직접 세는 방식으로 측정했습니다.
상태 | rows | cells |
|---|---|---|
가상화 OFF | 300 | 3,600 |
가상화 ON (일반) | 24 | 288 |
가상화 ON (최대, overscan 포함) | 34 | 408 |
셀 기준 약 89% 감소 (3,600 → 408)
상태 | Scripting | Rendering | Painting |
|---|---|---|---|
ON | 5,348ms | 1,080ms | 521ms |
OFF | 2,045ms | 4,262ms | 917ms |
Rendering: 4,262ms → 1,080ms (약 75% 감소) Scripting만 보면 OFF가 낮아 보이지만, 스크롤 프리징의 실제 원인은 Rendering + DOM이었고, 개선의 본체도 여기에 있었습니다.
상태 | Scripting | Rendering | Painting |
|---|---|---|---|
ON | 384ms | 113ms | 75ms |
OFF | 15,363ms | 1,297ms | 1,971ms |
Scripting: 약 15.4s → 0.4s OFF 상태에서 사용자가 체감하던 장시간 Freeze가 크게 완화됐습니다.
상태 | Scripting | Rendering | Painting | 체감 프레임 |
|---|---|---|---|---|
ON | 547ms | 99ms | 55ms | 0.4~0.5s |
OFF | 8,600ms | 1,715ms | 799ms | ~2s |
Scripting: 약 8.6s → 0.5s / 프레임 블로킹: 약 2s → 0.5s OFF 상태는 입력 반영 자체가 지연되어 "같은 시간에 몇 글자를 쳤는가"보다 "입력이 멈추지 않고 반영되는가"가 더 중요한 기준이었습니다.
측정 결과를 그대로 받아들이지 않고, 시나리오별로 왜 다른 패턴이 나오는지 분석했습니다.
모달·타이핑: 리렌더링해야 할 대상이 대량에서 소량으로 줄어드는 문제 → 가상화로 Scripting이 감소
스크롤: 매 스크롤마다 가시 행/열을 새로 계산하고 mount/unmount하는 작업이 발생하는 문제 → 가상화로 Scripting이 오히려 증가할 수 있음
시나리오 | 가상화의 역할 | Scripting | Rendering |
|---|---|---|---|
모달·타이핑 | 리렌더 범위를 줄임 | ↓ | - |
스크롤 | 가상화 계산을 새로 함 | ↑ | ↓↓ |
이는 실패가 아니라 트레이드오프였습니다. 그래서 지표도 시나리오별로 나눠서 봐야 한다는 결론을 내렸습니다.
시나리오 | 중점적으로 봐야 할 지표 |
|---|---|
스크롤 | Rendering, DOM 개수 |
모달·타이핑 | Scripting, 프레임/Long Task |
이전
정책상 약 2,000건까지 가능하다고 명시돼 있었지만
테이블 UI로는 사실상 사용 불가에 가까웠고
엑셀·인프라 우회에 의존하는 상황이었습니다
이후
행·열 가상화로 DOM 및 프리징 구간을 대폭 줄이고
스크롤/모달/타이핑 각 시나리오의 실측 데이터로 "테이블로도 동작한다"는 근거를 확보했으며
이를 바탕으로 실사용 처리 한도를 3,000건까지 상향했습니다
즉 이번 작업은 단순한 UI 최적화가 아니라, "테이블로 대용량 일괄 처리를 제품 차원에서 지원 가능하게 만드는 근거"를 만든 일이었습니다.
수천 건의 데이터를 클라이언트에 유지해야 한다는 구조적 제약 자체는 여전히 남아 있습니다. 다만 예전처럼 "그리느라 브라우저가 멈추는" 문제에는 더 이상 막히지 않게 됐다는 점이 핵심입니다.
항목 | 이전 | 이후 |
|---|---|---|
실사용 처리 한도 | 2,000건 | 3,000건 |
DOM 셀 개수 (최대) | 3,600 | 408 |
DOM 행 개수 (최대) | 300 | 34 |
스크롤 Rendering | 4,262ms | 1,080ms |
모달 오픈 Scripting | ~15.4s | ~0.4s |
타이핑 Scripting | ~8.6s | ~0.5s |
타이핑 프레임 블로킹 | ~2s | ~0.5s |
대용량 일괄 편집 화면에서 서버 페이지네이션이 불가능하다면, 데이터는 결국 클라이언트에 남을 수밖에 없습니다. 이 제약 안에서 브라우저 프리징을 줄이는 실질적인 방법은 행·열 가상화로 가시 영역만 렌더링하는 것이었습니다.
측정을 통해 DOM 개수, 스크롤 Rendering, 모달/타이핑 Scripting이 실제로 개선됨을 확인했고, 이 데이터를 근거로 테이블의 실사용 한도를 2,000건에서 3,000건으로 상향할 수 있었습니다.