Post

2025-02-03-TIL

2024-02-03-TIL

Today I Learned

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 |

이 중에서 가장 효율적인 방법은 비동기 처리 + 스트리밍 다운로드 + 파일 최적화를 조합하는 것입니다. 프로젝트 환경에 맞게 적용하면 좋을 것 같아요! 🚀

References

@Async, Virtual Thread, and Completable Future

MySQL Gap Lock

This post is licensed under CC BY 4.0 by the author.