SpotEditor 프로젝트 ‐ AWS S3 Presigned‐Url 방식의 이미지 업로드 구현과 발생했던 트러블 슈팅 정리 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 요구사항 정리

  • 팀 단위 회의와 디자이너 분의 요구사항은 아래와 같았다.
- 🧑 공간을 등록할 때는 이미지를 추가할 수 있었으면 좋겠습니다.
- 🧑 공간에 대한 이미지는 3장 정도가 좋을 것 같습니다.
  • 이미지를 업로드하는 방법을 찾아보았고 아래와 같이 3가지로 축약할 수 있다.
    • Stream 업로드
    • MultipartFile 업로드
    • AWS Multipart 업로드
  • 결론부터 말하자면 내가 채택한 방법은 바로 S3 Presigend-Url을 활용한 이미지 업로드 방법인 AWS Multipart 업로드였다.

[ Stream 업로드 방식 ]

image

  • Stream 업로드는 HttpServletRequest(서블릿)의 InputStream을 이용하여 AWS S3에 다이렉트로 파일을 전송하는 방법이다.
    • 장점
      • 서버 메모리 사용량을 최소화하면서 대용량 파일도 거뜬히 처리할 수 있다.
      • 디스크에 임시 저장하지 않고 S3에 전송한다.
      • 중간 단계가 없어 전송 속도가 빠르다.
    • 단점
      • 중간에 실패할 경우 부분적으로 업로드된 데이터 처리가 필요하다.
      • 파일 관련 부가 정보를 전송하기 어렵다.
      • 하나의 요청에 여러 파일을 포함하기 어렵다. 여러 파일 업로드시 서버 측으로의 별도의 요청이 필요하다.

❗결국 디자이너 분의 요구사항이었던 여러 이미지 업로드에 대한 요건을 충족시킬 수 없어 이 부분을 제외했다.

[ MultipartFile 업로드 방식 ]

image

  • MultipartFile 인터페이스는 Spring에서 제공해주는 인터페이스이다.
    • 장점
      • 여러 파일을 하나의 요청으로 전송 가능하다.(서블릿 기반의 Stream 업로드의 단점 해소)
      • 파일과 함께 다른 폼 데이터(multipart/form-data)도 함께 전송 가능하다.
      • 임시 저장된 파일을 서버 디스크에 저장하기에 업로드 실패시 복구가 용이하다.
      • Spring에서 제공해주는 인터페이스가 있기 때문에 구현이 쉽다.
    • 단점
      • 서버 디스크에 임시 파일 저장 공간을 필요로 하며 대용량 파일 처리시 메모리 사용량이 급증할 수 있다.
      • 임시 파일 저장과 읽기 과정으로 인한 지연이 발생하며 대용량 파일의 경우 처리 시간이 길어질 수 있다.
      • 동시에 많은 파일 업로드 요청이 들어오게 되면 서버에 과부하가 발생한다.
      • 서버 디스크 공간 제약으로 인해 확장에 제한이 있을 수 있다.
  • 이 MultipartFile 인터페이스를 사용하려면 application.yml에서 관련 설정을 추가해주어야 한다.

image

  • 클라이언트가 서버로 업로드 요청을 보낸다.
  • 내장 톰켓 서버(WAS)가 받은 파일을 스트림 데이터 형태로 서버 디스크에 임시 저장 한다.
  • 서버 디스크에 임시 저장된 파일을 스트림 데이터로 읽어서 AWS S3로 최종 업로드한다.

❗개인 프로젝트와는 달리 사용자를 고려하는 상황이었기에 이것 역시 쉽게 도입할 순 없을 것이라고 판단했다.

[ AWS Multipart 업로드 ]

image

  • AWS Multipart Upload는 AWS S3에서 제공해주는 파일 업로드 방식이다. 업로드할 파일을 작은 part로 나누어 각 부분을 개별적으로 업로드한다. 그리고 파일의 바이너리가 Spring Boot를 거치지 않고 S3에 다이렉트로 업로드가 된다는 점에서 서버의 부하를 발생시키지 않는다는 장점이 있다.
  • 실제로 참고한 블로그인 우아한 형제들 블로그의 내용에선 업로드 진행률을 위해서인지 Upload ID를 발급받는 프로세스가 따로 있었다. 하지만 우리 프로젝트에서 그런 요구사항은 따로 없었기에 프로세스를 아래와 같이 구성했다.

1. 클라이언트가 서버 측에 Presigned-Url 발급을 요청한다.

2. 서버 측에서 클라이언트에 Presigned-Url 응답을 보낸다.

3. 받은 Presigned-Url을 사용해 AWS S3에 이미지를 다이렉트로 업로드한다.

4. S3로부터 업로드 완료 응답을 받는다.

  • 위와 같이 구성을 해서 실제로 구현을 마쳤다.(코드 내용은 프로젝트 리포지토리에서 열람할 수 있습니다.)
  • 하지만 이걸 구현하면서 발생할 수 있는 잠재적 문제점에 대해서 정리해보았더니 조금 많아서 정리해보았다.
- 공간을 등록할 때는 데이터와 함께 이미지도 업로드할 수 있어야 한다.
  - S3 - DB간 데이터 정합성 문제
    - 하지만 예측할 수 없는 오류로 인해 공간이 추가되지 않았는데 이미지가 올라갔다면?
    - 반대로 공간은 추가가 되었는데 이미지가 안 올라갔다면?
  • 이 문제를 해결하기 위해 여러 가지 시도를 해봤으나 해결이 어려웠고 AWS 의존적인 방법이지만 이 방법으로 해결할 수 있었다.
* 임시 저장용 버킷과 메인 버킷을 준비
* 사용자가 파일 업로드를 하면 우선적으로 임시 저장용 버킷에 저장
* 사용자가 공간 추가 API를 호출해 공간을 추가할 경우 임시 저장용 버킷에 있는 객체를 메인 버킷으로 옮기는 처리