포스트

엑셀로 내려준 표가 깨져 보일 때 — POI 셀 스타일과 열 너비

운영자가 받는 엑셀은 내용뿐 아니라 읽히는 모양이 중요하다. POI에서 CellStyle을 재사용해야 하는 이유와 autoSizeColumn의 비용, 그 대안.

엑셀로 내려준 표가 깨져 보일 때 — POI 셀 스타일과 열 너비

운영자에게 내려주는 엑셀은 데이터만 맞다고 끝이 아니다. 헤더가 굵고, 숫자에 천 단위 콤마가 찍히고, 열 너비가 적당해야 “읽힌다”. 그런데 POI(Apache POI)로 서식을 입히다 보면, 느려지거나, 아예 파일이 안 열리는 함정에 빠진다. 원인은 거의 항상 같다 — 스타일 객체를 셀마다 새로 만드는 것.

CellStyle을 매 셀 new 하면 안 되는 이유

POI의 CellStyle은 셀이 들고 있는 게 아니라 워크북 전역 스타일 테이블의 인덱스를 가리킨다. 엑셀 파일 포맷(특히 구형 .xls)은 워크북당 스타일 개수에 상한이 있다 — .xls는 약 4,000개. 셀마다 createCellStyle()을 호출하면 행 수만큼 스타일이 생겨 한도를 넘고, The maximum number of cell styles was exceeded 예외로 죽거나, 한도가 큰 .xlsx여도 메모리가 폭증한다.

스타일은 반복되는 종류만큼만 미리 만들어 재사용한다.

1
2
3
4
5
6
7
8
// 안티패턴: 행마다 스타일 생성 → 스타일 폭증
for (Order o : orders) {
    Row row = sheet.createRow(i++);
    Cell c = row.createCell(0);
    CellStyle s = workbook.createCellStyle(); // ❌ 매번 new
    s.setDataFormat(fmt.getFormat("#,##0"));
    c.setCellStyle(s);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 올바른 패턴: 스타일은 루프 밖에서 한 번 만들어 공유
CellStyle moneyStyle = workbook.createCellStyle();
moneyStyle.setDataFormat(workbook.createDataFormat().getFormat("#,##0"));

CellStyle headerStyle = workbook.createCellStyle();
Font bold = workbook.createFont();
bold.setBold(true);
headerStyle.setFont(bold);
headerStyle.setAlignment(HorizontalAlignment.CENTER);

for (Order o : orders) {
    Row row = sheet.createRow(i++);
    Cell c = row.createCell(0);
    c.setCellValue(o.getAmount());
    c.setCellStyle(moneyStyle); // ✅ 공유 스타일 참조
}

스타일 생성을 공통 메서드로 빼서 “헤더용/금액용/날짜용”처럼 종류 단위로만 만들면, 데이터가 10만 행이어도 스타일은 몇 개로 고정된다.

autoSizeColumn의 비용과 대안

열 너비를 맞추는 sheet.autoSizeColumn(i)는 편하지만 비싸다. 해당 열의 모든 셀을 렌더링해 텍스트 폭을 측정하므로 데이터가 많을수록 느려지고, 서버에 폰트 메트릭(헤드리스 환경의 폰트)이 없으면 오작동하기도 한다. 또 SXSSF(스트리밍) 모드에선 이미 디스크로 밀린(flush) 행은 측정할 수 없다.

대안:

  • 고정 너비 — sheet.setColumnWidth(i, 20 * 256) (단위는 1/256 문자폭). 가장 빠르고 예측 가능하다.
  • 꼭 자동이 필요하면 마지막에 한 번만 — 데이터를 다 쓴 뒤 호출하고, 스트리밍 모드면 SXSSFSheet.trackAllColumnsForAutoSizing()로 추적을 켠다.

핵심 요약

  • CellStyle은 워크북 전역 자원이다. 셀마다 new 하면 한도 초과·메모리 폭증.
  • 스타일은 종류 단위로 루프 밖에서 만들어 공유한다.
  • autoSizeColumn은 측정 비용이 크다 — 고정 너비가 기본, 자동은 마지막에 한 번만.

면접 한 줄: “엑셀 다운로드가 행 수에 비례해 느려지고 가끔 죽는다, 왜?” → 셀마다 CellStyle을 생성해 스타일 테이블이 폭증한 것. 스타일 재사용으로 해결한다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.