티몬 개발팀이 Java 엑셀 다운로드를 JXLS로 구현했다가 운영에서 OutOfMemory로 서버가 다운된 뒤 POI의 SXSSF로 바꾼 기록이다. 두 라이브러리의 구현 방법과 장단점, 장애 원인, 전환 후 성능 비교까지 이어진다. 1만 행 기준 평균 수행 시간이 10.35초에서 2.097초로 줄었다.
핵심 포인트- JXLS는 데이터를 메모리에 계속 들고 있어 행이 많아지면 느려지고 OutOfMemory로 이어진다.
- 신규 프로젝트라 더미 데이터 몇 건으로만 테스트하고 배포한 것이 장애의 직접 배경이었다.
- 약 1만 행을 넘기는 순간 OOM으로 서버가 다운됐다.
- SXSSF는 메모리의 행을 디스크 임시 파일로 흘려보내며 처리해 메모리 사용을 낮춘다.
- flushRows로 내보내고 dispose로 임시 파일을 지우는 것이 한 쌍이다.
- 1만 행 29컬럼 기준 10회 평균이 JXLS 10.35초, SXSSF 2.097초였다.
상세 정리- 두 갈래: Java 엑셀 다운로드는 보통 템플릿 기반의 JXLS와 셀을 직접 다루는 POI 중 하나를 쓴다.
- JXLS 방식: 미리 만든 엑셀 템플릿에 명령어를 넣고 자바 모델 속성명과 템플릿 속성명을 맞추면 데이터가 반복해 채워진다. 개발이 빠른 것이 장점이다.
- 템플릿 명령어: jx:area의 lastCell로 명령 영역의 끝을 지정하고, items에 자바에서 만든 컬렉션의 컨텍스트명을, var에 셀에서 쓸 모델명을 준다.
- 주의점 1: context.putVar의 컨텍스트명과 템플릿의 items 값이 정확히 같아야 동작한다.
- 주의점 2: lastCell 밖의 셀에 명령을 써도 실행되지 않아 데이터가 비어 보인다.
- 주의점 3: 메모의 명령어를 복사·붙여넣기로 넣다가 큰따옴표 인코딩이 깨져 동작하지 않는 경우가 있는데 겉보기로는 구분이 안 된다.
- 장애 경위: 어드민 엑셀 다운로드 요구사항을 개발 기간에 맞추려 JXLS로 만들었고 동작 여부만 확인하고 배포했다. 운영 데이터가 쌓이자 느리다는 이야기가 나왔고 재현하려는 순간 서버가 다운됐다.
- 원인 분석: 다운로드 대상이 약 1만 행을 넘자 OutOfMemory가 발생했다. 신규 프로젝트라 개발 시점에는 데이터가 거의 없었던 것이 테스트가 무의미했던 배경이다.
- 해결 조치: SXSSF로 바꾸는 동시에 DB에서 데이터를 가져올 때 페이징 처리를 함께 적용했다.
- POI 세 갈래: HSSF는 엑셀 2007 이전 xls용, XSSF는 2007 이후 xlsx용, SXSSF는 XSSF의 스트리밍 버전으로 대용량에 쓴다.
- SXSSF 원리: OOXML 형태의 파일을 서버 디스크에 만들어 데이터를 임시로 기록하고 메모리를 비우는 방식이라 메모리를 적게 쓴다.
- 워크북 생성: 템플릿 파일로 XSSFWorkbook을 만든 뒤 이를 인자로 SXSSFWorkbook을 만든다. 생성자의 두 번째 인자는 디스크로 flush되기 전까지 메모리에 유지할 행 수다.
- 데이터 삽입: POI는 row를 만들고 셀마다 값을 직접 넣어야 한다. 삽입이 끝나면 flushRows로 메모리의 행을 디스크로 옮긴다.
- 마무리: 응답 헤더를 세팅하고 write로 스트림에 쓴 뒤 dispose로 임시 파일을 지운다. dispose가 호출되지 않으면 임시 파일이 디스크에 그대로 남는다.
- 디버깅 관점: JXLS는 템플릿의 명령어나 속성명 오타로 조용히 실패해 원인 파악에 시간이 들고, SXSSF는 자바 코드에서 전부 통제하므로 상대적으로 쉽다.
- 실측 조건: 약 1만 건, 행당 29개 컬럼, 각 방식 10회 수행이며 JXLS는 8.7~14.3초, SXSSF는 1.4~4.3초 범위였다.
- 실무 운영: 티몬은 속성이 20~30개에 달해 템플릿을 미리 만들어 두고 쓰지만, 템플릿 없이 빈 통합문서에 동적으로 헤더를 만드는 방법도 가능하다.
왜 읽나어드민 엑셀 다운로드를 붙이기 전에 데이터가 쌓였을 때의 메모리 한계와 SXSSF 전환 비용을 미리 가늠할 수 있다.