pile·
백엔드·트렌비트렌비·

백오피스 엑셀 다운로드 속도 개선하기

트렌비 백오피스에서 엑셀 다운로드가 504 Gateway Timeout으로 실패하던 문제를 두 단계로 고친 기록이다. JPA N+1 제거로 2분 40초를 6초로, Apache POI의 Workbook 구현체 교체로 생성 시간을 2초에서 400밀리초로 줄였다. 데이터가 3만 건일 때의 차이까지 재서 붙였다.

핵심 포인트
  • 1,473건을 내려받는 데 2분 넘게 걸렸고 운영에서는 504로 끊겼다.
  • 원인은 JpaSpecificationExecutor의 findAll이 호출될 때마다 나던 N+1 쿼리였다.
  • fetchJoin을 쓰려 해도 그 인터페이스로는 JPQL을 자유롭게 쓰기 어려워 QueryDSL로 옮겼다.
  • 엔티티를 통째로 가져오는 대신 필요한 필드만 담는 Projection으로 조회했다.
  • XSSFWorkbook은 데이터를 전부 메모리에 올려 쓰기 때문에 OOM 위험이 있다.
  • SXSSFWorkbook은 설정된 창 크기만큼만 메모리에 두는 방식이라 3만 건에서 16초 차이가 났다.
상세 정리
  • 문제 인지: 구매확정 메뉴에서 엑셀 다운로드 시 504가 반환된다는 제보가 사내 메신저로 들어왔다.
  • 측정: 로컬에서 1,473건을 받아보니 2분 40초가 걸렸다. 운영은 스펙과 로그 설정이 달라 더 낫겠지만 방치할 수 없는 수준으로 봤다.
  • 원인 확인: 버튼 한 번에 무수히 많은 쿼리가 나가는 것이 로컬 테스트에서 바로 드러났다.
  • 문제 지점: 필요한 데이터를 JpaSpecificationExecutor의 findAll로 가져오고 있었고 그때마다 N+1이 발생했다.
  • 변경 1의 이유: 그 인터페이스는 개발자가 JPQL을 자유롭게 쓰기 어렵고 호출 시점에 어떤 쿼리가 나갈지 알기 힘들어 유지보수에 걸림돌이 된다고 판단했다.
  • 관계의 규모: 대상 엔티티가 파트너, 이력, 배송 안내, 취소 등 많은 객체와 연관을 맺고 있고 그중 일부만 필요했다.
  • 중첩 관계 문제: 연관된 객체가 또 다른 객체와 관계를 맺고 있어, 루트에서 여러 depth를 타고 내려가며 전부 fetchJoin을 걸면 불필요한 조인이 과도해진다.
  • 변경 2의 선택: 그래서 엔티티 목록을 반환하는 대신 엑셀에 필요한 필드만 담는 DTO를 Projections로 만들어 조회했다.
  • 1차 결과: N+1이 사라져 2분 40초가 6초로 줄었다. 이후로는 DB 조회 시간과 엑셀 생성 시간을 나눠 측정했다.
  • 남은 불만: 조인 테이블이 많아 조회 시간은 어쩔 수 없다 쳐도, 1,000건으로 엑셀을 만드는 데 2초가 걸리는 것은 수만 건에서 감당할 자신이 없었다.
  • 두 번째 발견: 기존 엑셀 유틸의 Workbook 구현체가 XSSFWorkbook으로 되어 있었다.
  • 구현체의 차이: XSSF는 데이터를 모두 메모리에 올려 쓰기 때문에 공간을 많이 쓰고 데이터가 많으면 OOM 확률이 높다.
  • 공식 문서 근거: SXSSF는 XSSF를 확장한 스트리밍 API로 아주 큰 스프레드시트를 만들어야 하고 힙 공간이 제한적일 때 쓰라고 안내한다.
  • 동작 원리: SXSSF는 BigGridDemo 전략을 구현해 기본값 100행 크기의 창만큼만 메모리에 올려 작업한다.
  • 2차 결과: 엑셀 생성 시간이 2초에서 400밀리초로 줄어 처음 대비 약 97% 개선됐다.
  • 규모 확대 검증: 3만 건으로 두 구현체를 비교하니 16초 차이가 났다. 1,000건에서는 작아 보이던 차이가 데이터가 늘수록 벌어진다.
  • 마무리 권고: JPA로 엑셀용 데이터를 조회한다면 Projection을 고려하고, Apache POI를 쓴다면 Workbook 구현체로 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