TL;DR
- **샤딩 = 수평 확장의 답**: 단일 DB로 안 되는 규모에서 필수
- **샤드 키 선택이 운명**: 잘못 선택하면 hot spot, 재샤딩 지옥
- **3대 전략**: Range, Hash, Directory (Lookup) — 각각 장단점
- **분산 트랜잭션이 어려움**: 2PC는 느림, Saga가 현실적
- **Vitess가 표준**: YouTube/Slack/GitHub이 사용. MySQL 위의 분산 레이어
1. 샤딩이란?
1.1 단일 DB의 한계
[Single DB]
├─ CPU: 1 머신만큼만
├─ RAM: 256GB? 512GB?
├─ 디스크: 30TB
└─ 네트워크: 25 Gbps
→ 결국 한계에 도달
**수직 확장 (Vertical Scaling)**:
- 더 큰 머신
- 한계 있음 (가장 큰 EC2 = 24TB RAM, $20+/시간)
- 단일 장애점
**수평 확장 (Horizontal Scaling)**:
- 여러 머신
- 무한 확장
- 복잡도 증가
1.2 Read Replica로 해결?
[Primary] (쓰기)
↓ replication
[Replica 1] [Replica 2] [Replica 3] (읽기)
**도움**: 읽기 트래픽 분산.
**한계**: **쓰기는 여전히 Primary 1대**. 쓰기가 병목이면 도움 안 됨.
→ **샤딩**이 필요합니다.
1.3 샤딩의 정의
**샤딩** = 데이터를 여러 독립적 DB(샤드)에 분산.
[users 테이블]
↓ shard by user_id
[Shard 1: user_id 1-1M]
[Shard 2: user_id 1M-2M]
[Shard 3: user_id 2M-3M]
...
각 샤드는 **완전히 독립**적인 DB. 자체 CPU, RAM, 디스크.
1.4 샤딩의 효과
| | 단일 DB | 100 샤드 |
|---|---|---|
| 데이터 | 1 TB | 100 TB |
| 쓰기 TPS | 10,000 | 1,000,000 |
| 비용 | $10K/월 | $50K/월 |
| 복잡도 | 낮음 | **매우 높음** |
**100배 성능 + 5배 비용 = ROI 좋음**. 단, **복잡도가 큰 문제**.
2. 샤드 키 선택 — 가장 중요한 결정
2.1 좋은 샤드 키의 조건
1. **균등 분포** — 데이터가 모든 샤드에 균등 분산
2. **불변** — 한 번 정해지면 거의 안 변함
3. **쿼리 패턴 일치** — 자주 함께 조회되는 데이터는 같은 샤드
4. **카디널리티 높음** — 충분히 많은 고유 값
5. **단조 증가 X** — auto-increment ID는 hot spot 위험
2.2 흔한 샤드 키 후보
**user_id**:
- ✅ 사용자 데이터를 한 샤드에 모음 (관련 데이터 같이)
- ❌ 큰 사용자가 hot spot
- ❌ user 간 JOIN 어려움
**created_at**:
- ❌ 단조 증가 → 모든 새 데이터가 한 샤드에 (hot spot)
- 시계열 데이터에는 OK
**hash(id)**:
- ✅ 균등 분포
- ❌ 범위 쿼리 불가능
- ❌ 관련 데이터가 다른 샤드
**country**:
- ✅ Geographic 효율
- ❌ 매우 불균등 (US가 거대, 모나코는 작음)
**organization_id**:
- ✅ B2B SaaS에 이상적
- ❌ 큰 조직이 hot spot
2.3 Slack의 사례
Slack의 **샤드 키 = workspace_id**.
**이유**:
- 같은 workspace의 모든 데이터를 한 샤드에 → 빠른 조회
- workspace 간 JOIN 거의 없음
- workspace 단위로 자연스러운 격리
**단점**: 거대 enterprise workspace가 단일 샤드 압도 → "Whale shard" 문제.
**해결**: 거대 workspace는 자체 전용 샤드.
3. 샤딩 전략
3.1 Range Sharding
데이터를 키의 **범위**로 분할.
Shard 1: id 1 ~ 1,000,000
Shard 2: id 1,000,001 ~ 2,000,000
Shard 3: id 2,000,001 ~ 3,000,000
**장점**:
- 범위 쿼리 효율 (`WHERE id BETWEEN 500000 AND 600000`)
- 단순한 라우팅
- 새 샤드 추가 쉬움
**단점**:
- **Hot spot 위험**: 단조 증가 키면 새 데이터가 모두 마지막 샤드에
- 불균등 분포 가능
**사용 사례**: 시계열 데이터, BigTable, HBase
3.2 Hash Sharding
샤드 키의 **hash**로 분할.
shard_id = hash(user_id) % num_shards
**장점**:
- **균등 분포** — hash가 잘 만들어졌으면
- 단조 증가 키도 안전
**단점**:
- 범위 쿼리 불가능 (`WHERE id BETWEEN ...`)
- 새 샤드 추가 시 거의 모든 데이터 재분배 (`% num_shards`가 변하니까)
**해결**: **Consistent Hashing** — 샤드 추가/제거 시 일부 데이터만 이동.
3.3 Consistent Hashing
[원형 해시 공간]
Shard A
/
Shard D
\
Shard B
/
Shard C
각 샤드와 데이터를 같은 해시 공간에 매핑. 데이터는 시계방향으로 가장 가까운 샤드에.
**샤드 추가 시**: 해당 부분만 재분배 (전체의 1/N).
**Virtual Nodes**: 각 샤드를 여러 가상 노드로 표현 → 더 균등.
**사용**: Cassandra, DynamoDB, Riak, Memcached.
3.4 Directory-Based Sharding
**Lookup 테이블**로 어떤 데이터가 어떤 샤드에 있는지 명시적 매핑.
[Lookup]
user_1 → Shard A
user_2 → Shard B
user_3 → Shard A
...
**장점**:
- **유연**: 임의의 매핑 가능
- 균등하지 않은 분포도 처리
- 마이그레이션 쉬움
**단점**:
- **Lookup 자체가 병목** (캐싱 필수)
- 추가 인프라 (Redis, Consul)
- 단일 장애점
**사용**: Vitess (이런 방식의 변형)
3.5 Geographic Sharding
위치별 샤드:
- US 사용자 → US 샤드
- EU 사용자 → EU 샤드
- Asia 사용자 → Asia 샤드
**장점**:
- 사용자에 가까이 → 낮은 latency
- 규제 준수 (GDPR — EU 데이터를 EU에)
- 자연스러운 분할
**단점**:
- 사용자 이동 시 어려움
- 글로벌 쿼리 어려움
- 매우 불균등 (지역별 사용자 수)
3.6 비교표
| 전략 | 균등성 | 범위 쿼리 | 추가 | 쿼리 패턴 |
|---|---|---|---|---|
| **Range** | 낮음 | ✅ | 쉬움 | 범위 |
| **Hash** | 높음 | ❌ | 어려움 | 정확 |
| **Consistent Hash** | 높음 | ❌ | **쉬움** | 정확 |
| **Directory** | 높음 | 부분 | 쉬움 | 자유 |
| **Geographic** | 낮음 | 부분 | 보통 | 지역 |
4. Hot Spot 문제
4.1 Hot Spot이란?
특정 샤드에 트래픽/데이터가 집중되는 현상.
**원인**:
- 잘못된 샤드 키 (인기 사용자, 인기 상품)
- 단조 증가 키 (created_at)
- 불균등 분포 (몇몇 거대 조직)
4.2 영향
[Shard 1: 10% 트래픽]
[Shard 2: 5% 트래픽]
[Shard 3: 80% 트래픽] ← Hot spot! 죽음
[Shard 4: 5% 트래픽]
전체 시스템이 **가장 약한 샤드에 의해 결정**됩니다.
4.3 탐지
-- 샤드별 데이터 양
SELECT shard_id, COUNT(*) as row_count
FROM users
GROUP BY shard_id;
-- 샤드별 쿼리 수 (애플리케이션 메트릭)
**경고 신호**:
- 한 샤드가 평균보다 5배+ 큼
- 한 샤드의 CPU/IO가 100%
4.4 해결책
**1. 샤드 키 변경**
- 가장 좋지만 가장 어려움 (재샤딩 필요)
**2. Composite Key**
shard_key = hash(user_id + timestamp_bucket)
시간 기반 + 사용자 기반 결합.
**3. 하위 샤딩 (Sub-sharding)**
- Hot 샤드를 더 잘게 분할
- "Whale shard" 처리
**4. 캐싱**
- 핫 데이터를 Redis로
- DB 부하 감소
**5. 읽기 복제본**
- 읽기 부하 분산
- 쓰기는 여전히 hot
4.5 예시: Twitter의 Celebrity Problem
**문제**: 한 셀럽 사용자(예: 일론 머스크)가 트윗 → 수억 followers의 timeline 업데이트.
**해결**:
- **Fan-out on write** (일반 사용자): 트윗 시 followers의 timeline에 미리 작성
- **Fan-in on read** (셀럽): 트윗 시 timeline 안 만듦, 읽을 때 가져옴
- **하이브리드**: 활성 followers는 fan-out, 비활성은 fan-in
5. 분산 쿼리의 어려움
5.1 단일 샤드 쿼리
SELECT * FROM users WHERE user_id = 12345;
→ user_id로 샤드 결정 → 해당 샤드만 쿼리. **매우 빠름**.
5.2 Cross-Shard 쿼리
SELECT COUNT(*) FROM users WHERE country = 'KR';
→ **모든 샤드에 쿼리** → 결과 합산. **느림 + 부하**.
5.3 JOIN의 어려움
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.user_id
WHERE u.country = 'KR';
users와 orders가 다른 샤드에 있으면? **분산 JOIN** 필요.
**전략**:
1. **Co-location**: 같은 user_id의 users와 orders를 같은 샤드에
2. **Denormalization**: orders에 user 정보 복사
3. **분산 쿼리 엔진**: Vitess, Citus가 자동 처리
4. **CQRS**: 쿼리용 별도 DW (Snowflake) 사용
5.4 분산 트랜잭션
def transfer(from_user, to_user, amount):
두 사용자가 다른 샤드에 있을 수 있음
db.update(f"... WHERE user_id={from_user}", -amount)
db.update(f"... WHERE user_id={to_user}", +amount)
**문제**: 첫 update 성공 + 두 번째 실패 → 돈 사라짐.
**해결**:
**1. 2PC (Two-Phase Commit)**:
- Prepare → Commit
- 모든 샤드가 동의해야 commit
- **느림 + 단일 장애점** → 비추천
**2. Saga 패턴**:
- 보상 트랜잭션
- Eventually consistent
- 실전 표준
**3. 같은 샤드에 보장**:
- 가능하면 전송자/수신자를 같은 샤드에
- 항상 가능하지는 않음
6. 리샤딩 — 가장 무서운 작업
6.1 왜 리샤딩이 어려운가?
- **데이터 이동**: 수십 TB 이동
- **무중단**: 사용자 영향 X
- **롤백**: 문제 시 복구
- **일관성**: 이동 중 쓰기/읽기 처리
6.2 리샤딩 시나리오
**1. 샤드 추가**: 4 샤드 → 8 샤드
**2. 샤드 분할**: 거대 샤드를 둘로
**3. 샤드 병합**: 작은 샤드 합치기
**4. 샤드 키 변경**: 가장 어려움
6.3 리샤딩 패턴
**1. Bulk Copy + Switchover**:
1. 새 샤드에 데이터 복사
2. 짧은 maintenance window
3. 트래픽 전환
4. 옛 샤드 삭제
**단점**: 다운타임.
**2. CDC + Dual Write**:
1. CDC로 옛 → 새 샤드 동기화
2. 애플리케이션이 옛 + 새 둘 다 쓰기 (dual write)
3. 데이터 일치 확인
4. 읽기를 새로 전환
5. 옛 샤드 폐기
**무중단**, 복잡.
**3. Online Migration**:
- Vitess의 VReplication
- 자동화된 무중단 마이그레이션
- 진행률 모니터링
6.4 사례: Slack의 샤딩 진화
- **2017**: 단일 MySQL → 복제본
- **2018**: 첫 샤딩 (workspace_id)
- **2020**: Vitess 도입
- **2022**: Whale workspace 처리
- **2024**: 수천 샤드, 페타바이트
**교훈**: 샤딩은 한 번에 끝나지 않음. **지속적 진화**.
7. Vitess — MySQL 위의 분산 레이어
7.1 Vitess란?
YouTube가 만든 MySQL 샤딩 솔루션. **CNCF 졸업**.
[Client]
↓
[VTGate] (쿼리 라우터)
↓
[VTTablet] [VTTablet] [VTTablet]
↓ ↓ ↓
[MySQL] [MySQL] [MySQL]
**장점**:
- **MySQL 호환** — 기존 코드 그대로
- **자동 샤딩** — 키 기반 라우팅
- **스키마 마이그레이션** — 안전한 DDL
- **VReplication** — 무중단 리샤딩
- **검증된 규모** — YouTube, Slack, GitHub, Square
7.2 사용 회사
- **YouTube** (창시자, 페타바이트)
- **Slack** (대규모)
- **GitHub** (MySQL → Vitess 전환)
- **Square**
- **Pinterest**
- **JD.com** (중국 최대 e-commerce)
7.3 VSchema 예시
{
"sharded": true,
"vindexes": {
"hash": {
"type": "hash"
}
},
"tables": {
"users": {
"column_vindexes": [
{
"column": "user_id",
"name": "hash"
}
]
}
}
}
→ VTGate가 자동으로 user_id 기반 샤딩.
8. 다른 분산 DB 옵션
8.1 Citus (PostgreSQL)
PostgreSQL 확장. 분산 쿼리, 분산 트랜잭션 지원.
SELECT create_distributed_table('events', 'user_id');
**장점**: PostgreSQL 호환, SQL 그대로.
**단점**: PostgreSQL 한계 (Vitess보다 성숙도 낮음).
8.2 CockroachDB
처음부터 분산 SQL DB로 설계.
**장점**:
- **PostgreSQL 호환**
- **자동 샤딩** (range-based)
- **글로벌 분산** 지원
- **분산 ACID 트랜잭션**
**단점**:
- 일부 PG 기능 미지원
- 더 비싼 운영
- 학습 곡선
**사용**: DoorDash, Bose, Comcast.
8.3 YugabyteDB
CockroachDB와 비슷. **PostgreSQL 호환 + Cassandra 호환** 동시.
8.4 TiDB
**MySQL 호환** + 자동 분산. 중국에서 인기.
8.5 비교표
| | Vitess | Citus | CockroachDB | YugabyteDB | TiDB |
|---|---|---|---|---|---|
| **기반** | MySQL | PostgreSQL | 자체 | 자체 | 자체 |
| **호환성** | MySQL | PostgreSQL | PostgreSQL | PG + CQL | MySQL |
| **분산 트랜잭션** | 제한 | ✅ | ✅ | ✅ | ✅ |
| **자동 샤딩** | 수동 키 | 수동 키 | **자동** | 자동 | 자동 |
| **운영 난이도** | 보통 | 낮음 | 보통 | 보통 | 보통 |
| **검증** | YouTube, Slack | 다수 | DoorDash | 다수 | 중국 큰 회사 |
9. 실전 — e-commerce 샤딩 설계
9.1 시나리오
- 1억 사용자
- 일일 1000만 주문
- 단일 PostgreSQL 한계 도달
9.2 분석
**테이블**:
- `users` (1억 행)
- `orders` (10억 행)
- `products` (100만 행)
- `reviews` (50억 행)
9.3 샤딩 결정
**1. users**:
- 샤드 키: `user_id` (hash)
- 16개 샤드
- 균등 분포
**2. orders**:
- 샤드 키: `user_id` (hash, **users와 같은 샤드**)
- 같은 사용자의 user + orders가 같은 샤드 → JOIN 빠름
**3. products**:
- 작음 (100만 행)
- **샤딩 X** — 단일 DB + 읽기 복제본
- 또는 모든 샤드에 복제
**4. reviews**:
- 샤드 키: `product_id` (hash)
- product 기준 분석에 적합
9.4 쿼리 패턴
-- 사용자 주문 (같은 샤드)
SELECT * FROM orders WHERE user_id = 12345;
-- ✅ 빠름
-- 사용자 정보 + 주문 (같은 샤드, JOIN OK)
SELECT u.*, o.*
FROM users u JOIN orders o ON o.user_id = u.user_id
WHERE u.user_id = 12345;
-- ✅ 빠름
-- 제품 리뷰 (다른 샤드)
SELECT * FROM reviews WHERE product_id = 67890;
-- ✅ 빠름
-- 사용자가 작성한 모든 리뷰 (cross-shard)
SELECT * FROM reviews WHERE user_id = 12345;
-- ⚠️ 느림 — 모든 reviews 샤드 검색
-- 해결: 비정규화. users 샤드에도 review 요약 저장.
9.5 인프라
[Application]
↓
[ProxySQL / Vitess]
↓
[16 user shards]
[16 order shards (co-located)]
[1 products DB + 5 read replicas]
[8 reviews shards]
9.6 모니터링
- 샤드별 쿼리 수
- 샤드별 디스크 사용량
- Cross-shard 쿼리 비율
- Hot spot 탐지
10. 샤딩의 함정과 교훈
10.1 너무 일찍 샤딩
**잘못된 결정**: 트래픽이 1000 QPS인데 샤딩.
**현실**:
- 단일 PostgreSQL = 50,000 QPS+
- Read replica = 200,000 QPS+
- 적절한 인덱스 + 캐싱 = 더 많이
→ **샤딩 전에 다른 모든 최적화를 먼저 시도**하세요.
10.2 잘못된 샤드 키
GitHub의 **첫 샤딩 시도**가 실패한 사례:
- repo_id로 샤딩 → 인기 repo가 hot spot
- Linus의 linux 저장소가 단일 샤드 압도
- **재샤딩 필요** (수개월의 작업)
**교훈**: 샤드 키는 **변경하기 매우 어렵다**. 처음에 신중히.
10.3 분산 트랜잭션 회피
분산 트랜잭션은 정말 어려움. 가능한 한 **같은 샤드 안에서만 트랜잭션** 보장.
**설계 시**: 쓰기 트랜잭션이 항상 단일 샤드 안에 들어가도록 데이터 모델 설계.
10.4 운영 복잡도
샤딩한 시스템은:
- 백업 복잡 (16개 샤드 동시)
- 모니터링 복잡
- 디버깅 어려움 (어느 샤드?)
- 마이그레이션 어려움
→ **자동화** 필수. **운영 인력** 충분히.
10.5 NewSQL이 답?
CockroachDB, YugabyteDB 같은 NewSQL은 자동 샤딩 + 분산 트랜잭션. **수동 샤딩의 복잡도 없음**.
**언제 NewSQL?**:
- 새 프로젝트
- 강한 일관성 필요
- 운영 인력 부족
**언제 수동 샤딩?**:
- 기존 MySQL/PostgreSQL
- 극단적 성능 필요
- NewSQL 성숙도 우려
퀴즈
**답**: Read replica는 **읽기**만 분산합니다. **쓰기는 여전히 Primary 1대**가 처리. 쓰기가 병목인 시스템 (예: 일일 10억 INSERT)은 read replica로 해결 불가능. 샤딩만이 **쓰기를 여러 머신에 분산**할 수 있습니다. 단, 샤딩은 복잡도가 매우 높으므로 read replica + 캐싱 + 인덱스 최적화로 해결 가능한지 먼저 검토해야 합니다.
**답**: **장점**: 균등 분포 (hash가 잘 만들어졌으면). 단조 증가 키도 안전. **단점**: 범위 쿼리 불가능 (`WHERE id BETWEEN ...`). **샤드 추가 시 거의 모든 데이터 재분배** (`% num_shards`가 변하므로). 해결책: **Consistent Hashing** — 샤드 추가/제거 시 일부 데이터(1/N)만 이동. Cassandra, DynamoDB, Riak가 사용. Virtual nodes로 더 균등한 분포.
**답**: (1) **샤드 키 변경** — 가장 좋지만 재샤딩 필요 (어려움), (2) **Composite key** — `hash(user_id + timestamp_bucket)`로 시간/사용자 분산, (3) **하위 샤딩** — Hot 샤드를 더 잘게 분할 ("whale shard"), (4) **캐싱** — 핫 데이터를 Redis로, (5) **읽기 복제본** — 읽기 부하 분산. 가장 흔한 hot spot: 인기 사용자, 인기 상품, 단조 증가 키. **샤드 키 선택 시 반드시 hot spot 시나리오 검토**.
**답**: (1) **YouTube에서 시작** — 거대한 규모에서 검증됨, (2) **MySQL 호환** — 기존 코드/도구 그대로 사용, (3) **자동 샤딩** — 키 기반 라우팅, (4) **VReplication** — 무중단 마이그레이션과 리샤딩, (5) **CNCF 졸업** — 클라우드 네이티브 표준, (6) **검증된 사용자** — Slack, GitHub, Square. 단점은 분산 트랜잭션이 약한 편 (Saga 패턴 권장). PostgreSQL 사용자는 Citus가 유사한 역할.
**답**: **NewSQL (CockroachDB, YugabyteDB) 선택**: 새 프로젝트, 강한 일관성 필요 (분산 ACID), 운영 인력 부족, PostgreSQL 호환 가능. **수동 샤딩 (Vitess + MySQL) 선택**: 기존 MySQL 시스템, 극단적 성능 필요, NewSQL 성숙도 우려, 더 큰 통제력 원함. **현실**: 새 시스템은 점점 NewSQL로, 기존 거대 시스템은 Vitess 유지. 둘 다 valid한 선택이며 트레이드오프에 따라 결정.
참고 자료
- [Designing Data-Intensive Applications](https://dataintensive.net/) — Martin Kleppmann (샤딩 챕터)
- [Vitess](https://vitess.io/) — YouTube 공식
- [Citus Data](https://www.citusdata.com/)
- [CockroachDB Docs](https://www.cockroachlabs.com/docs/)
- [YugabyteDB](https://docs.yugabyte.com/)
- [TiDB](https://docs.pingcap.com/tidb/stable)
- [Slack's Data Stores](https://slack.engineering/scaling-datastores-at-slack-with-vitess/) — Vitess 마이그레이션
- [GitHub MySQL → Vitess](https://github.blog/2021-09-27-partitioning-githubs-relational-databases-scale/)
- [Stripe's Sharding Approach](https://stripe.com/blog/online-migrations)
- [Discord's Trillion Messages](https://discord.com/blog/how-discord-stores-billions-of-messages) — Cassandra 사용
- [Pinterest Sharding](https://medium.com/pinterest-engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f)
현재 단락 (1/391)
- **샤딩 = 수평 확장의 답**: 단일 DB로 안 되는 규모에서 필수