S3 vs RDS 데이터 저장 선택 기준
TL;DR
- S3 vs RDS 데이터 저장 선택 기준의 핵심 개념과 사용 범위를 한눈에 정리
- 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
- 주요 특징과 체크리스트를 빠르게 확인
1. 개념
┌─────────────────────────────────────────────────────────────┐ │ │ │ 핵심 질문: "이 데이터로 무엇을 할 것인가?" │ │ │ │ S3 (Object Storage): │ │ ├── 저장하고 → 그대로 꺼내기 │ │ ├── Key-Value 형태 (key = 경로, value = 파일) │ │ ├── 부분 수정 불가 (전체 덮어쓰기) │ │ └── 비유: "파일 서랍", "창고" │ │ │ │ RDS (Relational Database): │ │ ├── 저장하고 → 검색/수정/조인/집계 │ │ ├── 테이블/행/열 구조 │ │ ├── 부분 수정 가능 (특정 필드만) │ │ └── 비유: "엑셀 시트", "장부" │ │ │ └─────────────────────────────────────────────────────────────┘
2. 배경
S3 vs RDS 데이터 저장 선택 기준이(가) 등장한 배경과 기존 한계를 정리한다.
3. 이유
이 주제를 이해하고 적용해야 하는 이유를 정리한다.
4. 특징
- S3와 RDS의 본질적 차이
- RDS를 선택해야 할 때
- S3를 선택해야 할 때
- 일반적인 선택
- 특수 케이스
5. 상세 내용
작성일: 2026-02-09 카테고리: Cloud / AWS / Architecture 포함 내용: S3, RDS, 데이터 저장 전략, Object Storage, Relational Database, 메타데이터, 파일 저장, 비용 비교
1. 핵심 개념
S3와 RDS의 본질적 차이
┌─────────────────────────────────────────────────────────────┐
│ │
│ 핵심 질문: "이 데이터로 무엇을 할 것인가?" │
│ │
│ S3 (Object Storage): │
│ ├── 저장하고 → 그대로 꺼내기 │
│ ├── Key-Value 형태 (key = 경로, value = 파일) │
│ ├── 부분 수정 불가 (전체 덮어쓰기) │
│ └── 비유: "파일 서랍", "창고" │
│ │
│ RDS (Relational Database): │
│ ├── 저장하고 → 검색/수정/조인/집계 │
│ ├── 테이블/행/열 구조 │
│ ├── 부분 수정 가능 (특정 필드만) │
│ └── 비유: "엑셀 시트", "장부" │
│ │
└─────────────────────────────────────────────────────────────┘
2. 판단 기준 체크리스트
RDS를 선택해야 할 때
┌─────────────────────────────────────────────────────────────┐
│ │
│ 다음 질문에 "예"가 많으면 → RDS │
│ │
│ □ 트랜잭션이 필요한가? (ACID) │
│ 예: 계좌 이체, 주문 처리, 재고 차감 │
│ │
│ □ 복잡한 쿼리가 필요한가? │
│ 예: JOIN, GROUP BY, WHERE 조건 검색, 정렬 │
│ │
│ □ 데이터 간 관계가 있는가? │
│ 예: 사용자-주문-상품, 부서-직원 │
│ │
│ □ 부분 수정이 빈번한가? │
│ 예: 사용자 이름만 변경, 재고 수량 감소 │
│ │
│ □ 실시간 정합성이 중요한가? │
│ 예: 재고, 잔액, 예약 상태, 좌석 현황 │
│ │
│ □ 인덱스 기반 빠른 검색이 필요한가? │
│ 예: 이메일로 사용자 찾기, 날짜 범위 조회 │
│ │
└─────────────────────────────────────────────────────────────┘
S3를 선택해야 할 때
┌─────────────────────────────────────────────────────────────┐
│ │
│ 다음 질문에 "예"가 많으면 → S3 │
│ │
│ □ 파일 단위로 저장/조회하는가? │
│ 예: 이미지, PDF, 동영상, ZIP │
│ │
│ □ 한번 쓰고 거의 수정 없는가? (Immutable) │
│ 예: 로그, 백업, 아카이브, 감사 기록 │
│ │
│ □ 크기가 큰가? (수 MB 이상) │
│ 예: 동영상, 대용량 CSV, ML 모델, 데이터셋 │
│ │
│ □ 직접 다운로드/스트리밍 하는가? │
│ 예: 웹에서 이미지 보기, 파일 다운로드 링크 │
│ │
│ □ 저렴한 비용이 중요한가? │
│ 예: 대용량 장기 보관, 콜드 데이터 │
│ │
│ □ CDN과 연동하는가? │
│ 예: 정적 자산, 미디어 스트리밍 │
│ │
└─────────────────────────────────────────────────────────────┘
3. 데이터 유형별 선택 가이드
일반적인 선택
| 데이터 유형 | S3 | RDS | 이유 |
|---|---|---|---|
| 사용자 정보 (이름, 이메일) | ✅ | 검색/수정 빈번, 관계 | |
| 프로필 이미지 | ✅ | 파일, 수정 드묾 | |
| 주문 내역 | ✅ | 트랜잭션, 관계, 검색 | |
| 주문 첨부 파일 | ✅ | 파일 | |
| 상품 정보 | ✅ | 검색, 필터, 정렬 | |
| 상품 이미지 | ✅ | 파일, CDN 연동 | |
| 결제 기록 | ✅ | 트랜잭션, ACID 필수 | |
| 영수증 PDF | ✅ | 파일, 다운로드 | |
| 애플리케이션 로그 | ✅ | 대용량, Immutable | |
| 감사 로그 (검색 필요) | ✅ | 쿼리 필요 | |
| ML 모델 파일 | ✅ | 대용량 바이너리 | |
| ML 학습 메타데이터 | ✅ | 검색, 비교, 버전 관리 | |
| 백업/아카이브 | ✅ | 장기 보관, 저렴 | |
| 설정/Config | ✅ | 빈번한 조회/수정 | |
| 정적 웹 자산 | ✅ | 파일, CDN |
특수 케이스
| 데이터 유형 | 추천 | 이유 |
|---|---|---|
| 세션 데이터 | Redis/ElastiCache | 빠른 접근, TTL |
| 캐시 | Redis/ElastiCache | 빠른 접근 |
| 검색 인덱스 | OpenSearch | 전문 검색 |
| 시계열 데이터 | TimeStream/InfluxDB | 시계열 최적화 |
| 그래프 관계 | Neptune | 그래프 쿼리 |
4. 흔한 패턴: 메타데이터 + 파일 분리
패턴 설명
┌─────────────────────────────────────────────────────────────┐
│ │
│ 핵심 원칙: │
│ ├── 메타데이터 (검색 가능해야 하는 정보) → RDS │
│ └── 실제 파일 (바이너리 데이터) → S3 │
│ │
│ RDS에는 S3 경로(key)만 저장 │
│ 실제 파일은 S3에서 직접 다운로드 │
│ │
└─────────────────────────────────────────────────────────────┘
예시: 게시글 + 첨부파일
┌─────────────────────────────────────────────────────────────┐
│ │
│ RDS (posts 테이블): │
│ ┌────────────────────────────────────────────────────┐ │
│ │ id │ title │ content │ author_id │ created_at │ │
│ ├────┼──────────┼──────────┼───────────┼────────────┤ │
│ │ 1 │ "안녕" │ "본문" │ 42 │ 2026-02-09 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ RDS (attachments 테이블): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ id │ post_id │ filename │ s3_key │ size │ │
│ ├────┼─────────┼─────────────┼─────────────────┼──────┤ │
│ │ 1 │ 1 │ "image.png" │ "posts/1/a.png" │ 2MB │ │
│ │ 2 │ 1 │ "doc.pdf" │ "posts/1/b.pdf" │ 5MB │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ S3 (my-bucket): │
│ ├── posts/1/a.png ← 실제 파일 │
│ └── posts/1/b.pdf ← 실제 파일 │
│ │
└─────────────────────────────────────────────────────────────┘
조회 흐름
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. 클라이언트 요청: GET /posts/1 │
│ │
│ 2. 서버: RDS에서 post + attachments 조회 (JOIN) │
│ SELECT p.*, a.* FROM posts p │
│ LEFT JOIN attachments a ON p.id = a.post_id │
│ WHERE p.id = 1 │
│ │
│ 3. 서버: 각 attachment의 s3_key로 Presigned URL 생성 │
│ s3.getSignedUrl('getObject', { │
│ Bucket: 'my-bucket', │
│ Key: 'posts/1/a.png', │
│ Expires: 3600 // 1시간 │
│ }) │
│ │
│ 4. 응답: │
│ { │
│ "id": 1, │
│ "title": "안녕", │
│ "attachments": [ │
│ { │
│ "filename": "image.png", │
│ "url": "https://s3...?X-Amz-Signature=..." │
│ } │
│ ] │
│ } │
│ │
│ 5. 클라이언트: Presigned URL로 S3에서 직접 다운로드 │
│ │
└─────────────────────────────────────────────────────────────┘
5. 속도(레이턴시) 비교
레이턴시 비교
┌─────────────────────────────────────────────────────────────┐
│ │
│ 같은 VPC 내 레이턴시: │
│ │
│ Redis (ElastiCache): < 1ms ████ │
│ RDS (PostgreSQL): 1-10ms ████████ │
│ S3: 50-200ms ████████████████████████ │
│ │
│ 순서: Redis > RDS > S3 │
│ │
└─────────────────────────────────────────────────────────────┘
왜 이런 차이가 나는가?
┌─────────────────────────────────────────────────────────────┐
│ │
│ Redis: │
│ ├── 인메모리 (RAM에 저장) │
│ ├── 디스크 I/O 없음 │
│ ├── 단순 Key-Value 조회 │
│ └── < 1ms 가능 │
│ │
│ RDS: │
│ ├── 디스크 기반이지만... │
│ ├── 인덱스 + 버퍼 풀 캐싱 (자주 쓰는 데이터는 RAM) │
│ ├── 연결 유지 (Connection Pool) │
│ ├── 바이너리 프로토콜 (효율적) │
│ └── 1-10ms 수준 │
│ │
│ S3: │
│ ├── HTTP 기반 REST API 호출 │
│ ├── 매 요청마다 AWS Signature v4 서명 │
│ ├── 분산 스토리지에서 객체 위치 파악 │
│ ├── 파일 단위 전송 (작은 데이터도 오버헤드) │
│ └── 50-200ms 수준 │
│ │
└─────────────────────────────────────────────────────────────┘
S3가 느린 이유 (상세)
┌─────────────────────────────────────────────────────────────┐
│ │
│ S3 조회 과정: │
│ 1. HTTP 요청 생성 │
│ 2. AWS Signature v4 서명 계산 │
│ 3. DNS 조회 │
│ 4. TLS 핸드셰이크 │
│ 5. S3 서비스 라우팅 │
│ 6. 분산 스토리지에서 객체 위치 파악 │
│ 7. 객체 조회 및 전송 │
│ 8. HTTP 응답 │
│ │
│ → 작은 데이터(수 KB)도 전체 과정 필요 │
│ → 파일 크기와 무관한 기본 오버헤드 존재 │
│ │
│ RDS 조회 과정: │
│ 1. Connection Pool에서 연결 획득 (이미 연결됨) │
│ 2. SQL 쿼리 전송 (바이너리 프로토콜) │
│ 3. 인덱스/캐시에서 데이터 조회 │
│ 4. 결과 반환 │
│ │
│ → 연결 유지로 오버헤드 최소화 │
│ → 인덱스로 빠른 조회 │
│ │
└─────────────────────────────────────────────────────────────┘
용도별 최적 저장소
| 용도 | 최적 | 레이턴시 | 이유 |
|---|---|---|---|
| 세션/캐시 | Redis | < 1ms | 인메모리, TTL 지원 |
| 정형 데이터 조회 | RDS | 1-10ms | 인덱스, 쿼리 |
| 파일 저장/다운로드 | S3 | 50-200ms | 대용량, 저렴 |
| 정적 웹 자산 | S3 + CloudFront | ~10ms | CDN 캐싱 |
6. 비용 비교
저장 비용
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1TB 데이터 월간 저장 비용 (서울 리전 기준): │
│ │
│ S3 Standard: ~$23/월 │
│ S3 Infrequent Access: ~$13/월 │
│ S3 Glacier: ~$4/월 │
│ │
│ RDS (db.r5.large + 1TB gp3): │
│ ├── 인스턴스: ~$180/월 │
│ ├── 스토리지: ~$115/월 │
│ └── 합계: ~$295/월 │
│ │
│ 비용 차이: S3가 약 13배 저렴 │
│ │
│ → 쿼리 필요 없는 대용량 데이터는 S3가 경제적 │
│ → 하지만 쿼리 필요하면 RDS 비용은 정당화됨 │
│ │
└─────────────────────────────────────────────────────────────┘
전송 비용
┌─────────────────────────────────────────────────────────────┐
│ │
│ 데이터 전송 비용: │
│ │
│ 같은 리전 내: │
│ ├── S3 → EC2/EKS: 무료 │
│ └── RDS → EC2/EKS: 무료 │
│ │
│ 인터넷으로: │
│ ├── S3: $0.09/GB (처음 10TB) │
│ └── RDS: 직접 노출 안 함 (앱 통해 전송) │
│ │
│ → S3 + CloudFront 조합이 대용량 파일 전송에 유리 │
│ │
└─────────────────────────────────────────────────────────────┘
7. 안티패턴
하지 말아야 할 것들
┌─────────────────────────────────────────────────────────────┐
│ │
│ ❌ 이미지를 RDS BLOB에 저장 │
│ │
│ 문제: │
│ ├── DB 부하 증가 (I/O, 메모리) │
│ ├── 백업 느려짐 │
│ ├── 비용 폭탄 (RDS 스토리지 비쌈) │
│ └── 확장성 제한 │
│ │
│ 해결: S3에 파일 저장 + RDS에 URL/경로만 저장 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ │
│ ❌ 로그를 RDS에 무한 적재 │
│ │
│ 문제: │
│ ├── 테이블 비대화 │
│ ├── 쿼리 성능 저하 │
│ ├── 백업 시간 증가 │
│ └── 비용 증가 │
│ │
│ 해결: │
│ ├── S3에 로그 저장 + Athena로 분석 │
│ ├── 또는 OpenSearch/CloudWatch Logs 사용 │
│ └── RDS에는 최근 N일만 유지 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ │
│ ❌ JSON 전체를 S3에 저장하고 검색하려 함 │
│ │
│ 문제: │
│ ├── 검색할 때마다 전체 파일 다운로드 필요 │
│ ├── 인덱스 없음 → 느린 검색 │
│ └── 부분 수정 불가 │
│ │
│ 해결: │
│ ├── 검색 필요하면 RDS 또는 DynamoDB │
│ ├── 분석용이면 S3 + Athena │
│ └── 전문 검색 필요하면 OpenSearch │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ │
│ ❌ 정형 데이터를 CSV로 S3에만 저장 (운영용) │
│ │
│ 문제: │
│ ├── JOIN 불가 │
│ ├── 트랜잭션 불가 │
│ ├── 부분 수정 불가 │
│ └── 실시간 쿼리 어려움 │
│ │
│ 해결: │
│ ├── 분석/배치용이면 S3 + Athena OK │
│ ├── 운영 데이터면 RDS 사용 │
│ └── ETL로 RDS → S3 아카이빙 │
│ │
└─────────────────────────────────────────────────────────────┘
8. 의사결정 플로우차트
┌─────────────────────────────────────────────────────────────┐
│ │
│ 데이터 저장 위치 결정: │
│ │
│ ┌──────────────────────┐ │
│ │ 트랜잭션 필요? │ │
│ └──────────┬───────────┘ │
│ Yes │ No │
│ ↓ └──────────────────┐ │
│ [RDS] │ │
│ ┌────────────┴────────────┐ │
│ │ 복잡한 쿼리/검색 필요? │ │
│ └────────────┬────────────┘ │
│ Yes │ No │
│ ↓ └─────────────┐ │
│ [RDS] │ │
│ ┌────────────┴───────────┐ │
│ │ 파일/바이너리 데이터? │ │
│ └────────────┬───────────┘ │
│ Yes │ No │
│ ↓ │ │
│ [S3] │ │
│ ┌────────────┴───────────┐ │
│ │ 수정 빈번? 크기 작음? │ │
│ └────────────┬───────────┘ │
│ Yes │ No │
│ ↓ ↓ │
│ [RDS] [S3] │
│ │
└─────────────────────────────────────────────────────────────┘
9. K8s 환경에서의 실제 구성
아키텍처
┌─────────────────────────────────────────────────────────────┐
│ │
│ EKS Cluster │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Pod (App) │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 1. 메타데이터 CRUD → RDS │ │ │
│ │ │ 2. 파일 업로드/다운로드 → S3 │ │ │
│ │ │ 3. Presigned URL 생성 → 클라이언트 전달 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │ │ │
│ │ │ SQL │ AWS SDK │ │
│ │ ▼ ▼ │ │
│ └───────┼────────────────────────┼────────────────────┘ │
│ │ │ │
│ ┌───────┴───────┐ ┌───────┴───────┐ │
│ │ RDS │ │ S3 │ │
│ │ (PostgreSQL) │ │ (Bucket) │ │
│ │ │ │ │ │
│ │ - users │ │ - images/ │ │
│ │ - orders │ │ - documents/ │ │
│ │ - products │ │ - logs/ │ │
│ │ - files_meta │ │ │ │
│ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
환경 변수 설정 예시
# deployment.yaml
env:
# RDS 접속 정보
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
# S3 버킷 정보
- name: S3_BUCKET
value: "my-app-files"
- name: AWS_REGION
value: "ap-northeast-2"
# IRSA로 S3 권한 부여 (IAM Role)
# → AWS SDK가 자동으로 자격 증명 획득
관련 키워드
S3, RDS, Redis, ElastiCache, Object Storage, Relational Database, 데이터 저장 전략, 메타데이터, 파일 저장, Presigned URL, BLOB, 비용 비교, 레이턴시, 안티패턴, EKS, AWS