참고) https://techblog.woowahan.com/11392/
dependencies {
...
implementation("com.amazonaws:aws-java-sdk-s3:1.12.174")}
AWS SDK에서 제공하는 S3 업로드 인터페이스를 빈으로 등록
@Configuration
class S3Config(
@Value("\${aws.s3.accessKey}")
private val accessKey: String,
@Value("\${aws.s3.secretKey}")
private val secretKey: String,
) {
@Bean
fun amazonS3Client(): AmazonS3 {
return AmazonS3ClientBuilder.standard()
.withCredentials(
AWSStaticCredentialsProvider(BasicAWSCredentials(accessKey, secretKey))
)
.withRegion(Regions.AP_NORTHEAST_2)
.build()
}
}
사용되는 모든 AWS S3Client는 위에 정의된 빈으로 사용된다.
HttpServletRequest의 InputStream을 이용하여 AWS S3에 다이렉트로 파일을 전송하는 방식
- 모든 바이너리를 메모리에 로드하지 않는 이상 이미지 리사이징과 같은 전처리가 불가능하다.
- 업로드할 파일의 바이너리 전체를 Springboot을 실행하고 있는 서버의 디스크나 힙 메모리에 저장하지 않는다는 점이다.
- 1회 API 호출에 1개의 파일만을 업로드할 수 있다.
- 클라이언트 네트워크 환경, 클라우드 인스턴스 유형에 따라 업로드 속도 편차가 크기 때문에 운영환경과 동일한 인프라로 충분한 속도 테스트가 필요하다.
- 구조적으로 클라이언트에게 업로드 현황을 제공할 수 없다.
따라서 클라이언트가 기다릴 수 있을만큼 적당한 파일사이즈 제한이 필요하다.- 대용량 파일을 업로드 중 오류가 발생하였을 때 전체 파일을 처음부터 다시 업로드해야하기 때문에 시간과 대역폭이 낭비될 수 있다.
- 메모리 사용량은 거의 없었다.
- 937MB 파일을 업로드하는데 16분정도 소요
Stream 업로드 방식은 서버의 리소스가 한정적이고 작은 용량의 파일을 업로드할 때 효과적
만약 프로덕트에서 Stream 업로드 방식을 채택한다면, 위에서 이야기한 것처럼 운영 환경과 동일한 인프라로 충분한 업로드 속도 테스트가 필요하다.
Spring에서 제공하는 MultipartFile Interface를 이용하여 파일을 업로드하는 방식
spring:
servlet:
multipart:
enabled: true # 멀티파트 업로드 지원여부 (default: true)
file-size-threshold: 0B # 파일을 디스크에 저장하지 않고 메모리에 저장하는 최소 크기 (default: 0B)
location: /users/charming/temp # 업로드된 파일이 임시로 저장되는 디스크 위치 (default: WAS가 결정)
max-file-size: 100MB # 한개 파일의 최대 사이즈 (default: 1MB)
max-request-size: 100MB # 한개 요청의 최대 사이즈 (default: 10MB)
클라이언트가 파일을 업로드했을 때 WAS(Tomcat)가 해당 파일을 임시 디렉터리에 저장한다.
여기서 임시 디렉터리에 저장된 파일은 힙 메모리가 아닌 Servlet Container Disk에 저장
요청 처리가 끝나면 임시 저장된 파일이 삭제
업로드된 파일의 크기가 file-size-threshold 값 이하라면 WAS가 임시파일을 생성하지 않고 파일 바이너리를 메모리에 다이렉트로 할당
파일 처리 속도는 더 빠르겠지만, 스레드가 작업을 수행하는 동안 부담이 될 수 있기 때문에 충분한 검토가 필요하다.
Stream 업로드 및 MultipartFile 업로드 방식을 사용할 때 다수의 사용자로부터 동시에 요청이 들어올 경우, 서버의 스레드가 빠르게 소진될 위험이 있다. 이에 따라 스레드 풀 설정이 적절하지 않으면 스레드 고갈로 인해 타임아웃이 발생할 위험이 있다.
이러한 문제를 대비해 resilience4j의 bulkhead 패턴을 활용하여 동시에 처리될 수 있는 작업의 최대 수를 제한하고, 별도의 스레드 풀을 사용함으로써 다른 비즈니스 로직에 영향을 주지 않도록 방지할 수 있습니다. 또한 파일 업로드를 전담하는 서버를 별도로 분리하는 전략도 고려해볼 수 있습니다.
AWS S3에서 제공하는 파일 업로드 방식
업로드할 파일을 작은 part로 나누어 각 부분을 개별적으로 업로드한다.
파일의 바이너리가 Springboot를 거치지 않고 AWS S3에 다이렉트로 업로드되기 때문에 서버의 부하를 고려하지 않아도 된다는 큰 장점이 있다.
만약 모든 part가 업로드 되었을 경우 AWS에서 하나의 객체로 조립하여 저장한다.
몇 개의 파트가 업로드되었는지 확인하여 위와 같이 사용자에게 업로드 진행사항을 제공할 수 있다.
멀티파트 업로드 시작(initiate-upload)을 요청하면 서버는 멀티파트 업로드에 대한 고유 식별자인 Upload ID를 응답합니다. 부분 업로드, 업로드 완료 또는 업로드 중단 요청 시 항상 Upload ID를 포함해야 하기 때문에 클라이언트는 이 값을 잘 저장해야 합니다.