S3 Basics
분류: Layer 3 - AWS 인프라 & 보안
1. 한 줄 정의
섹션 제목: “1. 한 줄 정의”S3(Simple Storage Service)는 AWS의 객체 스토리지 서비스로, 파일을 무제한으로 저장하고 꺼낼 수 있는 클라우드 저장소이다.
2. 왜 중요한가
섹션 제목: “2. 왜 중요한가”이미지, 로그, 백업, 정적 파일, 데이터 아카이브 등 거의 모든 종류의 파일 저장에 S3가 쓰인다. AWS에서 가장 기본적이고 가장 많이 쓰이는 서비스 중 하나.
2.5 왜 등장했나 — POSIX 파일시스템의 한계 → S3
섹션 제목: “2.5 왜 등장했나 — POSIX 파일시스템의 한계 → S3”S3는 2006년 3월 출시됐다. 그 전까지 대규모 데이터의 표준은 POSIX 파일시스템(NFS, ext4 등 1988년 표준화)이었지만, 페타바이트 규모에서 다음 한계로 깨졌다.
- 트리 전체 consistency domain: POSIX는 디렉터리 트리 전체에 일관성을 강제한다 — 한 디렉터리의 변경이 모든 클라이언트에 동시 가시되어야 한다. 노드를 수평 확장할수록 동기화 오버헤드가 폭증해 단일 파일시스템으로 수억 객체를 유지하기 어렵다.
- inode 고갈: ext4 등은 포맷 시점에 inode 카운트가 고정된다. 작은 파일을 수억 개 저장하면 공간이 남아도 inode가 먼저 떨어진다 —
df -i로 사용량 확인. - 디렉터리 락 경합: 같은 디렉터리에 동시 쓰기 시
renameatomicity 보장을 위한 락 때문에 throughput 상한이 생긴다.
S3가 푸는 방식 — Flat namespace + bucket 단위 consistency:
- 디렉터리 트리를 버리고
bucket + key의 평면 키-값 구조로 전환. consistency domain이 bucket 단위로 좁아져 수평 확장이 가능해졌고, 단일 버킷의 객체 수 상한은 사실상 없다(수십억 객체 운용 사례 다수). - 단, 출시 후 14년간 overwrite PUT / DELETE는 eventual consistency였다. EMR·데이터 레이크가 쓰기 직후 LIST에서 stale 결과를 받아 EMRFS Consistent View, S3Guard 같은 보조 메커니즘이 필수였다. 2020-12-01부터 GET/PUT/LIST가 모두 강한 read-after-write 일관성으로 전환되면서 이 우회 코드가 전부 제거 가능해졌다 — 다만 Cross-Region Replication을 사용하면 복제 지연 동안은 여전히 eventual하다.
이 토픽이 사라지면 무엇이 깨지나: 클라우드 데이터 레이크·이미지 업로드·로그 아카이브가 페타바이트 규모에서 작동하지 못한다. NFS로 동일 워크로드를 구성하면 inode 고갈·디렉터리 락·복제 latency로 먼저 무너진다 — Netflix·Airbnb 등의 데이터 플랫폼이 모두 객체 스토리지 위에 서 있는 이유다.
📖 출처: Amazon S3 Update – Strong Read-After-Write Consistency (AWS News Blog, 2020-12-01), From POSIX to AWS and S3 – an Evolution (Scality)
3. 핵심 개념
섹션 제목: “3. 핵심 개념”Bucket (버킷)
파일을 담는 최상위 컨테이너. 이름이 전 세계적으로 고유해야 한다.
Object (객체)
S3에 저장된 파일 하나. 파일 자체 + 메타데이터로 구성. Key(경로)로 식별한다.
s3://my-bucket/images/profile.jpg→ Bucket: my-bucket, Key: images/profile.jpg
S3가 내부적으로 어떻게 동작하는가
비유: S3는 “무한히 확장되는 클라우드 창고”이다. 일반 파일시스템(폴더 구조)과 달리, S3는 Flat(평탄한) 구조이다.
핵심 차이:
- 일반 파일시스템: 실제 폴더가 존재함
- S3: “폴더”는 없음.
images/profile.jpg는images/라는 폴더가 아니라,images/profile.jpg라는 이름의 Key를 가진 객체 1개
콘솔에서 폴더처럼 보이는 건 AWS 콘솔이 /를 기준으로 시각적으로 분리해서 표시해주는 것이다. 실제로는 Key가 긴 객체일 뿐이다.
데이터 내구성: S3는 내부적으로 데이터를 여러 AZ에 분산 저장한다. Erasure Coding으로 일부 디스크가 손상돼도 데이터를 복구할 수 있다. 공식 내구성 99.999999999%(11-nine).
📖 더 보기: Object Storage Architecture of AWS S3 — S3의 Flat 구조, Erasure Coding, 분산 저장 메커니즘을 그림과 함께 설명 (중급)
Storage Class (스토리지 클래스)
- Standard: 자주 접근하는 데이터 (기본값)
- Infrequent Access (IA): 가끔 접근. Standard보다 저렴. 단, 최소 30일 보관 요구
- Glacier Instant Retrieval: 아카이브 + 즉시 복원 가능. 최소 90일 보관
- Glacier Deep Archive: 장기 아카이브. 매우 저렴하지만 복원에 12시간 소요. 최소 180일 보관
- Intelligent-Tiering: 접근 패턴을 자동 분석하여 최적 클래스로 자동 이동. 예측이 어려운 데이터에 유용
Lifecycle Policy — 비용 절감의 핵심
비유: Lifecycle Policy는 “자동 파일 정리 규칙”이다. “업로드한 지 30일이 지난 로그 파일은 IA로 이동하고, 90일이 지나면 Glacier로, 365일이 지나면 삭제”처럼 규칙을 설정하면 S3가 알아서 처리한다.
일반적인 Lifecycle 전략으로 스토리지 비용을 30~70% 절감할 수 있다:
Day 0: Standard (업로드 직후)Day 30: Standard-IA로 전환 (접근 빈도 감소)Day 90: Glacier Instant Retrieval로 전환Day 365: Glacier Deep Archive로 전환 (또는 삭제)AWS 콘솔에서 설정하는 방법:
S3 → 버킷 선택 → Management 탭 → Lifecycle rules → Create lifecycle rule→ Transition actions: 원하는 날짜에 클래스 전환 설정→ Expiration actions: 특정 날짜 이후 자동 삭제 설정주의: IA/Glacier 전환 시 최소 보관 기간이 있어, 너무 빨리 삭제하면 잔여 기간에 대한 요금이 부과된다.
Intelligent-Tiering — 접근 패턴을 모를 때의 베스트 선택 (2025)
접근 패턴이 불규칙하거나 예측이 어려운 데이터라면 Intelligent-Tiering이 최선이다. 내부적으로 Frequent Access / Infrequent Access / Archive Instant Access 세 계층을 자동으로 이동하며, 별도 검색 비용이 없다.
30일 미접근 → Infrequent Access tier (Standard-IA보다 저렴)90일 미접근 → Archive Instant Access tier→ 다시 접근하면 Frequent Access tier로 즉시 복귀 (검색 지연 없음)제약: 128KB 미만 객체는 자동 티어 이동이 안 됨. 객체당 월 $0.0025의 모니터링 요금 발생.
실제 절감 효과: AWS 발표에 따르면 S3 Intelligent-Tiering 사용자들은 Standard 대비 평균 67% 스토리지 비용 절감을 달성했다.
S3 보안 하드닝 — 2025년 체크리스트
-
HTTPS 강제 적용 (Bucket Policy)
HTTP로 오는 요청을 차단한다. 데이터 전송 중 암호화를 강제하는 버킷 정책이다.
{"Effect": "Deny","Principal": "*","Action": "s3:*","Resource": ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"],"Condition": {"Bool": {"aws:SecureTransport": "false"}}} -
서버 사이드 암호화 강제 (SSE)
모든 업로드 시 암호화를 강제하려면 버킷의 Default Encryption을 활성화한다.
S3 → 버킷 → Properties → Default encryption → SSE-S3 또는 SSE-KMS 선택→ 이후 업로드되는 모든 객체는 자동으로 암호화 -
GuardDuty S3 위협 탐지 활성화
GuardDuty는 S3 데이터 접근 이벤트를 지속적으로 분석하여 이상 접근(비정상적인 IP에서 대량 다운로드 등)을 자동 탐지한다.
경로: GuardDuty → Settings → Enable → S3 Protection 활성화→ 이상 접근 감지 시 CloudWatch Events로 알림 수신 가능
Bucket Policy / ACL
버킷 레벨의 접근 권한 설정. IAM Policy와 함께 “누가 이 버킷의 파일을 읽기/쓰기 가능한지”를 정의.
⚠️ 퍼블릭 접근 주의
기본적으로 S3 버킷은 비공개다. “Block Public Access” 설정이 기본으로 켜져 있다.
이 설정을 끄면 전 세계 누구나 버킷 내용에 접근할 수 있다.
민감 데이터(개인정보, 로그, 백업)가 있는 버킷은 절대 퍼블릭으로 열지 않는다.
퍼블릭 파일 제공이 필요하면 Presigned URL 또는 CloudFront를 사용한다.
Versioning (버전 관리)
같은 Key로 파일을 덮어써도 이전 버전을 보관. 실수로 삭제해도 복구 가능.
주의: Versioning 활성화 후 삭제하면 실제 삭제가 아닌 “Delete Marker”가 생성된다. 완전 삭제하려면 Delete Marker도 함께 제거해야 한다.
Presigned URL — 왜 필요한가
비유: Presigned URL은 “일회용 입장권”이다. S3 버킷은 잠겨 있지만(비공개), 이 티켓을 가진 사람은 지정된 시간 동안 특정 파일에 접근할 수 있다.
사용 시나리오: 사용자가 파일을 업로드하거나 다운로드할 때, 서버가 Presigned URL을 생성해서 클라이언트에게 전달한다. 클라이언트는 이 URL로 S3에 직접 업로드/다운로드 — 서버를 거치지 않아 트래픽 절약.
NestJS에서 Presigned URL 생성 예시:
// npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignerimport { S3Client, GetObjectCommand, PutObjectCommand,} from "@aws-sdk/client-s3";import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3Client = new S3Client({ region: "ap-northeast-2" });
// 다운로드 URL (10분)async function getPresignedDownloadUrl( bucket: string, key: string,): Promise<string> { const command = new GetObjectCommand({ Bucket: bucket, Key: key }); return getSignedUrl(s3Client, command, { expiresIn: 600 });}
// 업로드 URL (5분) — 클라이언트가 직접 S3에 업로드async function getPresignedUploadUrl( bucket: string, key: string,): Promise<string> { const command = new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: "image/jpeg", // 클라이언트도 반드시 이 Content-Type으로 업로드해야 함 }); return getSignedUrl(s3Client, command, { expiresIn: 300 });}
// 사용 예시const url = await getPresignedDownloadUrl("my-bucket", "images/profile.jpg");// 출력 예시:// https://my-bucket.s3.ap-northeast-2.amazonaws.com/images/profile.jpg// ?X-Amz-Algorithm=AWS4-HMAC-SHA256// &X-Amz-Credential=...// &X-Amz-Expires=600// &X-Amz-Signature=...📖 더 보기: NestJS에서 S3 Presigned URL 구현 — 업로드/다운로드 Presigned URL 생성부터 프론트엔드 연동까지 단계별 가이드 (중급)
4. 실무에서 어디에 쓰이나
섹션 제목: “4. 실무에서 어디에 쓰이나”- 사용자 업로드 파일 저장 (이미지, 문서 등)
- 애플리케이션 로그 아카이브
- 정적 웹사이트 호스팅
- DB 백업 저장
- CI/CD 빌드 아티팩트 저장
- 데이터 레이크 (분석용 원시 데이터)
5. 현재 내 업무와 연결점
섹션 제목: “5. 현재 내 업무와 연결점”- 서비스에서 업로드된 파일이 어디에 저장되는지 이해
- 로그 아카이브 확인 시 S3에서 찾아야 함
- 배포 아티팩트가 S3에 있을 수 있음
- 비용 최적화 시 스토리지 클래스 전환 검토
5.3 스토리지 클래스 전환 트리거 — 의사결정 기준표
섹션 제목: “5.3 스토리지 클래스 전환 트리거 — 의사결정 기준표”| 전환 트리거 | 권장 클래스 | 이유 |
|---|---|---|
| 접근 패턴 예측 가능, 월 1회 이상 | Standard | 검색 요금 없음. 빈번 접근에 최적 |
| 마지막 접근 후 30~89일 경과, 예측 가능 | Standard-IA | Standard 대비 저장 비용 40% ↓. 최소 30일 보관 요금 주의 |
| 마지막 접근 후 90일 이상, 즉시 복원 필요 | Glacier Instant Retrieval | Standard-IA 대비 68% ↓. 최소 90일 보관 |
| 1년 이상 아카이브, 12시간 내 복원 가능 | Glacier Deep Archive | 가장 저렴. 최소 180일 보관 |
| 접근 패턴 불규칙/예측 불가, 128KB 이상 | Intelligent-Tiering | ML이 자동 분류. 검색 요금 없음. 모니터링 비용 $0.0025/객체/월 |
| 접근 패턴 불규칙, 128KB 미만 소형 파일 다수 | Standard 유지 | I-T 모니터링 비용이 저장 비용 초과 가능 |
Lifecycle + Intelligent-Tiering 조합 선택 기준:
- 새 데이터에 자주 접근 → 오래될수록 안 보는 패턴 → Lifecycle(규칙 기반 전환)
- 특정 파일은 자주 / 다른 파일은 안 봄 패턴 → Intelligent-Tiering(파일별 자동 분류)
(출처: Amazon S3 Intelligent-Tiering 공식 문서, AWS S3 storage classes 요금)
5.5 객체 스토리지 원리의 전이 가능성
섹션 제목: “5.5 객체 스토리지 원리의 전이 가능성”S3에서 배운 핵심 원리는 벤더에 종속되지 않는다. GCS, Azure Blob Storage, MinIO 모두 동일한 객체 스토리지 아키텍처를 따른다.
주요 플랫폼 비교
섹션 제목: “주요 플랫폼 비교”| 항목 | AWS S3 | Google Cloud Storage | Azure Blob Storage | MinIO |
|---|---|---|---|---|
| 키 구조 | Flat namespace — /는 시각적 구분자, 실제 폴더 없음 | Flat namespace (동일) | Flat namespace. Azure Data Lake Gen2에서는 계층형 NS 선택 가능 | S3 호환 Flat namespace |
| Consistency | 강한 일관성 (2020년 이후 — PutObject 즉시 반영) | 강한 일관성 (전역) | 강한 일관성 (단일 리전). 지역 복제 시 eventual | 강한 일관성 (단일 노드/클러스터) |
| 멀티파트 업로드 | Multipart Upload API (5MB~5TB 파트) | Parallel Composite Upload | Block Blob 청킹 (블록 단위 커밋) | S3 Multipart API 완전 호환 |
| 요청 한도 | 3,500 PUT / 5,500 GET per prefix/sec | 자동 스케일 (quota 없음) | 2,000 req/sec per blob | 하드웨어 의존 |
| Presigned URL | X-Amz-Signature 서명 방식 | Signed URL (V4 호환) | SAS Token (비슷한 역할) | S3 Presigned URL 완전 호환 |
| Versioning | Object Versioning + Delete Marker | Object Versioning (유사) | Blob Versioning / Soft Delete | Versioning 지원 |
| 비용 구조 | $0.023/GB (Standard) | $0.020/GB (Standard) | $0.018/GB (Hot tier) | 자체 호스팅 (S3 스토리지 비용 없음) |
S3 원리 → 타 플랫폼 전이 공통 개념
섹션 제목: “S3 원리 → 타 플랫폼 전이 공통 개념”Flat namespace / Key-Value 조회는 객체 스토리지의 보편 원리다. S3의 bucket + key → object 패턴은 GCS와 MinIO에서 동일하게 동작한다. 플랫폼이 바뀌어도 “폴더는 없고 Key 이름에 /가 포함될 뿐”이라는 메커니즘은 그대로 적용된다.
Eventual Consistency → 강한 일관성으로 수렴: 2020년 이전 S3는 overwrite PUT / DELETE에 대해 eventual consistency였다. 현재 S3, GCS, Azure Blob 모두 강한 일관성을 제공하지만, 지역 복제(Cross-Region Replication)를 사용하는 경우 복제 지연(보통 수 초~수 분) 동안은 여전히 eventual하다.
Presigned URL / SAS Token 패턴: “서버가 자격증명 없이 클라이언트에게 임시 접근권을 위임”하는 패턴은 클라우드 스토리지 전반에 동일하게 적용된다. S3는 X-Amz-Signature, GCS는 Signed URL, Azure는 SAS Token이라는 이름을 쓰지만 원리는 동일하다.
Content Addressable Storage(CAS)와의 연결: Git의 .git/objects는 내용의 SHA-1 해시를 Key로 사용하는 객체 스토리지다. “콘텐츠 = Key” 구조는 S3의 Key-Value 패턴과 동일한 아키텍처다. 파일 내용이 같으면 동일한 Key → 중복 저장 방지. Versioning도 같은 원리 — Git의 commit history와 S3 Object Versioning 모두 “변경 불가능한 객체 + 새 버전 추가” 패턴이다.
MinIO: S3 API를 완전히 구현한 오픈소스 객체 스토리지. S3 SDK를 그대로 사용해 온프레미스에 배포 가능하다. Kubernetes 기반 분산 배포를 지원하며, 엔터프라이즈 환경에서 멀티클라우드/하이브리드 아키텍처에 자주 사용된다.
새 객체/블록/파일 스토리지를 만났을 때 — 분석 체크리스트
| 질문 | S3 답변 | 다른 시스템 이식 시 확인할 것 |
|---|---|---|
| 1. 네임스페이스 구조 | Flat — “폴더”는 없고 Key에 / 포함 | GCS/MinIO: 동일 Flat. Azure Data Lake Gen2: 선택적 계층형 NS |
| 2. Consistency 보장 | 강한 일관성 (단일 리전). Cross-Region 복제 시 수 초~수 분 eventual | DB 선택 시 CAP 정리 기준과 동일한 질문 |
| 3. 대용량 업로드 지원 | Multipart Upload (5MB~5TB 파트, 병렬) | GCS: Parallel Composite. Azure: Block Blob 청킹 |
| 4. 접근 권한 위임 방식 | Presigned URL (시간 제한 서명) | GCS: Signed URL. Azure: SAS Token. 패턴 동일 |
| 5. 비용 구조 핵심 변수 | 스토리지(GB)+요청 수+데이터 전송 3종 과금 | 어떤 스토리지든 3종 변수 확인 → S3 503 SlowDown처럼 요청 수가 주요 비용이 됨 |
| 6. 장애 내성 | 여러 AZ 분산, 11-nine 내구성. Single-AZ(Express One Zone)는 내구성 ↓ | 단일 AZ vs 다중 AZ vs 지역 복제 — SLA와 비용 trade-off |
이 체크리스트는 GCS, Azure Blob, MinIO, NFS, 블록 스토리지(EBS), 자체 파일 서버 등 어떤 스토리지 시스템이든 동일하게 적용할 수 있다.
6. 자주 헷갈리는 개념 비교
섹션 제목: “6. 자주 헷갈리는 개념 비교”| 개념 A | 개념 B | 차이점 |
|---|---|---|
| S3 | EBS | S3는 객체 스토리지(파일 단위), EBS는 블록 스토리지(EC2에 붙이는 디스크) |
| Bucket Policy | IAM Policy | Bucket Policy는 버킷에 붙는 권한, IAM Policy는 사용자/역할에 붙는 권한 |
| Standard | Glacier | Standard는 즉시 접근, Glacier는 아카이브용(접근에 시간 소요) |
| Public Access | Presigned URL | Public은 누구나 접근, Presigned URL은 시간 제한 임시 접근 |
| Lifecycle Policy | Intelligent-Tiering | Lifecycle은 수동 규칙 기반 전환, Intelligent-Tiering은 접근 패턴 자동 분석 전환 |
6.5 트러블슈팅
섹션 제목: “6.5 트러블슈팅”🔧 S3 접두사 집중으로 인한 503 SlowDown — 성능 병목
섹션 제목: “🔧 S3 접두사 집중으로 인한 503 SlowDown — 성능 병목”증상: 대량 업로드/다운로드 작업 중 503 Slow Down 에러가 간헐적으로 발생. 재시도해도 반복됨
원인: S3는 내부적으로 요청을 prefix(접두사) 단위로 파티셔닝한다. 파티션당 처리 한도는 AWS 공식 기준으로 초당 3,500 PUT/COPY/POST/DELETE, 5,500 GET/HEAD이다. 동일한 prefix로 요청이 집중되면 파티션이 포화 상태에 이르러 503 Slow Down 응답을 반환한다.
자주 발생하는 패턴:
❌ 날짜 순차 prefix — 특정 날짜에 요청 집중logs/2024-01-15/app-001.loglogs/2024-01-15/app-002.loglogs/2024-01-15/app-003.log→ "logs/2024-01-15/" prefix 1개에 모든 요청 집중 → 503 SlowDown
❌ 순차 번호 prefixuploads/00001.jpg, uploads/00002.jpg, uploads/00003.jpg→ "uploads/" prefix 1개에 집중AWS 공식 권고 파티셔닝 전략:
# 전략 1: 해시 기반 prefix 분산# 원본 Key에서 SHA-256 앞 4자리를 prefix로 사용 → 최대 65,536개 파티션original_key="logs/2024-01-15/app-001.log"hash_prefix=$(echo -n "$original_key" | sha256sum | cut -c1-4)s3_key="${hash_prefix}/${original_key}"# 예: a3f2/logs/2024-01-15/app-001.log
# 전략 2: 타임스탬프 역순 prefix# 최신 데이터가 다른 prefix에 분산됨# 2024-01-15 → 5102-98-40 (역순) → prefix로 사용
# 전략 3: UUID/랜덤 prefix 추가import uuids3_key = f"{uuid.uuid4().hex[:8]}/logs/2024-01-15/app-001.log"# 예: a1b2c3d4/logs/2024-01-15/app-001.logprefix 분산으로 달성 가능한 처리량:
prefix 1개: 5,500 GET/secprefix 10개: 55,000 GET/sec (10배 선형 스케일)prefix 100개: 550,000 GET/secS3는 높은 요청률이 지속되면 내부적으로 파티션을 자동으로 추가 분할하지만, 이 프로세스는 점진적이며 분할 완료 전까지 503이 발생할 수 있다.
silent failure — DeleteObjects 503 재시도 후 0건 삭제: SDK에 따라 503 응답을 받은 뒤 재시도 시 HTTP 200 + Deleted: [](0건 삭제)를 반환하는 버그가 보고된 적이 있다 — 약 25만 객체 일괄 삭제 워크로드에서 재현(aws-sdk-go #3707). 에러 없이 “성공”이라 응답해 호출 측 코드가 삭제됐다고 믿고 다음 단계로 넘어가는 가장 위험한 패턴이다. 감지·복구 절차:
# 1. 삭제 직후 실제 객체가 사라졌는지 확인aws s3api list-object-versions --bucket my-bucket --prefix path/to/deleted/ \ --query 'length(Versions)' --output text# 예상 출력: 0 ← 0이 아니면 silent failure 의심
# 2. 응답 본문의 Deleted/Errors 배열을 직접 카운트 (HTTP 코드만 믿지 말 것)# JS: response.Deleted.length === request.Objects.length 확인# Go: len(out.Deleted) == len(in.Delete.Objects) 확인권고: 대량 삭제 시 SDK 버전을 최신으로 고정하고, 응답의 Deleted 개수와 요청 개수를 항상 비교한다. 불일치 시 누락 객체만 재시도.
📖 출처: aws-sdk-go Issue #3707 — DeleteObjects fails silently on retry after 503
재시도 전략 (AWS 권고):
대용량 요청(>128MB): 가장 느린 5% 요청 재시도소용량 요청(<512KB): 2초 후 재시도 → 4초 후 재시도 (exponential backoff)503 발생 시: 즉시 재시도 X — 지수 백오프 + jitter 적용🔧 “403 Forbidden / AccessDenied” — S3 파일 접근 거부
섹션 제목: “🔧 “403 Forbidden / AccessDenied” — S3 파일 접근 거부”증상: aws s3 cp 또는 SDK에서 AccessDenied 에러. 콘솔에서는 보이는데 코드에서 안 됨
원인 1: IAM Policy에 s3:GetObject 또는 s3:PutObject 권한이 없음
원인 2: Bucket Policy가 해당 IAM Role을 명시적으로 Deny하고 있음
원인 3 (가장 흔한 함정): Bucket에 Block Public Access가 켜져 있는데 퍼블릭 접근을 시도함
해결:
- IAM Policy 확인: 해당 Role에
s3:GetObject,s3:PutObject등이 Allow되어 있는지 - Bucket Policy 확인: S3 → 버킷 → Permissions 탭 → Bucket Policy에 Deny 구문이 있는지
- 콘솔에서
aws s3 ls s3://bucket-name으로 접근 테스트
aws s3 ls s3://my-bucket/# AccessDenied 시: 위 3단계 확인# 정상 시:# 2024-01-15 10:00:00 1024 images/profile.jpg# 2024-01-15 10:01:00 2048 logs/app.log🔧 Presigned URL 관련 에러 — 서명 불일치 또는 만료
섹션 제목: “🔧 Presigned URL 관련 에러 — 서명 불일치 또는 만료”증상: 클라이언트에서 Presigned URL로 접근 시 403 Request has expired, SignatureDoesNotMatch, 또는 400 Bad Request 에러
원인별 정리:
| 에러 | 원인 | 해결 |
|---|---|---|
Request has expired | 만료 시간 초과 | 클라이언트에서 URL 발급 후 빠르게 사용하도록 안내, 또는 만료 전 재발급 로직 추가 |
SignatureDoesNotMatch | 서버 시계가 AWS와 5분 이상 차이 | EC2/컨테이너의 NTP 시간 동기화 확인 |
400 Bad Request | 업로드 시 Authorization 헤더를 추가로 붙인 경우 | Presigned URL 요청에는 Authorization 헤더를 추가하면 안 됨 |
SignatureDoesNotMatch (업로드) | URL 생성 시 지정한 ContentType과 실제 업로드 ContentType이 다름 | 클라이언트가 Content-Type 헤더를 URL 생성 시와 동일하게 보내야 함 |
2025년 AWS 보안 권고: Presigned URL의 유효 시간을 최대 15분으로 제한하는 것이 권장된다. 유출 시 피해를 최소화하기 위함이다.
🔧 S3 파일 업로드 후 파일이 안 보이는 경우
섹션 제목: “🔧 S3 파일 업로드 후 파일이 안 보이는 경우”증상: PutObject 성공 응답을 받았지만 콘솔에서 파일이 보이지 않음. 또는 이전 버전의 파일이 계속 반환됨
원인 1: 버킷 이름 또는 Key(경로)를 잘못 지정해서 다른 위치에 저장됨
원인 2: Versioning이 활성화된 버킷에서 삭제 마커(Delete Marker)가 생성되어 최신 파일이 숨겨짐
해결:
- 업로드 응답에서 실제 저장된 Key 확인 후 콘솔에서 직접 검색
- Versioning이 켜진 버킷이면 “Show versions” 토글을 켜서 삭제 마커 확인
🔧 S3 CORS 에러 — 브라우저에서 파일 업로드/다운로드 실패
섹션 제목: “🔧 S3 CORS 에러 — 브라우저에서 파일 업로드/다운로드 실패”증상: 브라우저 콘솔에서 Access to fetch at 'https://...' from origin '...' has been blocked by CORS policy 에러. Presigned URL로 직접 업로드하거나 S3 파일을 프론트엔드에서 직접 가져올 때 발생
원인: S3 버킷의 CORS 설정에 현재 도메인의 HTTP 메서드가 허용되지 않음
해결:
S3 → 버킷 → Permissions 탭 → Cross-origin resource sharing (CORS) → Edit
아래 설정 추가:[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST"], ← 필요한 메서드 모두 추가 "AllowedOrigins": ["https://my-frontend.com"], ← 실제 도메인으로 변경 "ExposeHeaders": ["ETag"] }]🔧 S3 비용이 예상보다 많이 나오는 경우
섹션 제목: “🔧 S3 비용이 예상보다 많이 나오는 경우”증상: AWS Cost Explorer에서 S3 요금이 급증. 스토리지 요금이 아닌 “Requests” 또는 “Data Transfer” 항목이 주요 원인
원인 1: API 요청 수가 많아 PutObject/GetObject 요청 과금 (S3는 요청 건수로도 과금)
원인 2: 같은 리전 내 서비스 간 Data Transfer는 무료이나, 리전 간 또는 인터넷으로 나가는 트래픽은 과금
원인 3: Versioning이 켜진 버킷에 오래된 버전 데이터가 누적
해결:
# S3 Storage Lens로 버킷별 비용 분석# 경로: S3 → Storage Lens → Create dashboard# → 버킷별 스토리지 크기, 요청 수, 복제 데이터 분석 가능
# Versioning이 켜진 버킷의 오래된 버전 정리 (Lifecycle Rule)# S3 → 버킷 → Management → Lifecycle rules → Create rule# → "Previous versions" 대상, Expiration: 30일 후 삭제 설정
# AWS CLI로 특정 버킷의 버전 개수 확인aws s3api list-object-versions \ --bucket my-bucket \ --query 'length(Versions)' \ --output text# 예상 출력: 15234 ← 버전이 너무 많으면 정리 필요7. 체크리스트
섹션 제목: “7. 체크리스트”- S3의 Bucket과 Object 개념을 설명할 수 있다
- Storage Class의 차이를 설명할 수 있다
- S3 접근 권한이 어디서 관리되는지 안다 (Bucket Policy + IAM)
- Presigned URL이 뭔지, 언제 쓰는지 설명할 수 있다
- Lifecycle Policy로 비용을 절감하는 방법을 설명할 수 있다
- 동일 prefix 집중 시 503 SlowDown이 발생하는 이유와 파티셔닝 전략을 설명할 수 있다
- S3 Flat namespace 원리가 GCS, Azure Blob, MinIO에서도 동일하게 적용됨을 설명할 수 있다
8. 추가 학습 키워드
섹션 제목: “8. 추가 학습 키워드”S3 Event Notification, Lifecycle Policy, Cross-Region Replication, Transfer Acceleration, CloudFront + S3, Intelligent-Tiering, S3 Prefix Partitioning, Content Addressable Storage, MinIO
📚 추천 리소스
섹션 제목: “📚 추천 리소스”- 📖 AWS S3 공식 문서 - Presigned URL 업로드 — Presigned URL로 업로드하는 원리와 코드 예시 공식 가이드 (입문)
- 📖 NestJS S3 Presigned URL 구현 가이드 — NestJS + AWS SDK v3로 업로드/다운로드 URL 생성 단계별 튜토리얼 (중급)
- 📖 S3 403 Forbidden 에러 트러블슈팅 — AccessDenied 원인 12가지 유형별 진단 방법 공식 문서 (입문)
- 📖 AWS S3 보안 베스트 프랙티스 2025 — Block Public Access, HTTPS 강제, 암호화, GuardDuty 연동 AWS 공식 가이드 (중급)
- 📖 S3 Intelligent-Tiering 비용 최적화 가이드 — 접근 패턴 분석과 자동 티어 이동 원리, 비용 절감 시나리오 (중급)
9. 내가 직접 확인해볼 것
섹션 제목: “9. 내가 직접 확인해볼 것”- AWS 콘솔에서 팀 S3 버킷 목록 확인
경로: S3 → Buckets확인: 버킷 이름, 리전, 접근 권한 (Public access blocked 여부)
- 버킷 하나를 열어보고 폴더 구조와 파일 확인
경로: S3 → <버킷 이름> → Objects 탭주의: "폴더"로 보이는 것은 실제로 Key에 /가 포함된 객체들의 그룹
- AWS CLI로 버킷 파일 목록 확인
Terminal window aws s3 ls s3://bucket-name --recursive | head -10# 예상 출력:# 2024-01-15 10:00:00 1024 images/profile.jpg# 2024-01-15 10:01:00 2048 uploads/doc.pdf# 2024-01-15 10:02:00 1048576 backups/db-2024-01-15.sql.gz - Bucket Policy가 어떻게 설정되어 있는지 확인
경로: S3 → <버킷> → Permissions 탭 → Bucket Policy 섹션
10. 5줄 요약
섹션 제목: “10. 5줄 요약”- S3는 파일을 무제한으로 저장할 수 있는 AWS 객체 스토리지이며, 99.999999999%(11-nine) 내구성을 여러 AZ에 분산 저장으로 보장한다
- Bucket(컨테이너) + Object(파일) + Key(경로) 구조이며, “폴더”는 시각적 표현일 뿐 실제로는 Key 이름에
/가 포함된 것이다 - Lifecycle Policy + Intelligent-Tiering으로 스토리지 비용을 30~67% 절감할 수 있다 — 파일 특성에 따라 선택한다
- 접근 권한은 Bucket Policy(버킷 레벨) + IAM Policy(사용자/역할 레벨)의 교집합으로 결정된다, Presigned URL은 비공개 파일의 임시 접근에 사용한다
- S3 버킷은 기본 비공개이며, Block Public Access는 절대 해제하지 않는다 — 퍼블릭 파일이 필요하면 CloudFront를 앞에 둔다
프론트엔드 → 플랫폼 브릿지
섹션 제목: “프론트엔드 → 플랫폼 브릿지”CDN Static File Hosting과 S3의 관계
프론트엔드 개발 시 Netlify나 Vercel에 배포하면 알아서 CDN을 통해 전 세계 사용자에게 빠르게 정적 파일을 제공해준다. AWS에서는 이것을 S3 + CloudFront 조합으로 직접 구성한다:
[Netlify/Vercel 내부 동작 (추상화됨)]git push → 빌드 → CDN 자동 배포
[AWS에서 동일한 구성 (직접 제어)]git push → GitHub Actions 빌드 → S3 업로드 (버킷이 원본 저장소) → CloudFront Invalidation (캐시 갱신)사용자 → CloudFront (엣지 서버, 캐시) → S3 (원본, 직접 접근 차단)S3 버킷 자체는 비공개로 유지하고, CloudFront만 Origin Access Control(OAC)로 접근 권한을 갖는다. 이 구조가 “S3 + CloudFront”의 핵심이다.
프론트에서 파일 업로드 구현 시 두 가지 방법
React 앱에서 사용자가 이미지를 업로드할 때:
방법 1: 서버 경유 (간단하지만 서버 부하)React → multipart/form-data → NestJS API → S3 PutObject단점: 대용량 파일이면 NestJS 서버 메모리/CPU 사용
방법 2: Presigned URL (권장 — 서버 부하 없음)React → NestJS API (Presigned URL 요청)NestJS → Secrets Manager에서 자격증명 → S3 Presigned URL 생성 → React에 반환React → Presigned URL로 S3에 직접 업로드 (NestJS 불경유)NestJS ← S3 Event Notification (업로드 완료 알림)
// React 업로드 코드 예시const uploadFile = async (file: File) => { // 1. 백엔드에서 Presigned URL 받기 const { uploadUrl, fileKey } = await fetch('/api/upload-url', { method: 'POST', body: JSON.stringify({ fileName: file.name, contentType: file.type }) }).then(r => r.json());
// 2. S3에 직접 업로드 (서버 불경유) await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } // Presigned URL 생성 시 지정한 것과 동일해야 함 });};왜 S3 파일을 CloudFront 없이 직접 serving하면 안 되나
S3 버킷을 Public으로 열어서 직접 URL(https://my-bucket.s3.amazonaws.com/...)로 서빙하면:
- S3 요청 비용이 직접 청구됨 (CloudFront 캐시 없음)
- 리전이 한국(ap-northeast-2)이면 해외 사용자는 느림
- 버킷이 퍼블릭이라 잘못된 버킷 정책으로 전체 데이터가 노출될 위험
CloudFront를 앞에 두면: 캐시 히트율 높아 S3 요청 비용 90% 절감 + 전 세계 엣지 서버로 빠른 응답 + S3 버킷은 비공개 유지.
실무 아키텍처 패턴 — S3 사용 시나리오별 구성
섹션 제목: “실무 아키텍처 패턴 — S3 사용 시나리오별 구성”[파일 업로드 패턴 — 서버 트래픽 최소화]클라이언트 → NestJS API (Presigned URL 발급, 5~15분 유효)클라이언트 → S3 (직접 업로드, 서버 불경유)NestJS API ← S3 Event Notification (업로드 완료 알림)
[정적 파일 서빙 패턴 — 보안 + 성능]클라이언트 → CloudFront (CDN, HTTPS, 캐시)CloudFront → S3 (Origin Access Control, S3 버킷 비공개 유지)→ S3 버킷에 퍼블릭 접근 없이 CloudFront를 통해서만 파일 제공
[로그 아카이브 패턴]ECS/EC2 → CloudWatch Logs → S3 (Export, 30일 이후 자동 이동)S3 Lifecycle: Standard → Standard-IA (30일) → Glacier (90일) → 삭제 (365일)S3 Express One Zone — 2025년 주목할 신규 스토리지 클래스
2024년 말 출시 후 2025년 4월에 대폭 가격 인하(스토리지 31%, GET 요청 85%)된 고성능 스토리지 클래스이다. 단일 AZ에 저장되어 내구성은 낮지만, S3 Standard 대비 10배 빠른 접근 속도와 단일 자릿수 밀리초 지연시간을 제공한다.
- 최대 처리량: 초당 200만 요청
- 사용 사례: AI/ML 학습 데이터, 실시간 분석, 미디어 렌더링, HPC
- 버킷 타입: 일반 S3 버킷과 다른 Directory Bucket 타입으로 생성
언제 쓰면 안 되는가 — 내구성 trade-off:
내구성을 정량으로 비교하면 차이가 분명하다. Standard는 다중 AZ 복제로 99.999999999%(11-nine) 연간 객체 손실 확률, Express One Zone은 단일 AZ만 사용하므로 해당 AZ 전체 장애·정전·자연재해 시 데이터 영구 손실이 발생한다(AWS는 Express One Zone에 11-nine을 보장하지 않음).
따라서 다음은 Express One Zone 금지:
- DB 백업·트랜잭션 로그·소스 코드·계약 문서 등 재생산 불가능한 원본
- 규정상 다중 AZ 보관 의무가 있는 데이터(금융·의료)
- 장기 보관(아카이브) — 단일 AZ에서 1년 이상 누적 시 손실 확률이 누적됨
적합한 결정 기준: “AZ가 통째로 사라져도 재계산·재학습으로 재생산 가능한가?” → Yes일 때만 사용. 예: ML 학습 epoch의 중간 텐서, 캐시 가능한 추론 결과, 원본이 다른 곳에 있는 임시 가공물. 핵심 데이터셋 자체는 Standard에 두고, 핫 워킹셋만 Express One Zone으로 미러링하는 2-tier 패턴이 안전하다.
📖 더 보기: Amazon S3 Express One Zone 공식 페이지 — 고성능 스토리지 클래스 사양, 가격, 사용 사례 (중급)