Spring 대용량(tiff, jpg) 압축파일 업로드

박성현·2025년 8월 18일

개발중 학습

목록 보기
17/44

Spring에서 대용량 파일 업로드 중 ERR_CONNECTION_RESET 문제 해결하기

최근 프로젝트에서 드론 이미지 같은 대용량 ZIP 파일을 업로드하다가 브라우저에서 갑자기 ERR_CONNECTION_RESET 에러가 뜨는 상황이 발생했는데
처음엔 이유를 잘 몰라서 삽질을 좀 했는데, 정리해보니 해결 흐름이 명확하더라고요. 여기서 배운 내용을 기록합니다.


문제 상황

  • Spring 웹 애플리케이션에서 여러 장의 이미지를 한 번에 업로드
  • ZIP 파일 업로드 시 브라우저에서 연결이 끊기면서 ERR_CONNECTION_RESET 발생
  • 파일 일부만 올라가거나 아예 업로드가 안 되는 경우가 많았음

처음에는 파일 이름이나 DTO 처리 문제인 줄 알았지만, 알고 보니 서버가 한 번에 처리할 수 있는 용량을 초과해서 연결을 끊는 것이 원인이었어요.


문제 분석

  1. Multipart 설정 부족

    • 기본 Spring MultipartResolver는 큰 파일 처리에 한계가 있음
    • maxUploadSizemaxInMemorySize를 넘으면 서버가 연결을 끊음
  2. 메모리 과부하

    • ZIP 안의 이미지를 한 번에 메모리에 올리면 JVM 힙이나 OS Temp 영역이 부담됨
  3. DB 처리 방식

    • 개별 insert로 처리하면 DB 왕복이 많아져 시간이 길어지고, 그 사이 클라이언트 연결이 끊김

해결 방법

1. Multipart 설정 변경

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10737418240"/> <!-- 10GB -->
    <property name="maxInMemorySize" value="20971520"/>   <!-- 20MB -->
</bean>
  • 업로드 최대 용량을 늘려서 큰 ZIP 파일도 받도록 함
  • 메모리 임계값을 낮춰서 일정 이상은 디스크 임시 파일로 처리하게 함

2. ZIP 스트리밍 처리

try (ZipInputStream zis = new ZipInputStream(file.getInputStream())) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        if (!entry.isDirectory()) {
            File tempFile = new File(System.getProperty("java.io.tmpdir"), entry.getName());
            Files.copy(zis, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            fileBatchList.add(tempFile);
        }
    }
}
  • ZIP 안의 파일을 한 번에 메모리에 올리지 않고 스트리밍 방식으로 처리
  • 메모리 과부하와 연결 끊김 문제를 크게 줄일 수 있음

3. 배치 단위 DB 저장 + 트랜잭션

public void saveBatch(List<File> files) throws SQLException {
    Connection conn = dataSource.getConnection();
    try {
        conn.setAutoCommit(false);

        String sql = "INSERT INTO images (filepath, filename, filesize) VALUES (?, ?, ?)";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            for (File file : files) {
                pstmt.setString(1, file.getAbsolutePath());
                pstmt.setString(2, file.getName());
                pstmt.setLong(3, file.length());
                pstmt.addBatch();
            }
            pstmt.executeBatch();
        }

        conn.commit();
    } catch (Exception e) {
        conn.rollback();
        throw e;
    } finally {
        conn.close();
    }
}
  • 배치 단위로 DB에 insert → DB 왕복 최소화
  • 트랜잭션 처리 → 중간 오류 시 안전하게 롤백

최종 구조

간단하게 그림으로 표현하면 흐름은 이렇습니다.

클라이언트 → Spring Multipart → 임시 파일 저장 → 배치 수집 → 트랜잭션 시작 → DB 배치 Insert → Commit/롤백 → 완료
  • 배치 사이즈, 임시 저장 위치, 트랜잭션 처리를 적절히 조절하면 대용량 업로드도 안정적
  • ZIP 스트리밍까지 적용하면 메모리 부담도 거의 없음

결론적으로, ERR_CONNECTION_RESET 에러는 서버가 한 번에 처리할 수 있는 용량과 시간 초과 때문에 발생하는 경우가 많습니다.
멀티파트 설정 조정, 스트리밍 처리, 배치 DB insert, 트랜잭션 관리 등 기능적 구조를 정리하면 자연스럽게 해결됩니다.


profile
개발기록장

0개의 댓글