[Nexacro N] Grid 가상화(Virtual Scrolling)

Nexacro N Grid의 virtualrowcount 속성으로 수만 행을 DOM 50개만으로 렌더링하는 가상 스크롤 기술을 설명하고, rowheight 고정 필수 조건과 셀 병합 등 제약사항을 다룹니다.

· 6 min read · PALDYN Team

지난 글에서 대용량 데이터의 전반적인 처리 전략을 살펴봤습니다. 이번 글에서는 그 중 Grid 가상화(Virtual Scrolling) 기술을 집중적으로 다룹니다. virtualrowcount 속성 하나로 수만 행 Grid의 렌더링 성능을 극적으로 개선할 수 있습니다.

가상화의 핵심 아이디어

일반적으로 Grid는 Dataset의 모든 행에 대해 DOM 요소를 생성합니다. 10,000행이면 10,000개의 행 DOM, 각 행마다 수십 개의 셀 DOM이 생깁니다. 이 많은 DOM을 브라우저가 처리하는 것이 성능 문제의 주원인입니다.

가상화는 발상의 전환입니다. 사용자가 실제로 볼 수 있는 행은 화면 크기에 따라 기껏해야 30~50행입니다. 나머지는 스크롤해야만 보입니다. 그렇다면 화면에 보이는 행만 DOM으로 만들고, 스크롤이 발생하면 DOM을 재사용하면서 데이터만 교체하면 됩니다.

Grid 가상화 원리

virtualrowcount 설정

<Grid id="grd"
      bindeddataset="dsHuge"
      virtualrowcount="50"
      rowheight="24"
      cellrecycling="true" />
속성설명
virtualrowcount실제 DOM으로 그릴 행 수. 화면에 보이는 행 수 + 여유분
rowheight행 높이(px). 가상화 사용 시 고정 값 필수
cellrecycling셀 DOM 재사용 활성화. 가상화와 함께 사용 권장

virtualrowcount="50"이면 Dataset에 10,000행이 있어도 DOM은 50개만 생성됩니다. 스크롤 시 DOM 위치를 이동하고 데이터만 교체합니다.

virtualrowcount 설정 코드

rowheight 고정이 왜 필수인가?

가상화는 행의 위치를 rowIndex × rowHeight 수식으로 계산합니다. 행마다 높이가 다르면 스크롤바의 위치와 실제 데이터 위치가 맞지 않아 틀어집니다. 따라서 autorowheight(내용에 따라 높이 자동 조정)와 virtualrowcount함께 사용할 수 없습니다.

행 안에 줄바꿈이 있거나 내용 길이가 다른 열이 있다면 가상화 대신 다른 전략을 선택해야 합니다.

virtualrowcount 값 결정

적절한 값은 화면에 표시 가능한 최대 행 수 × 1.5~2 배 정도가 권장됩니다.

// Grid 높이에 따른 최적값 계산
var nGridH   = grd.height;       // Grid 컴포넌트 높이
var nRowH    = grd.rowheight;    // 행 높이
var nVisible = Math.ceil(nGridH / nRowH);
var nVirtual = Math.ceil(nVisible * 2);

grd.set_virtualrowcount(nVirtual);

너무 작으면 스크롤 시 빈 행이 보이는 깜박임이 생기고, 너무 크면 DOM이 많아져 가상화 효과가 줄어듭니다.

스크롤 위치 제어

가상화가 활성화된 Grid에서 특정 행으로 바로 이동하려면 scrollToRow()를 사용합니다.

// 특정 행으로 스크롤 이동
grd.scrollToRow(4999); // 5,000번째 행(0 인덱스)

// 현재 스크롤 위치 저장
var nScrollPos = grd.vscrollpos;

// 데이터 갱신 후 위치 복원
function fn_refreshCb() {
    grd.set_vscrollpos(nScrollPos);
}

vscrollpos는 픽셀 단위 스크롤 위치입니다. 행 인덱스로 저장하려면 currentrowindex를 별도로 보관합니다.

셀 스타일과 가상화

oncellstyle 이벤트는 셀이 렌더링될 때마다 발생합니다. 가상화 환경에서는 스크롤 시 DOM이 재사용되므로 이 이벤트가 빈번하게 호출됩니다. 이벤트 핸들러가 복잡하면 스크롤 성능에 영향을 줍니다.

// 무거운 계산은 피하고 단순 분기만
function grd_oncellstyle(obj, e) {
    var sStatus = ds.getColumn(e.row, "STATUS");
    // switch보다 배열 조회가 빠름
    var oStyles = {
        "ERR":  { background: "#ff444433", forecolor: "#ff4444" },
        "WARN": { background: "#ffaa0033", forecolor: "#ffaa00" }
    };
    var oStyle = oStyles[sStatus];
    if (oStyle) {
        e.backgroundColor = oStyle.background;
        e.foreColor       = oStyle.forecolor;
    }
}

제약 사항 정리

가상화를 도입하기 전에 다음 제약을 확인합니다.

제약이유대안
autorowheight 미사용행 높이 가변 불가고정 rowheight 설정
셀 병합 주의가변 span과 충돌 가능헤더 병합만 허용
setCellMerge() 부분 제한스크롤 시 병합 계산 오류정적 병합만 사용
scrollToRow() 필요인덱스 직접 계산 안 됨API 사용

실전 적용 체크리스트

// 가상화 적용 체크리스트 함수
function fn_enableVirtualization() {
    // 1. rowheight 고정 확인
    if (!grd.rowheight || grd.rowheight <= 0) {
        grd.set_rowheight(24);
    }

    // 2. autorowheight 비활성화
    grd.set_autorowheight(false);

    // 3. virtualrowcount 설정
    var nVirtual = Math.ceil((grd.height / grd.rowheight) * 2);
    grd.set_virtualrowcount(nVirtual);

    // 4. cellrecycling 활성화
    grd.set_cellrecycling(true);
}

성능 비교 기대치

데이터 건수가상화 미사용 초기 렌더링가상화 사용 초기 렌더링
1,000건~100ms~80ms
5,000건~500ms~90ms
10,000건~1,200ms~100ms
50,000건5초+ (체감 불가)~150ms

수치는 환경마다 다르지만, 1만 건 이상에서 가상화 효과는 10배 이상입니다.


지난 글: Grid 대용량 데이터 처리

다음 글: Grid 렌더링 최적화


읽어주셔서 감사합니다. 😊