2025-02-03-TIL
Handling Bulk Excel with Spring Boot
대용량 엑셀을 생성하거나 다운로드할 때 시간이 오래 걸릴 수 있으므로, 사용자 경험을 고려하여 비동기 처리와 최적화를 적용하는 것이 중요합니다. 다음과 같은 방법을 고려할 수 있습니다.
1. 비동기 처리 (Async Processing)
엑셀 생성 작업이 시간이 오래 걸리는 경우, 비동기 처리를 사용하여 사용자 요청을 바로 응답하고 백그라운드에서 작업을 진행하는 것이 좋습니다.
Spring Boot (Java)에서 비동기 처리
@Async를 활용하여 백그라운드에서 엑셀을 생성하고, 완료 후 사용자에게 다운로드 링크 제공- 작업 상태를 Redis나 DB에 저장하고, 진행 상황을 조회할 수 있도록 API 제공
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class ExcelService {
@Async
public CompletableFuture<String> generateExcel() {
try {
// 엑셀 생성 로직 (대용량 데이터 처리)
String filePath = "/tmp/report.xlsx";
createExcel(filePath);
return CompletableFuture.completedFuture(filePath);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
Go에서 비동기 처리 (goroutine + channel 활용)
1
2
3
4
5
6
7
8
9
10
func generateExcelAsync() <-chan string {
result := make(chan string)
go func() {
defer close(result)
filePath := "/tmp/report.xlsx"
generateExcel(filePath)
result <- filePath
}()
return result
}
Python에서 비동기 처리 (Celery 활용)
Django 또는 Flask 같은 웹 프레임워크를 사용할 경우 Celery + Redis로 비동기 작업을 처리할 수 있습니다.
1
2
3
4
5
6
7
8
9
from celery import Celery
app = Celery('tasks', broker='redis://localhost:6379/0')
@app.task
def generate_excel():
file_path = "/tmp/report.xlsx"
create_excel(file_path)
return file_path
2. 스트리밍 방식 다운로드 (Chunked Response)
한 번에 메모리에 로드하지 않고 스트리밍 방식으로 데이터를 처리하면 메모리 부담을 줄이고 빠르게 응답할 수 있습니다.
Spring Boot Streaming Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GetMapping("/download")
public ResponseEntity<StreamingResponseBody> downloadFile() {
InputStream inputStream = new FileInputStream(new File("/tmp/report.xlsx"));
StreamingResponseBody responseBody = outputStream -> {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.xlsx")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
Go에서 HTTP Streaming
1
2
3
4
5
6
7
8
9
10
11
12
13
func downloadExcelHandler(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("/tmp/report.xlsx")
if err != nil {
http.Error(w, "File not found.", http.StatusNotFound)
return
}
defer file.Close()
w.Header().Set("Content-Disposition", "attachment; filename=report.xlsx")
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
io.Copy(w, file) // 파일을 스트리밍으로 전송
}
3. 엑셀 생성 최적화 (Memory 효율적 사용)
대용량 엑셀을 처리할 때 POI, Openpyxl 등의 라이브러리를 사용할 경우 메모리 사용량을 줄이는 방법을 적용할 수 있습니다.
Java - Apache POI SXSSFWorkbook 사용
SXSSFWorkbook을 사용하면 메모리에 모든 데이터를 올리지 않고, 디스크 기반으로 엑셀을 처리할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 메모리에 100개 행만 유지
Sheet sheet = workbook.createSheet("Sheet1");
for (int i = 0; i < 1000000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("Data " + i);
}
FileOutputStream out = new FileOutputStream("large_excel.xlsx");
workbook.write(out);
out.close();
workbook.dispose(); // 사용한 메모리 정리
Python - openpyxl 대신 Pandas + xlsxwriter 사용
1
2
3
4
import pandas as pd
df = pd.DataFrame({"A": range(1, 1000000), "B": range(1000000, 2000000)})
df.to_excel("large_file.xlsx", index=False, engine="xlsxwriter")
4. 파일을 미리 생성 후 다운로드 (Pre-generated File)
비동기 작업으로 미리 생성한 파일을 Redis나 S3에 저장하고 다운로드할 수 있도록 하면 빠른 응답이 가능하다.
Java - AWS S3에 업로드 후 다운로드 링크 제공
1
2
String presignedUrl = s3Client.generatePresignedUrl(request).toString();
return ResponseEntity.ok(presignedUrl);
Python - S3 Pre-signed URL 생성
1
2
3
4
5
6
7
import boto3
s3 = boto3.client('s3')
url = s3.generate_presigned_url('get_object',
Params={'Bucket': 'my-bucket', 'Key': 'large_excel.xlsx'},
ExpiresIn=3600)
print(url) # 다운로드 URL 반환
최적화 방법 정리
| 방법 | 설명 | 적용 기술 | |——|——|———-| | 비동기 처리 | 백그라운드에서 엑셀 생성 후 다운로드 링크 제공 | Spring @Async, Go goroutine, Python Celery | | 스트리밍 다운로드 | 파일 전체를 메모리에 로드하지 않고 스트리밍 | Java StreamingResponseBody, Go io.Copy(), Python send_file() | | 엑셀 최적화 | 메모리 사용량 최소화 | Java SXSSFWorkbook, Python xlsxwriter | | 사전 생성 후 다운로드 | S3, Redis에 저장 후 URL 제공 | AWS S3, Redis, Nginx Static File |
이 중에서 가장 효율적인 방법은 비동기 처리 + 스트리밍 다운로드 + 파일 최적화를 조합하는 것입니다. 실제로는 프로젝트 환경에 맞게 골라 적용합니다.
참고한 구현 사례
우아한형제들 기술블로그의 엑셀 다운로드 모듈 글은 SXSSFWorkbook으로 엑셀을 만들면서, 컬럼마다 getter를 호출해 셀을 하나씩 만드는 반복을 어노테이션과 리플렉션으로 없애는 과정을 다룬다. Apache POI 문서에 따르면 SXSSF는 메모리에 일정 개수의 행(기본 100개)만 슬라이딩 윈도우로 유지하고, 윈도우를 벗어난 오래된 행은 디스크에 써서 더 이상 접근할 수 없게 한다. 이때 임시 파일이 생기므로 dispose()를 반드시 호출해 정리해야 한다.
@Async, Virtual Thread, CompletableFuture
Spring 가이드에서 @Async를 붙인 메서드는 별도 스레드에서 실행되고, 반환 타입을 CompletableFuture<T>로 두면 호출한 쪽이 결과를 나중에 받는다. 이 동작은 설정 클래스에 @EnableAsync를 붙여야 켜진다. 어떤 스레드에서 실행할지는 TaskExecutor가 정하는데, Spring 문서는 가장 흔히 쓰는 구현으로 ThreadPoolTaskExecutor를 꼽고, SimpleAsyncTaskExecutor는 스레드를 재사용하지 않고 호출마다 새 스레드를 만든다고 설명한다.
Virtual Thread는 JEP 425로 Java 19에서 프리뷰로 들어왔다. Spring 블로그는 Virtual Thread가 가장 효과를 보는 곳을 데이터 저장소, 메시지 브로커, 원격 서비스와 통신하는 I/O 중심 애플리케이션으로 보고, 동시성을 높이려면 커넥션 풀의 여유 커넥션, 메모리, CPU 시간 같은 자원이 함께 있어야 한다고 짚는다. 같은 글은 호출마다 플랫폼 스레드를 만드는 SimpleAsyncTaskExecutor가 Virtual Thread를 쓰도록 바뀔 수 있다고 언급한다.
MySQL Gap Lock
MySQL 문서에 따르면 갭 락은 인덱스 레코드 사이의 간격, 또는 첫 레코드 앞이나 마지막 레코드 뒤의 간격에 거는 락이다. InnoDB의 갭 락은 다른 트랜잭션이 그 간격에 행을 삽입하지 못하게 막는 것만이 목적이라서, 여러 트랜잭션이 같은 간격에 갭 락을 함께 가질 수 있고 공유 갭 락과 배타 갭 락 사이에 차이가 없다.
참고한 자료외부 출처 29
외부 출처
- 아 엑셀다운로드 개발,,, 쉽고 빠르게 하고 싶다 (feat. 엑셀 다운로드 모듈 개발기)
- Solving Slow Excel Generation using Apache POITechie Shares
- Spring Boot Export Data to Excel Example
- Hibernate Bulk loading from excel: suggestions for design or approach for better performance
- Boost JPA Bulk Insert Performance by 90%
- Apache POI Java Excel Performance for Large Spreadsheets
- Generate Excel With POI With Most Optimized Way
- Working with Microsoft Excel in Java
- Spring Batch, Excel ItemReader로 읽어서 JPA ItemWriter 쓰기bigdown
- Reading and Writing Excel Data in Spring Boot with Apache POI
- Spring Boot Generate Excel with React fetch blob.
- How To Read Excel File In Java Spring Boot And Upload Into Database?
- How to Generate an Excel Report in a Spring Boot REST API with Apache POI and Kotlin
- The New Halloween DocumentSXSSF
- 자바의 Virtual Thread가 나와도 코틀린의 코루틴은 여전히 살아남을까?오늘도 끄적끄적
- Java Virtual Threads 훑어보기오늘도 끄적끄적
- Java Virtual Thread (10), @async 어노테이션hyokeun0419
- Creating Asynchronous Methods
- How To Do @Async in Spring
- [Spring] Task 실행과 스케줄링 ( TaskExecutor , TaskScheduler)
- [Spring] ThreadPoolTaskExecutor 설정으로 비동기 처리 효과적으로 하기슬기로운 개발생활
- Task Execution and Scheduling
- A Guide to the Java ExecutorService
- Embracing Virtual Threads
- Using Java Virtuals Threads for Asynchronous Programming
- The Future of Async in Java: CompletableFuture vs Virtual ThreadsJava Code Geeks
- Java의 미래, Virtual Thread
- MySQL Gap Lock 다시보기
- InnoDB Locking
댓글
아직 댓글이 없습니다