네이버페이 정산 시스템이 대형 가맹점 조회 타임아웃을 풀려고 CQRS 패턴 기반 MongoDB를 도입한 뒤 겪은 병목과 해결 과정을 다룬다. API 조회는 샤딩 DB 대비 40배 빨라졌지만 엑셀 다운로드 성능은 그대로였다. 원인은 MongoDB도 엑셀 라이브러리도 아닌, Tomcat이 Flux 리턴 타입으로 NDJSON을 한 줄씩 write·flush하며 매번 blocking하는 구조였다. 1000건씩 모아 한 번에 flush하는 버퍼링으로 해결했다.
핵심 포인트- 가맹점 기반 샤딩 DB가 상위 대형 가맹점에 데이터가 쏠려(1위 2퍼센트, 상위 10위 합산 7퍼센트) 조회 시 타임아웃이 잦았고, 이를 풀려고 CQRS로 MongoDB에 가공 데이터를 미리 저장했다.
- CQRS 도입으로 join 없는 API 조회는 샤딩 DB 대비 40배 빨라졌지만, 엑셀 다운로드 속도는 전혀 개선되지 않았다.
- MongoDB 튜닝(readConcern·projection)과 엑셀 라이브러리 교체(POI SXSSF에서 FastExcel)로도 엑셀 생성 병목이 안 풀렸다.
- 진짜 원인은 Spring MVC(Tomcat)에서 Flux 리턴 타입으로 NDJSON을 스트리밍할 때 한 줄마다 write·flush가 blocking되는 구조였다.
- Flux를 리턴하지 않고 HttpServletResponse에 1000건씩 모아 write 후 한 번 flush하는 확장 함수로 해결했다.
상세 정리- 배경: 정산 시스템은 성능·스케일아웃을 노리고 가맹점 기반 샤딩 DB를 썼으나, 대형 가맹점 데이터 쏠림으로 샤딩 불균형이 생겨 조회마다 타임아웃이 났다.
- CQRS 설계: MongoDB에 미리 가공된 데이터를 저장해 join 없이 빠르게 읽는 조회 전용 모델을 구축했다.
- 절반의 성공: API 조회 성능은 기존 샤딩 DB 대비 40배 이상 향상됐지만, 엑셀 다운로드는 개선 효과가 없었다.
- 구간별 측정: 병목 위치를 좁히려고 MongoDB 조회, 엑셀 생성, 네트워크, Spring web 구간을 나눠 각각 성능 테스트했다.
- MongoDB 조회: readConcern 레벨 조정과 projection으로 필요한 필드만 조회해 쿼리 성능은 10퍼센트 내외 개선됐지만 엑셀 생성 시간에는 영향이 없었다.
- 엑셀 라이브러리: POI SXSSF, EasyExcel, FastExcel을 벤치마킹해 FastExcel이 파일 생성 2배 빨랐지만, 전체 다운로드 병목은 여전했다.
- 네트워크: 결과 파일과 비슷한 용량 전송 테스트에서 8만 건이 1초 내외로 끝나 네트워크는 병목이 아니었다.
- 결정적 단서: 같은 데이터를 JSON 배열로 응답하면 37.7MB/s, NDJSON으로 응답하면 3.7MB/s로 10배 차이가 났다.
- NDJSON 구조: 각 줄이 JSON 객체 하나이고 개행으로 구분되는 포맷이라, Flux 아이템을 건별로 직렬화 후 write·flush하고 개행도 다시 write·flush한다.
- blocking 원리: Tomcat이 write 완료까지 blocking하므로 50만 줄이면 개행 포함 약 100만 번 blocking이 발생한다(ResponseBodyEmitter의 JsonEmitterSubscriber가 item별 처리).
- JSON 배열은 하나로 합쳐 write·flush를 한 번만 해 I/O blocking 시간이 절약돼 NDJSON보다 10배 빨랐다.
- 런타임 비교: 같은 스트리밍을 non-blocking Netty에서 돌리자 Tomcat 3~4MB/s 대비 19~20MB/s로 약 7배 빨랐지만, 한 기능 때문에 Netty 전환은 변경 범위가 커 포기했다.
- 해결: Flux의 writeBufferedStreamResponse 확장 함수로 buffer(1000) 후 각 배치를 writer로 쓰고 한 번 flush, blockLast로 마무리해 write·flush 횟수를 1/1000로 줄였다.
- 결과: 버퍼링 적용 후 기대한 엑셀 다운로드 성능 개선을 얻었고, 병목이 DB·라이브러리가 아닌 프레임워크의 스트리밍 방식에 있었음을 확인했다.
왜 읽나Spring MVC(Tomcat)에서 Flux/NDJSON으로 대량 데이터를 스트리밍하다 원인 모를 성능 저하를 겪는 백엔드 개발자에게 실전 진단·해결 레퍼런스.