資料分片 (Sharding)
Sharding 是「單台 DB 撐不住時」把資料拆散到多台機器的做法。提升儲存容量和吞吐量,但代價是巨大的複雜度。
Partitioning vs Sharding
| 詞 | 範圍 | 例子 |
|---|---|---|
| Partitioning | 單一 DB instance 內部拆分 | PostgreSQL table partition |
| Sharding | 拆到多台機器 | MongoDB / Cassandra cluster |
實務上很多人混用,重點是說清楚資料在一台還是多台。
Partitioning 兩種
| 類型 | 怎麼拆 | 例 |
|---|---|---|
| 水平分區(Horizontal) | 拆 row | 每年訂單一個分區 |
| 垂直分區(Vertical) | 拆 column | 常用欄位一區、大欄位另一區 |
Sharding 是「水平分區延伸到多台機器」。
Shard Key 的三條件
| 條件 | 為什麼 | 反例 |
|---|---|---|
| 高基數(High Cardinality) | 否則 shard 數受限 | is_premium(布林只能分 2 個) |
| 均勻分佈(Even Distribution) | 否則熱點 | 用 country 分片但 90% 用戶在美國 |
| 契合查詢模式 | 否則 query 必須打所有 shard | 用 user_id 分片,多數 query 限定單一用戶 |
Good vs Bad 範例
🟢 user_id 用於用戶導向 App
- 高基數(數百萬用戶)
- 分佈均勻
- 多數查詢限定單一用戶
🟢 order_id 用於電商訂單
- 高基數
- 查詢通常限定特定訂單
- 訂單隨時間均勻分散
🔴 is_premium(布林)
- 只能分 2 個 shard
- 一邊放免費用戶會嚴重不均
🔴 成長中表用 created_at
- 新寫入全部打到最新 shard → 寫入熱點
- 舊 shard 幾乎只在讀歷史時才有流量
三種分片策略
1. Range-Based Sharding(範圍分片)
依連續值範圍分組。
Shard 1 → user_id 1 ~ 1M
Shard 2 → user_id 1M ~ 2M
Shard 3 → user_id 2M ~ 3M
- ✅ 簡單、支援高效範圍掃描
- ❌ 存取模式通常不均(時間戳記分片 → 最新 shard 過熱)
- 💡 適合多租戶 SaaS:A 公司只查 A 範圍、B 公司只查 B 範圍,自然分散
2. Hash-Based Sharding(雜湊分片)— 預設首選
shard = hash(user_id) % N
User 42 → hash(42) % 4 = Shard 2
User 99 → hash(99) % 4 = Shard 3
User 123 → hash(123) % 4 = Shard 1
- ✅ 分佈均勻
- ❌ 增減 shard 時,餘數運算改變,幾乎所有資料都要搬
Consistent Hashing 在增減節點時把資料移動量降到最低(K/N),而不是 hash % N 的「幾乎全搬」。
面試預設:除非特別說明,hash sharding 是預設答案。
3. Directory-Based Sharding(目錄分片)
查找表存「哪筆對應哪個 shard」。
user_to_shard:
User 15 → Shard 1
User 87 → Shard 4
User 204 → Shard 2
- ✅ 靈活(熱 key 可單獨搬到專屬 shard)
- ❌ 每個請求多一次 lookup
- ❌ Directory 本身是 SPOF,掛了整個系統停擺
增加 SPOF 和延遲,面試官會追問一連串會讓對話偏離主題的問題。除非題目明確需要動態靈活性才提。
Sharding 的三大挑戰
挑戰 1:熱點與負載不均(Celebrity Problem)
就算 shard key 選得好,有些 key 本來就特別熱。
用
user_id分片,Taylor Swift 的 shard 流量是普通用戶的 1000 倍 —— 雜湊函數對她一視同仁,但她本身就是熱資料。
偵測:監控各 shard 的 query latency、CPU、QPS,發現某 shard 持續偏高即熱點。
解法:
- 熱 key 隔離到專屬 shard(這時 directory sharding 有用)
- 複合 shard key:
hash(user_id + date)—— 同一用戶的資料按時間分散 - 動態 shard 拆分:MongoDB balancer / Vitess online resharding
挑戰 2:跨 Shard 操作
查詢模式和 shard key 不對齊時,必須打所有 shard、等所有回應、再聚合。
用
user_id分片 → 「全站最熱門 10 篇貼文」要打 64 個 shard,64 倍的網路 + latency
最小化方式:
- 快取結果:「全站熱門」這類查詢快取 5 分鐘,第一次貴、後續從 cache
- 反正規化:常一起查的資料重複存到同一 shard(代價是寫入更複雜)
- 接受罕見查詢代價:admin dashboard 一天跑幾次的「總用戶數」慢一點 OK
如果你發現自己對常見場景說「查所有 shard 然後聚合」,暫停一下 —— 可能是 shard key 選錯或邊界劃分有問題。
挑戰 3:跨 Shard 一致性
單一 DB 的 transaction 失效。
解法(從好到差):
- 設計成完全避免跨 shard transaction:把同一用戶的所有資料放同一個 shard(用
user_id分片時最自然) - 真的避不掉 → 用 Saga 模式(見 05-Database-Advanced/01-Transactions)
- 接受最終一致性:粉絲數、計數類資料反正規化在多個 shard,幾秒不一致無妨
- 2PC:教科書答案,生產環境通常避免(效能與脆弱性代價過高)
一直需要分散式 transaction → shard key 選錯了。
現代資料庫的 Sharding
好消息:你大概不會從零實作 sharding。
| 資料庫 | 機制 |
|---|---|
| Cassandra | Partitioner(Murmur3)+ virtual nodes,consistent hashing 變體 |
| DynamoDB | 對 partition key 雜湊,自動拆分/合併分區(內部,不暴露 ring) |
| MongoDB | 依 shard key 切 chunk(範圍 or 雜湊),balancer 自動搬移 |
| Vitess / Citus | 架在 MySQL/PostgreSQL 前的開源 sharding 層 |
| Aurora / Cloud Spanner | 雲端內建分散式 SQL |
「我們用 DynamoDB 以 user_id 作為 partition key」
或「Vitess 以 user_id 分片,需要擴展時由操作人員發起 online resharding」
—— 通常就夠。除非面試官追問,不必細到實作層。
系統設計面試怎麼談 Sharding
時機(最常見錯誤:過早分片)
500GB / 1-2TB 就喊 sharding 是常見錯誤。Sharding 之前的優化階梯:
慢查詢 → 加 index → 加 cache → 加 read replica → 調 DB 參數 → 升級硬體 → 才談 sharding
帶出 sharding 的觸發訊號:
| 觸發點 | 數字(參考 02-Distributed-Systems/05-Numbers-to-Know) |
|---|---|
| 儲存空間 | 逼近 50TiB |
| 寫入吞吐 | 長期 > 10k TPS |
| 讀取吞吐 | read replica 也應付不了 |
| 跨區域 | 需要多 region |
四步驟說明(用社群媒體為例)
Step 1:根據存取模式選 shard key
「社群媒體查詢多是用戶為中心:feed、追蹤、按讚都限定單一用戶。我用
user_id分片。」
Step 2:選分佈策略
「Hash sharding 配 consistent hashing,把用戶均勻分散。」
Step 3:點出取捨
「全域查詢(如全站熱門貼文)會貴。用快取熱門內容 + 背景任務預計算來解決。」
Step 4:規劃成長
「從 64 個 shard 起步留充裕空間。Consistent hashing 讓未來增 shard 只搬一小部分資料。」
Related Notes
- 02-Distributed-Systems/04-Consistent-Hashing — Hash sharding 擴容的標準解法
- 02-Distributed-Systems/05-Numbers-to-Know — Sharding 觸發點的具體數字
- 05-Database-Advanced/01-Transactions — 跨 shard transaction 用 Saga
- 05-Database-Advanced/03-Replication — Sharding 通常同時搭配 replication
- 07-Caching-Storage/01-Caching — 快取昂貴的跨 shard 聚合查詢