배경 및 목표
시트가 유지보수 시 평균 2~3일이 소요되었고, 엑셀 다운로드 로직이 Node 서버에 별도로 존재하여 도메인 로직이 이중으로 관리되고 있었습니다. Node 유지보수가 가능한 인원이 소수였고, 앞으로의 요구 사항과 유지 보수를 위해선 기술 스택 통합이 시급했습니다.
graph LR subgraph AS_IS["❌ 기존: 기술 스택 분산"] A["Spring 서버"] -->|"도메인 로직"| D1["DB"] B["Node 서버"] -->|"엑셀 로직 + 도메인 복제"| D1 end subgraph TO_BE["✅ 개선: 단일 스택"] C["Spring 서버"] -->|"도메인 + 엑셀"| D2["DB"] end AS_IS -.->|"포팅"| TO_BE
- Node에 엔티티와 도메인 로직을 복제해서 관리 → 변경 시 양쪽 수정 필요
- 시트 추가 시 공통 로직을 매번 복사 → 확장성 없음
- 수만 건 데이터를 메모리에 전부 로드 → OOM 위험
목표
- 엑셀·도메인 로직 이중 관리를 없애고 Spring 단일 스택으로 통합한다.
- 수만 건 다운로드에도 메모리가 일정하게 유지되는 안정적 처리를 확보한다.
해결 방법과 해결 후보군
1. 템플릿 메서드 패턴으로 확장성 확보
단순 포팅이 아닌 구조를 재설계했습니다. 모든 시트는 makeSheet → makeHeader → fillData 공통 흐름을 따르고, 새 시트는 AbstractSheet를 상속해서 2개 메서드만 구현하면 됩니다.
classDiagram class AbstractSheet { <<abstract>> +makeSheet(data, workbook) #makeHeader(sheet)* #fillData(data, sheet)* } class 지원자정보Sheet { #makeHeader(sheet) #fillData(data, sheet) } class 평가정보Sheet { #makeHeader(sheet) #fillData(data, sheet) } AbstractSheet <|-- 지원자정보Sheet AbstractSheet <|-- 평가정보Sheet
2. 멀티 모듈 형태로 기존 도메인 로직 재사용
멀티 모듈을 이용하여 사용중인 도메인 모듈을 재사용하였습니다. 한 곳에서 비즈니스 로직을 관리할 수 있게 되었고, 예상보다 약 4md 정도 빠르게 엑셀 시스템을 이관할 수 있었습니다.
graph TB subgraph problem["도메인 로직 재사용"] A["api 모듈"] --> D B["worker 모듈"] --> D C["파일 처리 모듈(신규)"] --> D D["도메인 모듈"] end
3. SXSSFWorkbook 스트리밍으로 OOM 방지
XSSFWorkbook(전체 메모리 로드) 대신 SXSSFWorkbook(윈도우 방식)을 적용하여, 일정 행만 메모리에 유지하고 나머지는 디스크로 내려서 메모리 사용량을 일정하게 유지했습니다.
sequenceDiagram participant E as ExcelDownloader participant F as DataFetcher participant S as Sheet 구현체 participant D as Disk E->>E: SXSSFWorkbook 생성 (윈도우: N행) loop 청크 단위 E->>F: Slice 쿼리로 N개 조회 F-->>E: 데이터 청크 E->>S: 시트에 데이터 전달 S->>D: 임시 파일 생성 end E->>E: Streaming 응답
결과
| 지표 | 기존 (Node) | 개선 (Spring) |
|---|---|---|
| 도메인 관리 | 이중 관리 | 단일 관리 |
| 엑셀 유지보수 | 2~3일 | 0.5일 |
| 동시 처리 | 순차 (단일 스레드) | 병렬 (멀티 스레드) |
| 메모리 안정성 | OOM 위험 | 스트리밍 처리 |
모니터링
- 엑셀 다운로드 시 힙 메모리 사용량(스트리밍으로 일정 유지 여부)을 관측한다.
- 시트별 생성 시간·실패율을 관측한다.