안녕하세요!
저번 게시물에 이어서 방끗 기능 개발을 위해 고민을 해 보는 게시물입니다
블로그 글의 초반부는 지난 게시물을 갖고 오는 걸로... 하겠습니다 ☺️
저희 방끗 팀은 11월 수료 이후 간단한 유지 보수를 제외하고는 추가적인 기능 개발을 진행하지 않았어요 홍보와 유저 유치에 집중을 하던 시기였습니다 🎉
그러나! 여러 논의 끝에 이제 새로운 기능을 추가해 보기로 하여 가장 먼저 개발할 두 가지 기능을 픽스하였어요
그 기능 중 두 번째가 체크리스트에서 이미지가 추가 가능하도록 하는 부분이에요
두 가지 기능 중 어느 기능을, 누가 담당할지에 대한 논의가 이루어지기 전에 먼저 설계를 해 보고 싶어 위 글을 작성하게 되었어요 👍

기존에는 위와 같이 유저가 이미지를 업로드할 수는 없었어요

저희는 4차례 이상의 데모데이로 직접 유저에게 피드백을 듣는 시간을 가졌고, 이후에도 꾸준히 VoC를 통해 유저의 이야기를 들어왔어요
그 결과 가장 많이 나온 의견 중 하나가 "집의 이미지를 업로드 할 수 있게 해 주세요" 였답니다
해당 피드백은 정말 처음부터 꾸준히 나온 의견인데요,
방끗 팀 논의 당시에는 정해진 시간 안에 배포를 해야 했기 때문에 이미지를 업로드한다는 다소 시간이 많이 소요되는 기능은 잠시 미루어두었습니다
하지만 새로운 기능을 추가하기로 한 이상! 해당 부분에 도전해 보려고 합니다
저는 이미지 업로드 기능을 구현해 본 적이 없기 때문에
어떤 플로우로 동작하는지 간단히 알아 보아야 했어요
1) AWS S3 버킷을 생성한다
2) 이미지 업로드 API를 설계한다
이때 서버 업로드 방식과 클라이언트 업로드 방식을 선택해야 한다
3) S3에 업로드된 URL을 DB에 저장한다
등이 있다고 하여 하나씩 고민해 보겠습니다

저희는 현재 프리 티어 계정을 사용하고 있고 5GB, GET 요청 2만건, 2000건의 타 요청 내에서는 무료로 사용 가능하기 때문에 보통은 무료 금액 내에서 처리가 가능할 것이라 판단했어요

혹시나 해당 요청을 넘어서더라도 금액이 크지 않아서 저희 팀 내에서 부담할 수 있는 금액이었습니다!
이미지를 저장할 때는 클라이언트에서 서버를 거쳐 S3에서 업로드하는 방식,
혹은 클라이언트에서 직접 S3로 업로드하는 방식이 있습니다
각각의 장단점을 살펴볼게요!
✅ 장점
보안 관리가 용이: 서버에서 업로드 요청을 검증할 수 있어, 악성 파일이나 인증되지 않은 요청을 차단하기 좋음
일관된 권한 제어: 클라이언트가 직접 S3에 접근하지 않으므로, S3의 버킷 정책을 복잡하게 설정할 필요 없음
서버에서 추가적인 처리 가능: 이미지 리사이징, 워터마킹, 메타데이터 추가 등의 작업을 서버에서 처리 가능
로그 관리 및 감사 가능: 업로드 과정이 서버를 거치므로 로그를 남겨 추적 및 모니터링이 쉬움
❌ 단점
서버 부하 증가: 대량의 파일 업로드가 발생하면 서버의 네트워크 및 CPU 부하가 커질 수 있음
비용 증가: 클라이언트와 서버 간의 트래픽 + 서버와 S3 간의 트래픽이 발생하여 네트워크 비용 증가
지연 시간 증가: 서버를 한 번 거치기 때문에 업로드 속도가 느려질 수 있음
✅ 장점
서버 부하 감소: 서버가 파일을 직접 처리하지 않으므로, 트래픽과 리소스 사용이 줄어듦
빠른 업로드 속도: 클라이언트가 직접 S3로 올리므로 네트워크 홉이 줄어들어 성능이 개선됨
비용 절감: 서버를 거치지 않으므로 네트워크 비용을 절감할 수 있음
❌ 단점
보안 관리 필요: 클라이언트가 S3에 접근할 수 있어야 하므로, Presigned URL을 안전하게 관리해야 함
권한 관리 복잡: Presigned URL의 유효 기간과 버킷 정책을 신경 써야 함
추가적인 후처리 어려움: 서버에서 업로드를 통제하지 않으므로, 업로드된 파일을 가공하려면 별도 Lambda 같은 후처리 과정이 필요할 수 있음
클라이언트 복잡성 증가: Presigned URL을 받아서 파일을 업로드하는 로직을 구현해야 하므로 클라이언트 개발이 복잡해질 수 있음

사실 결론을 내기 어려워 Velog는 어떻게 하는지 직접 파일을 붙여넣고 요청을 살펴 보았습니다
위와 같이 요청이 서버로 날아가는 모습입니다

깃허브도 마찬가지로 서버에 요청이 날아가는 모습입니다
해당 개발자들은 왜 저런 선택을 하였을까? 나는 어떤 선택을 해야 할까?
고민 끝에 저는 서버 업로드 방식을 택하기로 하였어요!
사유는 아래와 같습니다
클라이언트에서 Presigned URL 방식으로 직접 S3에 접근한다면,
서버에 해당 URL 발급 요청을 보내고 응답을 받아야 합니다
그 과정에서 해당 URL이 탈취될 여지가 있습니다
클라이언트에서 리사이징 등을 처리하려면 AWS Lambda를 사용해야 합니다
하지만 해당 기능은 사용한 만큼 비용을 지불해야 하기 때문에 저희의 예산상 사용하기 어렵습니다
(추가) 리사이징 등 변환을 프론트에서 주면 해당 문제는 사라지는 것 아닐까요?
우아한 기술 블로그를 참고했어요!
| 방식 | 메모리 사용량 | 네트워크 효율 | 속도 | 대용량 파일 지원 | 구현 난이도 |
|---|---|---|---|---|---|
| Stream 업로드 | 낮음 | 높음 | 보통 | 비효율적 | 쉬움 |
| MultipartFile 업로드 | 높음 | 보통 | 보통 | 비효율적 | 매우 쉬움 |
| AWS Multipart 업로드 | 낮음 | 높음 | 빠름 | 매우 효율적 | 어려움 |
해당 세 가지 방안 중 저는 전처리를 해야 하고, 대용량은 아니기 때문에 MultipartFile 업로드 방식을 택하기로 하였어요
만약 사용자가 너무 고화질의, 용량이 큰 이미지를 올릴 경우에는 성능상 문제가 발생합니다
따라서 서버 측에서 리사이징 등의 최적화를 진행해 주어야 해요
아래로 가능한 이미지 최적화 방안을 고민해 봤어요
| 방식 | 주요 특징 | 장점 | 단점 | 추천 사용 사례 |
|---|---|---|---|---|
| 썸네일 생성 및 리사이징 | 크기 조정 & 용량 줄이기 | 빠르고 간단, 유지보수 쉬움 | 품질 저하 가능성, 고정 크기 문제 | 프로필 이미지, 미리보기 이미지 |
| WebP/AVIF 변환 | 차세대 포맷 변환 | 파일 크기 감소, 웹 최적화 | 일부 브라우저 미지원, 변환 속도 느림 | 웹사이트 이미지, 모바일 앱 |
| 이미지 압축 (Lossy/Lossless) | 품질 유지하며 용량 감소 | S3 비용 절감, 네트워크 최적화 | 화질 저하 가능, API 비용 발생 가능 | 사진 갤러리, 사용자 업로드 이미지 |
서치해 보니 우테코 6기 동료들이 발표한 테코톡이 있었어요
위 자료를 참고해서 리사이징과 파일 변환을 하기로 결정하였어요
이미지 압축은 불필요하다 판단하였는데요,
무손실 압축의 경우에는 압축률이 낮으며 손실 압축의 경우 화질 저하가 발생할 수도 있기 때문에
리사이징과 파일 변환까지로 충분히 이미지 최적화가 적당하다 생각했습니다
저희 구조에서는 이미지를 업로드할 때마다 업로드 요청을 보내서 링크를 만들어 반환하는 형식이에요
해당 부분에서는 폴라의 블로그를 참고할 수 있었어요
사실 뒤로가기 할 때 삭제하는 것이 가장 간편한 구현 방식이지만 뒤로가기가 아니라 강제로 웹을 꺼버리는 경우에는 인식하기 어려워 결국 사용하지 않는 파일이 생기게 됩니다
따라서 1차 업로드 폴더와 최종 사용 폴더를 분리하고 1차 업로드 폴더는 주기적으로 삭제하는 방식을 선택해 보았어요
이 정도로 고민을 끝낼 수 있을 것 같아요!
사이즈나 사진 개수 정책은 팀에서 논의 후 결정할 듯합니다
혹시 조언 주실 부분이나 논의 주실 사항은 언제나 환영이에요 🙇♂️