본문 바로가기

프로그래밍/spreadjs

SpreadJS 계산 성능 튜닝: 재계산 모드 설정과 CalculationProgress 이벤트로 대용량 시트 최적화

반응형

대용량 시트에서 계산이 느려지는 순간, 어디부터 잡을까요

대량의 수식과 참조가 얽힌 시트에서 버튼 클릭마다 수 초씩 멈춘다면, 단순한 렌더링 문제가 아니라 계산 전략의 문제일 가능성이 큽니다. SpreadJS는 재계산 모드와 진행 이벤트를 조합해 계산 타이밍과 사용자 체감을 동시에 조절할 수 있습니다.

 

실무에서는 입력 한 칸이 바뀌어도 전체 시트가 즉시 재계산되면 불필요한 오버헤드가 커집니다. 반대로 계산을 미루기만 하면 사용자는 응답이 없는 것처럼 느낄 수 있습니다.

 

이 글은 초보자도 따라 할 수 있도록 “언제 자동 계산을 끄고, 언제 배치로 모아 계산하며, 진행 상황을 어떻게 안내할지”를 구체적으로 다룹니다. 필요 시 SpreadJS 공식 제품 페이지에서 옵션 명칭과 버전을 다시 확인하는 것을 권장합니다.

 

재계산 모드 선택과 진행 표시의 실제 적용

피벗·SUMIFS가 많은 시트에서 입력이 몰리면 자동 재계산마다 지연이 누적됩니다. 반대로 반응이 느리면 오해가 생기니, 재계산 모드로 타이밍을 제어하는 구성이 안전합니다. 모드는 자동(즉시), 수동(명시 트리거), 배치(여러 변경 후 한 번)로 구분됩니다. 화면 체감 성능과 안정성은 모드 선택과 계산 시점 설계에 크게 좌우됩니다.

 

긴 계산에는 CalculationProgress로 진행률·상태를 노출해 불안감을 줄입니다. 대시보드 상단에 간단한 진행 바나 메시지를 두면 효과적입니다.

 

적용 체크리스트
- 연속 입력·붙여넣기가 잦은가?

→ 수동·배치 검토
- 외부 데이터 병합·복잡한 배열 수식이 많은가?

→ 진행 이벤트로 피드백
- 인터랙션 중 프레임 드롭이 보이는가?

→ 계산 시점을 일괄 처리로 재설계

 

아래 코드는 대량 편집 동안 계산을 지연하고, 완료 시 한 번만 계산하며 진행률을 표시합니다.

// 예시는 구조를 보여주기 위한 형태입니다.
const spread = new GC.Spread.Sheets.Workbook(host);
const sheet = spread.getActiveSheet();

function beginBatchEdit() {
  sheet.suspendCalcService();
  showProgress(0, '계산 대기 중');
  spread.bind(GC.Spread.Sheets.Events.CalculationProgress, (_, args) => {
    showProgress(args.progress, '계산 중');
  });
}

function endBatchEdit() {
  sheet.resumeCalcService();
  sheet.recalcAll();
  hideProgress();
}

function showProgress(pct, msg) { /* UI 업데이트 */ }
function hideProgress() { /* UI 숨김 */ }

핵심은 “변경은 모아서, 계산은 한 번에, 진행은 보이게”입니다

 

재계산 타이밍과 진행 표시 핵심 설계

변경이 몰리는 구간(연속 입력·대량 붙여넣기·외부 갱신)은 자동 계산을 잠시 멈추고 완료 시 1회만 수행하세요. 단일 셀처럼 즉시 응답이 필요한 경우에만 국소 재계산을 예외로 둡니다.

 

긴 계산은 진행률 노출로 체감 대기 시간을 줄이는 것이 중요합니다. 버튼·저장 같은 완료 트리거에 맞춰 재계산하고, 진행 이벤트로 퍼센트와 단계 메시지를 보여줍니다.

 

권장 절차
- 초기화: 자동 계산 비활성화
- 배치: 입력/붙여넣기 동안 플래그 on
- 완료: 저장·확인 시 1회 재계산
- 피드백: CalculationProgress로 퍼센트 표시
- 예외: 단일 셀은 국소 재계산

 

아래 예시는 편집 중 계산을 지연하고 완료 버튼에서 한 번 계산합니다. 

// 구조 예시
const wb = new GC.Spread.Sheets.Workbook('#ss');
const sheet = wb.getActiveSheet();
wb.options.calcOnDemand = true;

let batching = false;
const $bar = document.getElementById('progress');

wb.bind(GC.Spread.Sheets.Events.CalculationProgress, (s, a) => {
  const pct = Math.round((a.completed / Math.max(1, a.total)) * 100);
  $bar.style.width = pct + '%';
  $bar.setAttribute('aria-valuenow', String(pct));
});

function beginBatch() {
  batching = true;
  $bar.parentElement.style.display = 'block';
}

async function endBatchAndCalculate() {
  try { await wb.recalc(); }
  finally {
    batching = false;
    $bar.parentElement.style.display = 'none';
    $bar.style.width = '0%';
  }
}

document.getElementById('importBtn').addEventListener('click', async () => {
  beginBatch();
  await importDataToSheet(sheet);
  await endBatchAndCalculate();
});

패턴을 일관되게 유지하세요: 변경 축적 → 1회 계산 → 진행 표시. 필요하면 범위를 좁혀 계산 비용을 최소화합니다.

 

흔한 실수와 선택 기준: 언제 자동, 언제 수동·배치인가

초보자가 자주 겪는 문제는 모든 워크시트에 자동 재계산을 고정하는 것입니다. 연속 입력·대량 붙여넣기에서 자동 계산은 불필요한 반복을 폭증시켜 체감 지연을 크게 만듭니다. 반대로 수동 모드로만 고정하면 “저장했는데 값이 안 맞다”는 오류 신고가 늘어납니다. 완료 트리거(저장·확인·적용)에서 1회 계산을 보장하는 흐름이 안전합니다.

 

CalculationProgress를 표시하지 않는 것도 흔한 실수입니다. 긴 계산을 숨기면 멈춘 것으로 오해받으니, 최소한 퍼센트·단계 메시지를 상단에 노출하세요.

 

선택 기준
- 즉시 피드백이 중요한 단일 입력 폼: 자동 모드
- 대량 편집·붙여넣기·스크립트 일괄 수정: 배치/수동 모드 + 완료 시 1회 재계산
- 외부 데이터 병합·복잡한 배열/참조 체인: 진행 표시 필수, 계산은 사용자 트리거에 묶기

 

비교 관점
- 자동: 구현 단순, 체감 지연 위험 큼
- 수동: 성능 예측 용이, 계산 누락 리스크 존재(버튼/가드 필요)
- 배치: 중간값 흔들림 최소화, 설계·예외 처리 비용이 듦

 

주의사항
- 셀 변경 이벤트에서 추가 수정을 반복 호출하면 재계산이 중첩될 수 있습니다. - 계산 중 UI 차단이 길면 취소나 안내 버튼을 함께 제공합니다.

 

지연을 줄이는 계산 통제 핵심

재계산 시점을 직접 관리하고 진행률을 보여주면 체감 지연이 크게 줄어듭니다. 자동·수동·배치 계산을 상황별로 혼합하고 CalculationProgress로 사용자 피드백을 보완하세요.

 

바로 적용 체크리스트
- 초기화 시 자동 계산 끄기 → 완료 버튼에서 1회 재계산
- 연속 입력·대량 붙여넣기는 배치 편집으로 묶기
- CalculationProgress로 퍼센트·단계 메시지 표시
- 즉시 응답이 필요한 셀만 국소 재계산

아래 예시는 편집 구간을 배치로 묶고, 완료 시 한 번만 계산하는 구조입니다. 

// 구조 예시(실사용 전 API 명칭은 버전에 따라 확인하세요)
const spread = new GC.Spread.Sheets.Workbook('#ss');
const sheet = spread.getActiveSheet();

// 초기화: 자동 계산 비활성화
spread.options.recalcOnEdit = false;

// 진행 상황 표시
spread.bind(GC.Spread.Sheets.Events.CalculationProgress, (_, args) => {
  updateProgress(`${args.progress}% - ${args.status}`);
});

let batching = false;
function beginBatch() { batching = true; spread.suspendPaint(); }
function endBatchAndRecalc() {
  batching = false;
  spread.resumePaint();
  spread.calculate();
}

// 대량 붙여넣기 구간 묶기
sheet.bind(GC.Spread.Sheets.Events.ClipboardPasting, beginBatch);
sheet.bind(GC.Spread.Sheets.Events.ClipboardPasted, endBatchAndRecalc);

// 완료 버튼에서 보장
document.querySelector('#apply').addEventListener('click', endBatchAndRecalc);

 

진행 바와 취소로 계산 흐름 제어

대량 계산은 진행률을 보여주고 필요 시 중단할 수 있어야 합니다. CalculationProgress 이벤트로 퍼센트·단계 메시지를 바인딩하고, 취소 토글을 연결하면 체감 품질을 높일 수 있습니다.

 

아래 코드는 진행 바·메시지 갱신과 취소 신호 전달 구조를 간단히 보여줍니다. 

// 진행 표시 + 취소 토글 (구조 예시)
const spread = new GC.Spread.Sheets.Workbook('#ss');
const sheet = spread.getActiveSheet();

let cancelRequested = false;
const $bar = document.querySelector('#calc-progress');
const $msg = document.querySelector('#calc-message');
const $btnCancel = document.querySelector('#btn-cancel');

$btnCancel.addEventListener('click', () => { cancelRequested = true; });

sheet.bind(GC.Spread.Sheets.Events.CalculationProgress, (s, args) => {
  if (args.status === 'start') {
    cancelRequested = false;
    $bar.style.width = '0%';
    $msg.textContent = '계산 시작';
  } else if (args.status === 'calculating') {
    $bar.style.width = `${args.progress}%`;
    $msg.textContent = `계산 중... ${args.progress}%`;
    if (cancelRequested && args.cancel != null) args.cancel = true; // 지원 시 취소
  } else if (args.status === 'completed') {
    $bar.style.width = '100%';
    $msg.textContent = '계산 완료';
  }
});

document.querySelector('#btn-apply').addEventListener('click', () => {
  sheet.recalcAll(); // 버전에 따라 recalc 계열 API 확인
});

체크포인트
- 숫자 퍼센트와 짧은 단계 메시지를 함께 노출하세요.
- 취소 지원은 버전별로 다를 수 있습니다. 미지원이면 버튼 비활성화 또는 “다음 단계부터 중단” 안내가 안전합니다.

반응형