Zettelkasten

Valet Key 패턴은 제한된 presigned URL로 데이터 전송을 서버에서 오프로딩한다

·수정 2

요약

  • 서버가 큰 객체를 받아서 다시 내보내는 대신, 범위·시간이 제한된 키(presigned URL)만 발급해 클라이언트가 스토리지에 직접 접근하게 한다.
  • 줄어드는 건 서버 부하(CPU·대역폭·커넥션)와 확장성이지 egress 전송비가 아니다 — 바이트는 어차피 인터넷으로 한 번 나간다.
  • public 버킷 공개와 구분되는 핵심은 "제한된 키로 안전하게"라는 보안 통제. 부하 감소가 목적, 스코프 제한 키가 메커니즘.

본문

문제: 서버가 전송 경로에 끼면 부하가 거기 몰린다

cold 데이터(오래된 게시물 등)를 S3에 두고 유저에게 내려줄 때, 서버가 S3에서 받아 다시 클라이언트로 중계하면 큰 객체마다 서버의 대역폭·CPU·메모리·커넥션을 점유한다. 동시 다운로드가 몰리면 서버가 먼저 무너진다.

해법: 발렛 키를 쥐여주고 서버는 빠진다

발렛파킹 키처럼 "딱 이 객체만, 이 동작만(GET), 잠깐만" 허용하는 제한된 토큰을 클라이언트에 직접 준다. AWS에서는 이게 presigned URL이다.

GET /me/saved/archive
  서버: 1) 인증·인가 확인 (이 유저가 이 객체 주인인가)
        2) 객체 키 계산
        3) presigned URL 발급  ← 서명 연산만, 데이터는 안 거침
        4) { url, expiresIn: 300 } 응답
  클라: 그 URL로 S3에서 직접 다운로드

서버는 서명만 하고 전송 경로에서 빠진다. 데이터는 S3 → 클라이언트 직빵.

얻는 것 vs 안 얻는 것 — egress 오해

전송 offload로 얻는 것: 서버 CPU·대역폭·커넥션 절감, 동시 전송 확장성(S3가 감당), 중계 서버 비용 절감, 중간 홉 제거로 지연 감소.

egress(전송비)는 그대로다. egress는 "바이트가 AWS 밖으로 나갈 때" 과금되므로 누가 다운로드를 시작했는지와 무관하다. 서버 중계든 presigned든 인터넷으로 한 번 나가는 건 동일. egress를 실제로 줄이려면 CloudFront 캐싱, 전송 바이트 축소(Parquet 압축·필요 컬럼만), 재다운로드 방지가 필요하다. presigned는 egress 절감 수단이 아니다.

왜 이름이 "Valet Key"인가 — 보안이 정체성

부하만 줄이려면 버킷을 public으로 열어도 된다(가장 부하 적음). 하지만 그건 Valet Key가 아니다. Valet Key를 Valet Key답게 만드는 건 제한된 키다:

  • 특정 객체만 (남의 거 못 봄 → 발급 전 서버가 소유권 검사)
  • 특정 동작만 (GET만 / PUT만)
  • 짧은 시간만 (만료 — 유출 전제로 expiresIn은 수 분)

즉 "서버를 안 거치되, 아무나·아무거나·영원히 접근하게 두지는 않는다"가 패턴의 정체성. 부하 감소가 목적, 스코프 제한 키가 그걸 안전하게 달성하는 메커니즘.

한계 — 객체 통째 전송이라 필터/페이징은 클라 몫

presigned URL은 객체를 통째로 줄 뿐 WHERE로 거르지 못한다. 정렬·필터·일부만 보기는 클라가 받아서 처리. (예외로 S3 Select SelectObjectContent는 단일 객체(CSV/JSON/Parquet)에 server-side SQL WHERE가 되지만, 신규 AWS 고객에게는 더 이상 제공되지 않는다.) 그래서 "내 옛날 데이터 전체 보기/내보내기" 같은 통짜 조회엔 완벽하지만, "옛날 것 일부만 정밀 페이징"이나 cross-user 분석엔 안 맞아 그 부분은 Athena/BigQuery 같은 쿼리 엔진을 옆에 둔다.

서버 중계형 변종

전송 offload는 같지만 데이터가 인프라 안에 머무는 방식도 있다. Nginx X-Accel-Redirect, Apache X-Sendfile — 인가는 앱이 하고 실제 파일 전송은 웹서버에 위임. 클라이언트가 스토리지에 직접 닿지 않는다는 점이 Valet Key와 다르다.

관련 노트

참고