약 엑셀 하나당 100MB 이상의 대용량 엑셀 파일을 연속으로 처리해야하는 상황이 있었습니다.
칼럼수도 증가하며 row 수는 1000만건이 넘어가면서 메모리 부족 이슈가 발생했습니다. 거의 1GB 이상의 메모리에 올라가있는 상황이 발생했죠.
그래서 Apache POI 라이브러리의 메모리 최적화 방법을 적용해 문제를 해결한 경험을 공유합니다.
문제 상황
S3 엑셀 파일 다운로드 -> 엑셀 파싱 및 칼럼 변환 -> 사용자에게 엑셀 파일 다운로드 내려주기
이 과정에서 전체 엑셀을 메모리에 올려놓고 처리하는 방식이었기 때문에, row 수가 많아질수록 메모리 사용량이 급격히 증가했습니다.
일단 xlsx 포맷 내부 상황을 볼까요. xlsx 파일은 사실 zip 압축된 폴더입니다. 내부에는 여러 XML 파일들이 들어있고, 각 시트는 별도의 XML 파일로 저장됩니다. 그래서 zip 풀어서 보면
xl/worksheets/sheet1.xml
xl/worksheets/sheet2.xml
...
이렇게 들어있어요. 그리고 reader 로 이를 읽고 처리. 여기서 XML 시트 하나를 읽어들이는 방법에는 두 가지가 있습니다.
- DOM : 모든 시트 한 번에 메모리 올린 뒤 XML 파싱.
- SAX : 메모리에 전체 XML을 올리지 않고, 이벤트 기반으로 XML 데이터를 처리해서 각 개별 아니면 배치 단위의 row 처리.
초기에 DOM 방식으로 전체 엑셀을 메모리에 올려놓고 파싱처리했기때문에 OOM 가 발생했습니다. 이후 SAX 에 flush 타이밍을 배치단위로 잡아서 처리함으로써 메모리 사용량을 약 10MB 내외로 줄일 수 있었습니다. 즉, 여러 엑셀을 병렬로 파싱할 수 있도록 메모리 최적화를 진행했습니다.
읽고 처리하는것은 SAX 방식에 chunk 섞어서 수행했고, 이와 별도로 쓰는 것은 SXSSF 방식을 사용했습니다. 간단히 말하면 일정이상 메모리에 rows 올려넣고 디스크에 임시 저장하는 방식입니다. 이후 flush 타이밍에 디스크에서 읽어서 스트리밍 형태로 zipping 해서 내려주게 됩니다. 메모리 사용량을 제한하고 디스크로 대체할 수 있죠.