학습 목표
파티셔닝과 샤딩의 차이 — 한 DBMS 내부 분할 vs 여러 DB 분산 — 를 설명할 수 있다.
수평·수직 분할과 range·hash·list 파티션 전략을 비교할 수 있다.
샤드 키 선택이 균등 분산·핫스팟·조인에 미치는 영향을 설명할 수 있다.
Partition pruning·co-located shard 개념을 설명할 수 있다.
재샤딩·resharding 비용과 운영 시 주의점을 설명할 수 있다.
문제 상황
orders테이블 8억 행 — 인덱스·VACUUM·백업 한 테이블이 병목DELETE WHERE created_at < '2020'— 전체 lock·WAL
- 월별 아카이브만 버리고 싶은데 DROP TABLE만 가능
- 2020년 데이터만 통째로 제거하고 싶음
- user_id 순차로 샤딩 — 신규 가입 폭주 shard 과부하
tenant_id없이 샤드 — cross-shard JOIN · 2PC 지옥- PostgreSQL 파티션 추가했는데 planner가 전 파티션 scan
WHERE에 파티션 키 없음
앞서 복제는 같은 데이터 사본 — 이번 편은 데이터를 쪼개 크기·쓰기를 분산한다.
1. 파티셔닝 vs 샤딩

| 파티셔닝 | 샤딩 | |
|---|---|---|
| 범위 | 한 DBMS · 한 catalog | 여러 DB · 여러 노드 |
| SQL | SELECT 한 connection (대부분) |
샤드 라우터 · fan-out |
| 트랜잭션 | 로컬 ACID (파티션 간 제약) | cross-shard 어려움 |
| 목적 | 관리·pruning·I/O 국소화 | 수평 확장 (write scale-out) |
| 예 | PG PARTITION BY RANGE |
Vitess, Citus, app-level shard |
Partition: one PostgreSQL → orders_2024, orders_2025 (logical tables)
Sharding: shard A, B, C → separate instances
- 파티셔닝도 수평 분할이지만 엔진이 routing·pruning 일부 담당
- 샤딩은 앱·미들웨어가 키→shard 매핑 (Citus는 DBMS 내장 샤딩)
2. 수평 vs 수직

| 수평 (Horizontal) | 수직 (Vertical) | |
|---|---|---|
| 나눔 | 행 — 같은 스키마 | 열 — 컬럼 분리 |
| 예 | 월별 orders 파티션 | users + user_profiles 테이블 |
| 동기 | range·hash·list | 정규화·wide row 분리 |
| 쿼리 | pruning · shard key | JOIN 필요 |
Horizontal: orders WHERE 2025-03 → partition orders_2025_03 only
Vertical: users(id, name) + user_prefs(user_id, json) — different tables
- 수평 — 대용량·time-series·tenant 분리에 흔함
- 수직 — LOB·cold column 별도 — 파티션과 병행 가능
3. 파티션 전략

| 전략 | 키 | 장점 | 단점 |
|---|---|---|---|
| Range | 날짜·ID 구간 | pruning·아카이브 DROP | hot latest range |
| List | region·tenant enum | 명시 구역 | 값 폭증 시 관리 |
| Hash | hash(user_id) % N |
균등 분산 | range 쿼리 전 shard |
| Composite | (tenant, date) |
SaaS + time | DDL 복잡 |
-- PostgreSQL RANGE (code/partition-ddl.sql)
CREATE TABLE orders (
id bigint,
created_at timestamptz NOT NULL,
amount numeric(12,2)
) PARTITION BY RANGE (created_at);
CREATE TABLE orders_2025_01 PARTITION OF orders
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE orders_2025_02 PARTITION OF orders
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
-- HASH partition
CREATE TABLE events (
id bigint,
user_id bigint NOT NULL,
payload jsonb
) PARTITION BY HASH (user_id);
CREATE TABLE events_p0 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE events_p1 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 1);
| Partition pruning | planner가 불필요 파티션 제외 |
|---|---|
| 조건 | WHERE에 파티션 키 — created_at BETWEEN ... |
| 실패 | WHERE extract(year FROM created_at)=2025 — 함수 on key |
EXPLAIN SELECT * FROM orders
WHERE created_at >= '2025-02-01' AND created_at < '2025-03-01';
-- Append -> Seq Scan on orders_2025_02 only (ideal)
- Global unique — PK가 파티션 키 포함 필요 (PG) —
(created_at, id) - FK across partition — 제약 제한 — 설계 선행
4. 샤딩과 샤드 키
샤딩 — 샤드 키로 행을 물리 DB에 배치.

| 키 후보 | 분산 | 조인·쿼리 |
|---|---|---|
| user_id | hash 시 양호 | user 로컬 데이터 co-locate |
| tenant_id | SaaS 자연 | tenant 격리 |
| auto increment id | hot latest shard | ❌ 단조 증가 |
| geo region | list | cross-region join 비용 |
| composite (tenant, user) | tenant 내 균등 | multi-tenant 표준 |
# code/shard-routing.example
def shard_for_user(user_id: int, num_shards: int = 16) -> int:
return user_id % num_shards
def get_connection(user_id: int):
shard = shard_for_user(user_id)
return pools[shard]
| 패턴 | |
|---|---|
| Co-locate | orders·order_items 같은 shard key |
| Lookup table | email → shard directory |
| Vitess/Citus | Vindex·distribution column |
- 핫스팟 — celebrity user·promotion — hash만으로 부족 — sub-shard·cache
- Cross-shard — scatter-gather · 비용 큼 — avoid 설계
5. 재샤딩
Resharding — shard 개수·경계 변경 (N → 2N).
Before: hash(k) % 4 → shards 0..3
After: hash(k) % 8 → shards 0..7 (rebalance data)
| 단계 | |
|---|---|
| Dual write | old + new shard 동시 |
| Backfill | copy historical |
| Cutover | read new mapping |
| Drain old | remove duplicate |
| 비용 | |
|---|---|
| 네트워크 | TB 이동 |
| 일관성 | cutover window |
| 앱 | routing 버전 |
- 미리 shard slot 여유 (1024 virtual → 16 physical) — consistent hashing
- 파티션 detach (PG) — 한 달 테이블 이동은 재샤딩보다 가벼움
6. 파티션 vs 복제
| 복제 | 파티션/샤드 | |
|---|---|---|
| 데이터 | 동일 전체 | 조각 |
| 목적 | read scale·HA | size·write 분할 |
| 조합 | shard마다 replica 흔함 |
Shard 0 Primary + Replicas
Shard 1 Primary + Replicas
7. 체크리스트
| 항목 | 확인 |
|---|---|
| 키 | 쿼리 90%에 파티션/샤드 키 포함? |
| Pruning | EXPLAIN — Append child 수 |
| PK/UK | 파티션 키 포함 |
| Hotspot | monotonic id·latest range |
| Cross-shard TX | saga·avoid (분산 트랜잭션 편) |
| Ops | detach/drop old partition · monitor size |
8. 정리
- 파티션 — 한 DB 내 논리 분할 — pruning·아카이브
- 샤딩 — 여러 DB — write scale-out — routing 필수
- Range — time 아카이브 · Hash — 균등 · List — enum
- 샤드 키 — co-locate · cross-shard 최소화
- 재샤딩 — 비용 큼 — virtual shard·파티션 선설계
다음에 다룰 것
- 분산 트랜잭션 개요
- 2PC, saga, outbox
해당 내용은 Database System Concepts, 7/E (Avraham Silberschatz, Henry F. Korth, S. Sudarshan), Designing Data-Intensive Applications (Martin Kleppmann) 의 내용을 기반으로 합니다.
'데이터베이스 따라잡기' 카테고리의 다른 글
| NoSQL 개요 (0) | 2026.07.04 |
|---|---|
| 분산 트랜잭션 개요 (0) | 2026.07.03 |
| 복제 (0) | 2026.07.01 |
| MVCC (0) | 2026.06.30 |
| 로깅과 복구 (0) | 2026.06.29 |