--- id: db-sharding-strategies title: Sharding — 수평 분할 / 라우팅 / Resharding category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [database, sharding, scaling, vibe-coding] tech_stack: { language: "SQL / Postgres / Citus", applicable_to: ["Backend"] } applied_in: [] aliases: [sharding, partitioning, hash sharding, range sharding, Citus, Vitess] --- # Sharding > 1대 RDB 한계 (CPU/메모리/디스크) 도달. **여러 노드에 데이터 수평 분할**. Hash / Range / Lookup. **늦게, 진짜 필요할 때만** — read replica + cache + 인덱스 먼저. ## 📖 핵심 개념 - Shard key: 어떤 컬럼으로 분할 (보통 user_id, tenant_id). - Hash sharding: hash(key) % N. - Range: id 1-1M = shard1, 1M-2M = shard2. - Lookup: 어떤 shard 인지 별도 매핑. - Resharding: shard 수 변경 시 데이터 이동. ## 💻 코드 패턴 ### App-level sharding (가장 단순) ```ts const SHARDS = [pool0, pool1, pool2, pool3]; function shardFor(userId: string): Pool { const h = murmurhash(userId); return SHARDS[h % SHARDS.length]; } async function getUser(id: string) { const db = shardFor(id); return db.users.find(id); } ``` ### Multi-tenant by row ```sql -- 모든 테이블에 tenant_id CREATE TABLE orders ( id UUID, tenant_id UUID NOT NULL, ... PRIMARY KEY (tenant_id, id) ); CREATE INDEX orders_tenant ON orders(tenant_id); -- 모든 query 에 tenant_id 필수 SELECT * FROM orders WHERE tenant_id = $1 AND id = $2; ``` RLS (Row Level Security): ```sql ALTER TABLE orders ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_iso ON orders USING (tenant_id = current_setting('app.tenant_id')::UUID); ``` ### Citus (Postgres extension) ```sql SELECT create_distributed_table('orders', 'tenant_id'); -- Citus 가 hash(tenant_id) 로 자동 분산 ``` ```sql -- 같은 tenant 의 join 은 같은 shard 안 — colocated SELECT create_distributed_table('order_items', 'tenant_id', colocate_with => 'orders'); ``` ### Vitess (MySQL) - VTGate 가 query 라우팅. - VSchema 로 keyspace + sharding 정의. - Vindex 로 lookup 변환. - YouTube / Slack / Square 사용. ### MongoDB sharding ```js sh.shardCollection('app.orders', { tenantId: 'hashed' }); ``` ### Resharding (Hash → 더 많은 shard) **문제**: hash mod N 변경 시 모든 데이터 이동. **해결**: Consistent hashing — virtual node 로 일부만 이동. ```ts import { HashRing } from 'hashring'; const ring = new HashRing(['shard0', 'shard1', 'shard2'], 'md5', { vnodes: 256 }); const node = ring.get(userId); // 'shard1' // shard 추가 ring.add('shard3'); // 일부만 재분배 ``` ### Lookup table (가장 유연) ```sql CREATE TABLE shard_map ( user_id UUID PRIMARY KEY, shard_id INT NOT NULL ); -- 라우팅: lookup → shard 결정 -- 조회 비용 1 hop, 단 cache. ``` ### Cross-shard query (피하기) - 같은 shard key 로 joined 데이터 colocate. - Cross-shard 가 필요하면 fan-out + merge. - Aggregate 는 분석 DB 로 따로. ### Hot tenant 문제 - 한 tenant 가 너무 큼 → shard 불균등. - 해결: 그 tenant 만 별도 shard / sub-shard (user_id 도 같이). ## 🤔 의사결정 기준 | 규모 / 상황 | 추천 | |---|---| | <1TB / <10K QPS | Sharding 불필요 — 인덱스 / replica / cache | | Multi-tenant SaaS | tenant_id sharding (Citus) | | Time-series | range sharding (월별 파티션) | | 균일 access | hash sharding | | Hot key 있음 | + sub-sharding | | Geo (지역별) | region 별 cluster | | Strong consistency | 같은 shard 안만 | ## ❌ 안티패턴 - **너무 일찍 sharding**: 운영 부담 폭증. 다른 옵션 먼저. - **Shard key 변경 가정**: 거의 불가능. 잘 골라야. - **Cross-shard transaction 자주**: 2PC 어려움. 디자인 변경. - **모든 query 에 shard key 누락**: scatter-gather. - **N → N+1 변경 못 함**: consistent hashing 또는 lookup. - **Hot tenant 무시**: 한 노드 OOM. - **No backup / restore plan**: shard 별 backup 일관성. ## 🤖 LLM 활용 힌트 - 늦게 + 진짜 필요할 때. - Citus / Vitess 같은 매니지드 도구 가급적. - Tenant_id / user_id 같은 자연 shard key. ## 🔗 관련 문서 - [[DB_Partitioning_Patterns]] - [[DB_Read_Replica_Patterns]]