정의
사용자가 올린 큰 파일(이미지·동영상 등)을 앱 서버를 거치지 않고 클라이언트가 저장소로 곧장 올리게 하고 싶을 때 쓰는 방식이다. 서버는 "이 주소로 올려라"라는 특별한 URL만 발급하고, 실제 파일 전송은 브라우저가 저장소와 직접 한다.
용어부터 풀면: 오브젝트 스토리지(S3 등)는 파일을 통째로 하나의 객체(object)로 저장하는 클라우드 저장소, 바이너리는 이미지·동영상처럼 텍스트가 아닌 파일 원본 데이터를 뜻한다.
정확히 말하면 Presigned URL은 오브젝트 스토리지의 소유자인 백엔드가 자신의 자격증명으로 서명해(위조 못 하게 암호 서명을 붙여) 발급하는 시간 제한·권한 제한 일회용 URL이다. 이 URL을 받은 클라이언트는 별도의 인증(로그인 토큰 등) 없이, 서명에 박힌 범위(어떤 메서드·어떤 경로·언제까지) 안에서만 스토리지에 직접 요청을 보낼 수 있다. 즉 "누가 무엇을 언제까지 할 수 있는지"를 백엔드가 미리 결정해 URL 한 줄에 담은 위임 토큰이다.
왜 필요한가
큰 파일을 애플리케이션 서버를 거쳐 업로드하면, 서버가 업로드 트래픽 전체를 중계하느라 메모리·대역폭·요청 시간을 소모한다. 요청 본문 크기나 실행 시간이 제한된 환경(serverless-stateless-execution)에서는 이 방식 자체가 한계에 부딪힌다. presigned URL은 권한 발급(가벼운 메타데이터 요청) 과 실제 전송(클라이언트↔스토리지 직접) 을 분리한다. 서버는 작은 요청만 처리하고, 무거운 바이너리는 스토리지가 직접 받는다.
동작 원리
표준 흐름은 네 단계의 계약으로 정리된다.
| 단계 | 호출 | 역할 | 빠지면 |
|---|---|---|---|
| ① presign | POST /files → { fileId, uploadUrl, fileUrl } | 백엔드가 서명된 업로드 주소 발급 | 업로드 시작 불가 |
| ② PUT | PUT uploadUrl (body = 바이너리) | 클라이언트 → 스토리지 직접 전송 | 파일이 스토리지에 없음 |
| ③ complete | PATCH /files/{fileId} | 백엔드에 "업로드 끝남" 통지 | "파일 미업로드" 오류 |
| ④ create | POST /resources (fileId 참조) | 업로드된 파일로 리소스 생성 | 리소스 생성 실패 |
핵심은 ②와 ③의 분리다. 스토리지는 PUT 성공을 백엔드에 자동으로 알려주지 않는다. 그래서 백엔드가 "이 파일은 이제 사용 가능"이라고 인지하려면 ③ complete 통지가 별도로 필요하다.
실무 적용
목(mock) 환경에서 ② PUT을 건너뛰고 싶다면, 전역 목 플래그가 아니라 uploadUrl이 더미 주소인지(예: example.com 포함) 로 판단해야 한다. 그래야 실제 백엔드 presign URL에는 항상 PUT이 실행된다.
트레이드오프
- 장점: 앱 서버 부하·대역폭 절감, 서버리스 본문 크기 제한 회피, 대용량/다수 업로드에 유리.
- 비용: 단계가 늘어 클라이언트 로직이 복잡해지고, 만료·content-type 등 서명 조건을 클라이언트가 정확히 맞춰야 한다. 업로드 진행률·재시도·부분 실패 처리도 클라이언트 책임이 된다.
사용하면 안 되는 경우
- 파일이 아주 작고 메타데이터와 함께 한 번에 보내는 편이 단순할 때(단계 분리 이득이 적음).
- 서버가 업로드 도중 콘텐츠를 검증·변환해야 해서 바이너리가 반드시 서버를 거쳐야 할 때.
흔한 실수
- ③ complete 누락: ② 없이 또는 ③ 없이 ④를 호출해 "파일이 아직 업로드되지 않았다"는 오류를 만난다.
uploadUrl과fileUrl혼동: 쓰기 주소(uploadUrl)로 PUT 해야 하는데 읽기 주소(fileUrl)로 보낸다.- content-type 불일치: 서명에 포함된 조건과 PUT 헤더가 달라 서명 검증에 실패한다.
- 만료 무시: 발급 후 너무 늦게 PUT 해 URL이 만료된다.
관련 개념
- serverless-stateless-execution — 서버를 거치지 않는 직접 업로드가 무상태·단명 함수 환경에 적합한 이유.