pile·
백엔드·티몬티몬·

Java 대용량 엑셀 다운로드 기능 구현

티몬 개발팀이 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 전환 비용을 미리 가늠할 수 있다.
티몬
티몬 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·twilio-engTwilio Engineering·

    Programmable Messaging에서 Verify API로 마이그레이션하기

    Twilio의 Programmable Messaging API로 자체 OTP 솔루션을 운영하던 서비스가 Verify API로 전환하는 방법을 코드 예시와 함께 설명한다. Verify는 OTP 전송·검증을 위한 전용 API로, 전화번호 구매, 토큰 생성, DB 저장·만료 관리를 내부에서 처리해 개발자가 직접 구현할 코드를 크게 줄인다.

    요약 이어보기
    #authentication#twilio#sms+2
  2. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 2부

    포스타입 백엔드 엔지니어가 벡터 기반 개인화 추천 시스템을 실제 운영하며 맞닥뜨린 성능 장애와 용량 문제를 해결한 과정을 담은 2부다. 수백만 개의 벡터 KNN 검색이 피크 시간대에 전체 Elasticsearch 검색 성능을 흔드는 문제부터 클러스터 OOM 사태까지, 쿼리 최적화와 인프라 분리 두 가지 경로로 근본 해결에 이른다.

    요약 이어보기
    #elasticsearch#vector-search#recommendation-system+2
  3. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 1부

    포스타입이 태그 기반 추천의 한계를 극복하고 벡터 임베딩 기반 개인화 추천 시스템을 구축한 과정을 담은 1부다. 유사한 콘텐츠가 다른 용어를 쓰거나 동일한 태그가 전혀 다른 톤의 콘텐츠를 가리키는 문제를 임베딩 벡터로 해결하고, OpenSearch의 HNSW ANN 검색으로 수백만 벡터를 실시간 검색하는 시스템을 구축해 구매율 15% 향상을 달성했다.

    요약 이어보기
    #opensearch#vector-search#recommendation-system+2