← 모든 글

Amazon S3 제대로 이해하기: 객체 스토리지의 원리와 비용 구조

객체 스토리지로서 S3의 구조, 내구성과 가용성, 스토리지 클래스와 비용, 보안과 데이터 보호를 살펴봅니다.

AWSS3

S3는 왜 따로 존재할까요

S3는 "파일을 끝없이 쌓아 두고, 인터넷 어디서든 주소 하나로 꺼내 쓰는 저장소"입니다. 서버 디스크는 용량이 정해져 있고 그 서버에 붙어 있어야만 읽을 수 있습니다. 사진, 로그, 백업처럼 계속 늘어나고 여러 곳에서 읽는 데이터에는 이 한계가 부담이 됩니다.

AWS의 스토리지는 크게 세 갈래로 나뉩니다. 차이는 "데이터를 어떤 단위로 다루느냐"에 있습니다.

구분 대표 서비스 다루는 단위 접근 방식 잘 맞는 곳
블록 스토리지 EBS 고정 크기 블록 EC2 한 대에 디스크로 연결 OS, 데이터베이스처럼 일부만 자주 고쳐 쓰는 데이터
파일 스토리지 EFS 파일과 디렉터리 여러 서버가 NFS로 함께 마운트 여러 서버가 같은 폴더를 공유해야 할 때
객체 스토리지 S3 객체(데이터 + 메타데이터) HTTP API로 어디서든 사진, 동영상, 로그, 백업, 정적 웹 자원

블록은 하드디스크, 파일은 사내 공유 폴더, 객체는 "무한한 창고에 이름표를 붙여 맡기는 것"에 가깝습니다. 파일 일부를 초 단위로 고쳐 쓰는 작업은 EBS가, 한 번 쓰고 여러 번 읽는 대용량 데이터는 S3가 잘합니다.

버킷, 키, 객체: 폴더는 사실 없습니다

S3의 구조는 단 두 단계입니다. 버킷은 최상위 그릇이고, 그 안에 키(이름)와 값(데이터)이 짝을 이룬 객체가 들어 있습니다. 버킷 이름은 전 세계에서 유일하고, 만들 때 리전을 정합니다.

콘솔에서 보이는 photos/2026/11/cat.jpg의 폴더는 진짜 폴더가 아닙니다. 이 문자열 전체가 하나의 키이고, /는 그냥 글자일 뿐입니다. photos/2026/ 같은 앞부분을 접두사(prefix)라고 부르고, 목록 조회나 수명 주기 규칙을 걸 때 이 접두사를 씁니다. 그래서 "폴더 이름 바꾸기"는 사실 모든 객체를 복사하고 지우는 작업입니다.

객체는 통째로 씁니다. 1GB 파일의 한 줄만 고치고 싶어도 객체 전체를 다시 올려야 합니다. 대신 크기는 객체 하나에 최대 5TB까지, 버킷 전체는 사실상 무제한입니다. 큰 파일은 멀티파트 업로드로 조각을 병렬로 올리면 빨라집니다.

모든 조작은 PUT, GET, DELETE, LIST 같은 HTTP 요청입니다. SDK나 CLI도 결국 이 API를 호출하는 것이라, 브라우저나 모바일 앱도 바로 대화할 수 있습니다.

2020년 12월부터 S3는 강한 쓰기 후 읽기 일관성(read-after-write consistency)을 제공합니다. PUT이 성공한 직후의 GET과 LIST는 항상 새 내용을 돌려줍니다. 덮어쓰기와 삭제도 마찬가지라서 "방금 올린 파일이 안 보이는" 문제를 따로 처리할 필요가 없습니다.

내구성과 가용성은 다른 이야기입니다

S3 Standard의 내구성은 99.999999999%로, 흔히 '일레븐 나인'이라고 부릅니다. 객체 1,000만 개를 맡기면 1만 년에 한 개쯤 잃어버릴 수 있다는 수준입니다. 비결은 복제입니다. 객체를 저장하면 S3가 한 리전 안의 최소 3개 가용 영역(AZ)에 나눠 저장하고, 손상을 감지하면 스스로 복구합니다. 데이터 센터 하나가 통째로 멈춰도 데이터는 남습니다.

가용성은 "지금 요청하면 응답을 받을 수 있는가"입니다. Standard는 99.99%를 목표로 설계되어 있어, 내구성보다 훨씬 낮은 숫자입니다. 정리하면 내구성은 데이터를 잃지 않는 것, 가용성은 데이터에 언제든 닿을 수 있는 것입니다. 뒤에 나올 One Zone-IA는 가용성과 내구성 설계 모두가 AZ 하나에 묶여 있다는 점을 기억해 두세요.

비용은 세 가지로 나옵니다

S3 청구서는 저장 요금, 요청 요금, 데이터 전송 요금의 합입니다. 저장은 GB·월 단위, 요청은 PUT과 GET 1,000건 단위로 계산합니다. 전송은 인터넷으로 나가는 양(GB)에만 붙고, 올리는 것은 무료입니다. 작은 파일 수백만 개는 요청 요금이, 인기 동영상은 전송 요금이 청구서를 키웁니다.

스토리지 클래스는 저장 단가를 낮추는 대신 꺼낼 때의 비용과 시간을 늘리는 거래입니다. 자주 읽을수록 위쪽, 거의 안 읽을수록 아래쪽이 유리합니다. 아래 단가는 미국 동부(버지니아) 기준 대략적인 값입니다.

클래스 저장 단가 (USD/GB·월, 대략) 꺼내는 속도 조회 요금 최소 보관 기간 어울리는 데이터
Standard 0.023 밀리초 없음 없음 자주 읽는 데이터
Intelligent-Tiering 접근 빈도에 따라 자동 밀리초 (기본 계층) 없음 (객체당 모니터링 요금) 없음 접근 패턴을 모를 때
Standard-IA 0.0125 밀리초 GB당 있음 30일 가끔 읽지만 즉시 필요
One Zone-IA 0.01 밀리초 GB당 있음 30일 다시 만들 수 있는 사본
Glacier Instant Retrieval 0.004 밀리초 GB당 더 높음 90일 분기에 한 번 보는 아카이브
Glacier Flexible Retrieval 0.0036 수 분~12시간 속도별 차등 90일 백업, 장기 보관
Glacier Deep Archive 0.00099 12~48시간 속도별 차등 180일 규정상 수년 보관하는 기록

최소 보관 기간이 핵심입니다. IA에 올리고 열흘 만에 지워도 30일치가 청구됩니다. 그래서 저렴한 클래스는 오래 두고 거의 읽지 않는 데이터에만 돈을 아낄여 줍니다.

수명 주기(Lifecycle) 규칙은 이 이동을 자동화합니다. "logs/ 접두사는 30일 뒤 IA로, 90일 뒤 Glacier로, 365일 뒤 삭제"처럼 규칙 하나로 데이터가 나이에 맞는 자리로 내려갑니다. 접근 패턴을 예측할 수 없다면 Intelligent-Tiering에 맡기는 편이 낫습니다.

접근 제어: 기본은 잠금입니다

새 버킷과 객체는 만든 계정 말고는 아무도 읽을 수 없습니다. 여기에 Block Public Access가 기본으로 켜져 있어, 실수로 공개 정책을 붙여도 무시합니다. 공개는 일부러 여러 단계를 풀어야만 가능합니다.

권한을 주는 길은 두 가지입니다.

  • IAM 정책: 사람이나 역할에 붙입니다. "이 Lambda는 uploads/에 쓸 수 있다"처럼 주체 입장에서 씁니다.
  • 버킷 정책: 버킷에 붙입니다. 다른 AWS 계정이나 CloudFront 같은 서비스에 문을 열어 줄 때 씁니다.

두 정책은 함께 평가됩니다. 명시적인 Deny가 하나라도 있으면 다른 허용을 모두 이깁니다.

웹사이트 이미지처럼 누구나 봐야 하는 파일도 버킷을 직접 공개하지 않는 편이 좋습니다. CloudFront 앞에 두고 OAC(Origin Access Control)를 설정하면, 버킷 정책이 그 배포의 요청만 받아 줍니다. 버킷은 비공개로 남고, 엣지 캐시 덕분에 전송 비용과 지연도 줄어듭니다.

특정 사용자에게 잠깐 파일 하나만 허락하려면 presigned URL을 씁니다. 서버가 자기 권한으로 "이 키에 15분 동안 PUT 가능"을 서명한 URL을 만들어 클라이언트에 줍니다. 파일이 앱 서버를 거치지 않고 S3로 바로 가므로 서버 부담이 크게 줄어듭니다.

데이터 보호: 암호화, 버전, 복제, 잠금

지금 올리는 모든 객체는 기본으로 SSE-S3(S3가 관리하는 키)로 암호화됩니다. 누가 언제 키를 썼는지 기록하고 키 사용 권한까지 따로 통제하고 싶다면 SSE-KMS를 고릅니다. 이때는 객체를 읽으려면 S3 권한과 KMS 키 권한이 둘 다 필요하고, KMS 호출 요금도 붙습니다.

기능 막아 주는 사고 동작
버전 관리 실수로 덮어쓰기, 삭제 같은 키의 이전 버전을 모두 보관, 삭제는 삭제 마커만 남김
복제 (CRR/SRR) 리전 장애, 계정 침해 새 객체를 다른 버킷에 비동기로 복사, 버전 관리 필수
Object Lock 랜섬웨어, 내부자의 삭제 정해진 기간 동안 누구도 지우거나 고칠 수 없음(WORM)

버전 관리는 이전 버전도 저장 요금을 내므로, 수명 주기 규칙으로 "이전 버전은 30일 뒤 삭제"를 함께 거는 것이 보통입니다.

이벤트 알림: 올라오면 일이 시작됩니다

S3는 "객체가 생기거나 지워졌다"는 사건을 다른 서비스에 알려 줄 수 있습니다. 서버가 주기적으로 버킷을 뒤지는 대신, 파일이 도착하는 순간 처리가 시작됩니다.

  • Lambda 직접 호출: 가장 간단합니다. 썸네일 생성처럼 짧은 작업에 어울립니다.
  • SQS 큐: 업로드가 몰려도 메시지가 쌓여 있다가 소비자가 자기 속도로 처리합니다. 실패한 작업을 다시 시도하기도 쉽습니다.
  • SNS 또는 EventBridge: 하나의 이벤트를 여러 곳에 나눠 보내거나, 조건별로 라우팅할 때 씁니다.

필터는 접두사와 접미사로 겁니다. 예를 들어 uploads/로 시작하고 .jpg로 끝나는 객체만 골라 받을 수 있습니다. 처리 결과를 같은 접두사에 다시 쓰면 이벤트가 끝없이 반복되므로, 입력과 출력은 접두사나 버킷을 나눠 두세요. 알림은 드물게 중복될 수 있어서, 같은 이벤트를 두 번 처리해도 결과가 같도록 만드는 것이 좋습니다.

예시: 사진 공유 서비스를 설계해 봅시다

사용자가 사진을 올리면 썸네일을 만들고, 웹과 앱에서 빠르게 보여 주는 서비스입니다. 버킷은 용도별로 두 개를 둡니다.

버킷 담는 것 클래스와 수명 주기 접근
photo-originals 원본 사진 Standard 업로드, 60일 뒤 Standard-IA, 1년 뒤 Glacier Instant Retrieval presigned URL로 업로드만, 버전 관리 켬
photo-thumbs 썸네일, 리사이즈본 Standard (작고 자주 읽힘) CloudFront + OAC로만 읽기

요청이 흐르는 순서는 이렇습니다.

  1. 앱이 API 서버에 업로드를 요청하면, 서버가 photo-originals 버킷의 u123/abc.jpg 키에 대한 10분짜리 presigned URL을 돌려줍니다.
  2. 앱이 그 URL로 S3에 직접 PUT합니다. 사진은 API 서버를 지나지 않습니다.
  3. 업로드 완료 이벤트가 SQS에 쌓이고, Lambda가 이를 꺼내 썸네일을 photo-thumbs에 씁니다.
  4. 보는 사람은 CloudFront 주소로 썸네일을 받고, 원본이 필요하면 서버가 다시 presigned URL을 발급합니다.

입력과 출력 버킷이 나뉠어 있어 이벤트 루프가 생기지 않습니다. 썸네일은 원본에서 다시 만들 수 있으니 버전 관리나 복제를 굳이 켜지 않아도 됩니다. 오래된 원본은 수명 주기가 알아서 싼 자리로 옮겨 줍니다.

마무리: S3를 설계할 때 기억할 원칙

  • 데이터를 통째로 쓰고 여러 번 읽는다면 S3, 조금씩 고쳐 쓴다면 EBS나 EFS를 고릅니다.
  • 키 이름이 곧 구조입니다. 수명 주기, 이벤트, 권한을 걸 접두사를 처음부터 정해 둡니다.
  • 비용은 저장량보다 읽는 패턴이 좌우합니다. 데이터의 나이에 맞춰 클래스를 내립니다.
  • 버킷은 비공개로 두고, 공개는 CloudFront로, 일시 허락은 presigned URL로 합니다.
  • 잃으면 안 되는 데이터에만 버전 관리, 복제, Object Lock을 겁니다. 다시 만들 수 있는 데이터에는 비용만 늘립니다.

이 다섯 가지만 잡아 두면, 새로운 S3 기능을 만나도 "이건 어떤 비용을 내고 무엇을 얻는 기능인가"로 금방 자리를 찾을 수 있습니다.