[실전 서버리스 13] presigned 2단계 업로드, 큰 파일 처리

사용자가 큰 이미지나 동영상을 올릴 때, 그 파일을 어떻게 R2에 저장할까. 순진하게 하면 파일을 워커가 받아서 R2에 넘기는데, 이러면 큰 파일이 워커를 통째로 거쳐야 한다. 워커에는 처리 시간과 메모리 한도가 있어서, 큰 파일은 이 방식에서 문제가 된다. 정석은 presigned URL을 이용한 2단계 업로드다. 이번 편은 그 방법을 다룬다.


이건 오브젝트 스토리지 업로드의 표준 패턴인데, 처음엔 왜 이렇게 번거롭게 하나 싶다가도 이유를 알면 납득한다. 워커의 한도를 우회하는 영리한 방법이다.

1. 순진한 방식의 문제

가장 단순하게 생각하면, 사용자가 파일을 워커로 보내고, 워커가 그 파일을 R2에 저장하는 방식이다. 파일이 사용자에서 워커로, 워커에서 R2로 흐른다. 작은 파일은 이래도 괜찮다. 그런데 큰 파일에서 문제가 생긴다.


워커에는 한 요청을 처리하는 시간과 메모리에 한도가 있다. 큰 파일이 워커를 거치면, 그 파일 전체가 워커의 메모리에 올라가고 처리 시간을 잡아먹는다. 수십 메가바이트의 동영상 같은 걸 워커가 통째로 받아 넘기려면, 이 한도에 부딪히거나 비효율적이 된다. 앞서 다룬 CPU 시간도 그만큼 쓴다.


게다가 이건 낭비다. 워커는 파일을 그냥 R2로 넘기는 중개자 역할만 하는데, 그 큰 데이터가 굳이 워커를 거칠 이유가 없다. 사용자에서 R2로 바로 가면 될 걸, 워커가 중간에 끼어 부담만 지는 셈이다. 이 비효율을 없애는 게 2단계 업로드다.


순진한 방식의 문제를 겪어보면 2단계 업로드의 필요성이 절실해진다. 큰 파일을 워커가 통째로 받으려다 한도에 걸리거나 느려지는 걸 보고서야, 왜 파일을 워커 밖으로 빼야 하는지 이해한다. 워커는 가벼운 조율만 하고 무거운 데이터는 직접 흐르게 하는 게 서버리스의 지혜다.


한 가지 더, 2단계 업로드는 사용자 경험에도 좋다. 파일이 워커를 거치지 않고 스토리지로 직접 가니, 업로드가 더 빠르고 안정적이다. 큰 파일이 중간 지점을 안 거치니 전송 경로가 짧아지는 것이다. 워커의 부담을 더는 동시에 사용자에게도 빠른 업로드를 주는, 양쪽에 좋은 방식이다.

2. presigned URL이란

presigned URL은 미리 서명된 임시 업로드 주소다. 워커가 R2에게 이 파일을 여기에 올려도 좋다는 허가가 담긴 특별한 주소를 발급받아 사용자에게 준다. 사용자는 이 주소로 파일을 직접 R2에 올린다. 워커를 거치지 않고 사용자와 R2가 직접 연결되는 것이다.


이 주소가 서명됐다는 게 핵심이다. 아무나 R2에 파일을 올릴 수는 없고, 이 서명된 주소를 가진 사람만 정해진 파일을 정해진 위치에 올릴 수 있다. 그리고 이 주소는 임시라 일정 시간이 지나면 만료된다. 그래서 안전하다. 권한을 짧게, 특정 작업에만 위임하는 방식이다.


워커는 이 주소를 발급하는 짧은 일만 한다. 실제 큰 파일 데이터는 워커를 안 거치고 사용자에서 R2로 바로 간다. 워커는 허가증을 발급하는 문지기 역할만 하고, 무거운 짐 운반은 사용자와 R2가 직접 한다. 워커의 부담이 사라지는 것이다.


presigned URL이 임시이고 서명됐다는 게 보안의 핵심이다. 아무나 아무 데나 못 올리고, 서명된 주소를 가진 사람만 정해진 파일을 정해진 위치에 정해진 시간 안에 올린다. 권한을 짧게, 특정 작업에만 위임하는 이 방식이 직접 업로드를 안전하게 만든다. 무제한 권한을 주는 게 아니라 딱 필요한 만큼만 준다.


조금 더 배경을 주면, 이 2단계 패턴은 오브젝트 스토리지 업로드의 업계 표준이다. R2만이 아니라 다른 오브젝트 스토리지도 같은 방식을 쓴다. 그래서 이 패턴을 익혀두면 어느 스토리지를 쓰든 응용된다. 특정 플랫폼의 기교가 아니라 파일 업로드의 보편적 정석이라, 배워둘 값어치가 크다.

3. 2단계의 흐름

전체 흐름을 정리하면 이렇다. 첫 단계, 사용자가 파일을 올리려 하면 워커에 요청한다. 워커는 R2에게서 presigned URL을 발급받아 사용자에게 돌려준다. 이때 워커가 하는 일은 주소 발급뿐이라 가볍고 빠르다.


두 번째 단계, 사용자는 받은 주소로 파일을 R2에 직접 올린다. 이 큰 파일 전송은 워커를 안 거치고 사용자와 R2 사이에서 일어난다. 업로드가 끝나면 사용자가 워커에 완료됐다고 알린다. 그러면 워커는 그 파일의 주소를 데이터베이스에 기록한다. 파일이 올라왔다는 걸 사이트가 인지하는 것이다.


이 세 번의 상호작용, 즉 주소 요청, 직접 업로드, 완료 통보로 업로드가 완성된다. 워커는 주소를 발급하고 완료를 기록하는 가벼운 일만 두 번 하고, 무거운 파일 전송은 사용자와 R2가 직접 처리한다. 워커의 한도를 우회하면서 안전하게 큰 파일을 올리는 것이다.


2단계의 흐름이 처음엔 복잡해 보여도, 각 단계의 역할이 명확하다. 주소 요청, 직접 업로드, 완료 통보. 워커는 처음과 끝의 가벼운 일만 하고 무거운 전송은 사용자와 R2가 직접 한다. 이 역할 분담을 이해하면 왜 이렇게 나누는지가 자연스럽게 납득된다. 복잡함이 아니라 효율을 위한 구조다.


또 하나, 이 패턴을 익히면 다른 직접 업로드에도 응용할 수 있다. 파일뿐 아니라 대용량 데이터를 클라이언트가 직접 어딘가로 보내야 할 때, 워커가 권한만 발급하고 실제 전송은 직접 하게 하는 이 구조가 두루 쓰인다. 무거운 데이터를 중개자 없이 직접 흐르게 하는 발상이, 서버리스에서 여러 곳에 응용된다.

4. 검증과 보안

2단계 업로드에서도 보안은 챙겨야 한다. presigned URL을 발급할 때 아무 파일이나 아무 크기나 허용하면 안 된다. 허용할 파일 종류와 크기 한도를 정해두고, 그 조건에 맞을 때만 주소를 발급한다. 예를 들어 이미지만, 일정 크기 이하만 허용하는 식이다.


또 업로드된 파일이 정말 올바른지 확인하는 절차도 필요하다. 사용자가 이미지라고 해놓고 다른 걸 올릴 수도 있으니, 완료 통보를 받았을 때 그 파일이 실제로 예상한 종류와 크기인지 검증한다. 검증을 통과한 것만 정식으로 기록한다. 사용자 입력은 늘 의심하고 검증한다는 원칙이 여기서도 적용된다.


그리고 앞서 다룬 파일 처리, 예를 들어 이미지 압축이나 썸네일 생성도 이 흐름에 붙는다. 사용자가 원본을 올리면, 그걸 적절히 변환해 최적화된 형태로 저장한다. 이런 후처리는 워커나 별도의 비동기 작업으로 하는데, 무거운 처리는 뒤에서 다룰 큐로 넘기는 게 좋다.


업로드에서 검증과 보안을 챙기는 게 필수다. 발급 시 파일 종류와 크기를 제한하고, 완료 시 실제 파일이 맞는지 검증한다. 사용자가 이미지라며 다른 걸 올릴 수 있으니, 검증을 통과한 것만 정식으로 기록한다. 사용자 입력은 늘 의심하고 검증한다는 원칙이 파일 업로드에도 그대로 적용된다.


조금 더 실무적으로, 업로드 후처리를 어디서 할지도 정해야 한다. 이미지 압축이나 썸네일 생성 같은 무거운 처리를 업로드 흐름 안에서 하면 느려진다. 그래서 이런 후처리는 뒤에서 다룰 비동기 큐로 넘겨, 업로드는 빠르게 끝내고 처리는 뒤에서 진행하는 게 좋다. 무거운 일은 뒤로 미루는 게 서버리스의 원칙이다.

5. 이 패턴의 값어치

presigned 2단계 업로드는 처음엔 번거로워 보이지만, 익히고 나면 큰 파일 업로드의 표준으로 자리 잡는다. 워커의 한도에 부딪히지 않고, 비효율적인 데이터 흐름을 없애고, 안전하게 업로드를 처리한다. 큰 파일을 다루는 서비스라면 사실상 필수 패턴이다.


내 커뮤니티에서도 동영상 같은 큰 파일을 이 방식으로 올린다. 사용자가 동영상을 올려도 워커는 주소만 발급하고 완료만 기록하니 부담이 없다. 큰 파일이 워커를 통째로 거치던 순진한 방식이었다면 진작 한도에 걸렸을 것이다. 이 패턴 덕에 큰 파일도 문제없이 다룬다.


정리하면 presigned URL 2단계 업로드는 워커가 주소만 발급하고 실제 큰 파일은 사용자와 R2가 직접 주고받게 해서 워커의 한도를 우회하며, 검증과 보안을 챙기고, 큰 파일 처리의 표준이 된다. 다음 편에서는 R2를 쓰다 필연적으로 생기는 문제, 삭제되지 않고 남는 고아 파일을 retry와 큐로 정리하는 방법을 다룬다.


이 패턴의 값어치는 큰 파일을 다뤄보면 확실해진다. 동영상 같은 걸 순진한 방식으로 올렸다면 진작 한도에 걸렸을 것이다. 2단계 업로드 덕에 큰 파일도 워커 부담 없이 안전하게 처리된다. 처음의 번거로움을 넘으면, 이게 큰 파일 업로드의 표준인 이유를 몸으로 알게 된다.


다음 편에서는 R2를 쓰다 필연적으로 생기는 문제, 삭제되지 않고 남는 고아 파일을 다룬다. 글은 지웠는데 그 이미지가 스토리지에 남는 상황을, retry와 큐로 어떻게 정리하는지를 실전으로 짚는다.