본문 바로가기
👾 Server/☁️AWS

S3에 이미지 업로드 기능 아키텍처 비교 (서버에 직접 전송, presigned URL 활용)

by kukim 2022. 9. 4.

잘못된 내용이나 의견 있다면 편하게 말씀해주세요.🙏🏻

 

사이드 프로젝트를 하며 유저 프로필이나 음식점 리뷰 사진 등 이미지 업로드 기능이 필요했습니다.

처음엔 이미지를 업로드 기능을 단순하다고 느꼈지만 생각보다 고려할 것이 많았습니다.😵‍💫

 

떠올랐던 Check List

- 이미지 업로드/삭제 , 원본 파일 크기 제한(저장 공간 제약, 비정상적인 요청 방지)

- UI 마다 이미지 사이즈를 다르게 저장(리사이징)

- 빠른 응답을 위한 CDN 사용 

- 파일 접근 권한 (비공개 이미지에 익명의 사용자가 접근하는 문제)

 

이번 글에서는 이미지 업로드 기능을 구현하는 간단한 세 가지 방법(아키텍처)을 소개/비교하고자 합니다.

이미지 업로드

유저가 자신의 프로필 사진을 수정한다고 가정합니다.

이미지는 클라우드 스토리지(AWS S3)에 저장합니다.

유저 정보는 RDB에 저장하고 있으며,  이미지는 URI 문자열로 저장합니다.

이미지 파일 전송 방식은 multipart 기준으로 작성되었습니다.


1. 서버에 이미지 파일 전송하기

클라이언트가 multipart로 이미지 파일을 서버에 직접 전송하는 방법은 다음과 같은 장단점이 존재합니다.

 

장점

- 최초 이미지 업로드 시 서버에서 곧바로 이미지 파일을 관리할 수 있다. (업로드 파일 크기 제한, 이미지 파일 조작(워터마크),...)

단점

- 서버에 직접적인 부담(연산, 메모리 소모 등)이 간다.

- 네트워크 대역폭 소모가 크다. (만약 서버 앞에 load balancer들이 존재한다면 이미지 파일이 모든 대역폭을 거쳐야 한다.)

 

좀 더 자세히 서버에 직접 이미지 파일을 전송하는 두 가지 방법(아키텍처)을 살펴보겠습니다.

1.1. case 1 : API 한 번만 사용 (프로필 수정 API)

한 번에 이미지 업로드하는 API

1. 클라이언트는 서버에게 multipart image 파일이 포함되어있는 프로필 수정 API를 요청합니다

2. 서버는 클라이언트로부터 받은 multipart image를 검증하고 AWS SDK를 활용하여 S3에 업로드합니다.

3. S3는 이미지를 저장하고 URI 문자열을 서버로 반환합니다.

4. 서버는 S3로부터 받은 URI 문자열을 가지고 유저 테이블의 이미지 컬럼을 업데이트합니다.

5. 서버는 정상적으로 업데이트되었다고 클라이언트에게 응답합니다.

 

위 방식을 사용하면 클라이언트 입장에서 한 번의 API 요청으로 이미지를 업로드할 수 있습니다. 하지만 음식점 리뷰 이미지도 올려야 한다면 서버 입장에서 프로필 수정 API와 음식점 리뷰 API에 AWS S3에 이미지 업로드하는 로직이 중복으로 들어갑니다. 

1.2. case 2 : API 두 번 사용 (이미지 업로드 API -> 프로필 수정 API)

이미지 업로드 API 서버와 프로필 수정 API 서버 분리

1. 클라이언트는 이미지 업로드 전용 서버에게 multipart image 파일을 포함하여 이미지 생성 API를 요청합니다.

2. 서버는 클라이언트로부터 받은 multipart image를 AWS SDK를 활용하여 S3에 업로드합니다.

3. S3는 이미지를 저장하고 URI 문자열을 서버로 반환합니다.

4. 서버는 S3로부터 받은 이미지 URI 문자열을 클라이언트에 응답합니다.

5. 클라이언트는 URI 문자열을 가지고 유저 프로필 업데이트 API 요청을 합니다.

6. 서버는 클라이언트부터 받은 URI 문자열을 가지고 유저 테이블의 이미지 컬럼을 업데이트합니다.

7. 서버는 정상적으로 업데이트되었다고 클라이언트에게 응답합니다.

 

case 1과 다르게 이미지 업로드 전용 API와 프로필 수정 API를 별도로 구분하였습니다. 분리하였으니 API 별로 서버를 구분하여 사용할 수 있습니다. 예를 들어 이미지 업로드 API 전용 서버는 node나 spring, 서버리스(AWS Lambda)를 사용할 수 있고 프로필 수정 API는 스프링을 사용할 수 있습니다. 스프링 서버는 프로필 수정 API를 관리하는 서버는 Multipart 이미지를 처리하지 않아도 됩니다. 클라이언트가 요청한 String으로 된 URI에 대한 유효성 체크하고 DB에 저장하면 됩니다.

단, case 2와 같은 방식으로 구현하게 된다면 클라이언트에서 API를 두 번 사용해야 하고 만약 이미지 업로드 API 사용한 뒤 프로필 수정 API를 사용하지 않는 경우(연결이 끊긴 경우) 해당 이미지는 이미 업로드가 되었지만 아무도 사용하지 않는 공백(?) 문제가 발생하기 때문에 클라이언트에서 별도의 처리가 필요합니다.


2. 클라우드 스토리지에 직접 이미지 파일 전송하기(presigned URL 활용)

클라이언트의 이미지 파일을 서버를 거치지 않고 클라우드 스토리지(S3)에 곧바로 이미지 파일을 전송할 수 있습니다.

 

장점

- 이미지 업로드를 위해 서버 네트워크 대역망을 사용하지 않아도 된다.

 

단점

- 클라이언트에서 사용할 API가 증가한다.

- 클라이언트가 S3에 직접 업로드할 수 있는 보안 자격 증명이 필요하기 때문에 별도의 관리가 필요하다.

- 별도의 이미지 처리가 필요하다면 원본 이미지 업로드 후 처리해야 한다.

 

AWS S3 서비스는 보안 자격 증명 문제를 해결하는 미리 서명된 URL(presigned URL)이란 기능을 제공합니다.

미리 서명된 URL(presigned URL) : URL에 액세스 권한을 부여하여 해당 URL만 가지고 특정 파일에 대해 조회/생성/수정/삭제를 할 수 있다. (비유하자면 시간제한 자유이용권)

미리 서명된 URL는 일정 기간 동안만 유효하게 설정하거나 특정 파일 확장자만 업로드 가능, 파일 크기 제한 등 다양한 기능 옵션으로 생성할 수 있습니다. 미리 서명된 URL을 사용하면 클라이언트가 직접 S3에 업로드할 수 있고 관리하기 용이할 수 있습니다. (AWS docs)

 

클라우드 스토리지에 이미지 파일을 전송하는 방법(아키텍처)은 아래와 같습니다.

presigned URL 사용하여 클라이언트가 S3에 직접 이미지 업로드

1. 클라이언트는 서버에게 presigned URL을 얻는 API를 요청합니다

2. presigned URL 생성하는 Server는 S3 Access를 가지고 S3에 presigned URL을 요청합니다.

3. S3는 presigned URL을 서버에게 응답합니다.

4. presigned URL 생성하는 Server는 클라이언트에게 전송합니다. 

5. 클라이언트는 presigned URL으로 이미지를 업로드합니다.

6. S3는 클라이언트에게 생성한 이미지 URI 문자열을 응답합니다.

7. 클라이언트는 생성된 이미지를 가지고 유저 프로필 업데이트를 요청합니다

8. 서버는 유저 테이블의 이미지를 컬럼을 업데이트합니다.

9. 서버는 클라이언트에게 정상적으로 업데이트했다고 응답합니다.


마무리

클라우드 스토리지(S3)에 이미지 업로드하는 방법(아키텍처)들에 대해 알아보았습니다.

서버에 직접 이미지를 업로드하면 직접 관리할 수 있는 장점이 있지만 관리하는 것 자체가 단점이 될 수 있습니다.

presigned URL 방식을 사용하면 원본 이미지 업로드 시 서버에 부하가 없습니다. 하지만 클라이언트 입장에서 코드가 복잡해지고 presigned URL 생성에 엄격한 기준이 필요합니다. +a) 22.09.04 기준 AWS SDK Java는 파일 업로드 presigned URL을 생성할 때 파일 크기 제한 기능(POST presigned URL)을 지원하지 않습니다. (link)

 

다음 글은 이미지 리사이징에 아키텍처와 Spring에서 presigned URL을 생성하고 TestContainers와 logstack을 사용하여 테스트하는 방법에 대해 작성해보려 합니다.


+a) 22.09.07 : 2. presigned URL 아키텍처의 문제점과 고민

presigned URL를 간단히 구현한 뒤 문제를 발견했습니다. 

- 이미지 메타 데이터 관리하지 않는다. (누가 이미지를 생성했고, 소유권은 누구에 있는지, 만약 유저가 탈퇴한다면, 유저가 작성한 이미지 삭제하는 로직 구현 제약) 

- 이미지 저장 url이 origin image이기 때문에 글의 공개 여부에 따라 이미지도 접근할 수 없도록 지정하기 어렵다.

 

이미지 스토리지는 쉽게 생각할 수 없는 것 같습니다. 드롭박스구글 드라이브 아키텍처를 보며 개선시켜봐야겠습니다.

드롭박스 아키텍처 아이디어 주신 @로치 에게 감사함을 전합니다.


⛓ Reference

- Uploading to Amazon S3 directly from a web or mobile application

 - https://aws.amazon.com/ko/blogs/compute/uploading-to-amazon-s3-directly-from-a-web-or-mobile-application/

- AWS POST presigned URL

  - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html

- AWS SDK java POST URL 생성 기능 아직 지원하지 않음

  - https://github.com/aws/aws-sdk-java-v2/issues/1493

- Working with Amazon S3 presigned URLs in Spring Boot

  - https://dev.to/aws-builders/working-with-amazon-s3-presigned-urls-in-spring-boot-383n

- How We've Scaled Dropbox

  - https://www.youtube.com/watch?v=PE4gwstWhmc&t=1613s&ab_channel=Stanford 

 

댓글