01

總覽與定位

從「知道 Range / Hash / Directory」到「Production 怎麼落地」——實務分片決策地圖。

上一篇「資料分片基礎概念」建立了分片策略與系統挑戰的語彙。本篇聚焦實務落地:真實產品如何選 sharding key、如何設計 co-location、如何在不停機前提下做 rebalance,以及不同業務型態的典型分片藍圖。

面試與架構 review 的高分答案,往往不是背出公式,而是能說出:「這個存取模式 → 因此選這個 key → 用這種策略 → 擴容時走這條遷移路徑」 的完整因果鏈。

座標本篇 vs 基礎篇

• 基礎篇:Range / Hash / Directory 的定義、優缺點、hot spot 與 scatter-gather 概念。
• 本篇:多租戶 SaaS、電商、社交、時序等場景的具體分片藍圖、線上遷移 SOP、Directory 路由表治理,以及 co-location 設計模式。
• 延伸:Hash 擴容細節見「一致性雜湊綜合手冊」;Cassandra 的 partition key 與 clustering key 設計見寬列儲存篇。

實務決策層次 核心問題 常見產出
1. 存取模式 80% 請求是 point lookup 還是 range scan? 決定 Range vs Hash 的大方向
2. Sharding Key 哪個欄位能讓多數查詢只打一個 shard? user_id、tenant_id、order_id 等
3. Co-location 哪些表 / 列必須在同一 shard? 避免跨 shard JOIN 與 2PC
4. 擴容與遷移 新增 shard 或搬大 tenant 時怎麼零 downtime? 雙寫、backfill、map version、節流

實務分片是「存取模式驅動」的設計,不是先選技術再硬套業務。

一句話答法分片實務 = 用存取模式倒推 sharding key 與策略,再預留 co-location 與線上遷移能力;公式只是手段,業務查詢路徑才是起點。

02

四大典型應用場景

多租戶 SaaS、電商、社交訊息、時序日誌——各自的分片藍圖長什麼樣?

1. 多租戶 SaaS(B2B)

每個 tenant 有獨立資料邊界,但 tenant 大小差異極大(免費版 vs 企業 VIP)。典型做法:

  • Directory Sharding:維護 tenant_id → shard_id 路由表,control plane 可手動把大 tenant 遷到專用 shard。
  • Shared vs Dedicated:小 tenant 共用 shard pool;大 tenant 獨佔節點,避免 noisy neighbor。
  • 查詢模式:幾乎所有 API 帶 tenant_id,天然單 shard point lookup。

2. 電商訂單系統

核心矛盾:下單寫入高吞吐 vs 用戶查「我的訂單歷史」。

  • Sharding Key:通常用 user_id(或 buyer_id),讓同一用戶的 orders + order_items 共 shard。
  • Co-location:order 與 order_item 都用 order_id 或父級 user_id 做 key,本地 JOIN 成立。
  • 反例:用 order_id 做 key 卻常查「某用戶全部訂單」→ 每次 scatter-gather 全 shard。

3. 社交訊息 / Feed

寫入分散、讀取局部性強——適合 hash 均勻打散。

  • 訊息 inbox:以 conversation_id 或 user_id hash,同一對話訊息共 shard。
  • Hot user:celebrity 帳號可能打爆單 shard → 讀 replica fanout、edge cache、或 fan-out on write 改 fan-out on read。
  • Timeline:常另建 per-user timeline 表(denormalize),寫入時 fan-out,讀取變單 shard point lookup。
白話這幾個「fan-out」到底在幹嘛

• 寫入時 fan-out(fan-out on write):發文的當下,系統就主動把這篇貼文複製一份,塞進每個粉絲自己的動態列表;粉絲刷新時只需查自己那份,很快,但粉絲越多,發文時要複製的份數就越多。
• fan-out on read:反過來,發文時只存一份在作者自己那邊,不主動派送;等粉絲要看動態時,才臨時去逐一查詢所有追蹤對象、現場拼出畫面——寫入變輕,讀取變重。
• replica fanout:跟前兩者是不同層次的事,指把「同一個熱門 shard」收到的讀取請求,分散發給它底下多台唯讀複本一起扛,用來稀釋單台機器的讀取壓力,跟 on-write / on-read 的取捨無關。

4. 日誌 / 指標 / IoT 時序

寫入連續、查詢多為時間區間——Range by time 幾乎是預設答案。

  • Partition:按天 / 週 / 月切 partition,舊 partition 可整批 archive 或 TTL drop。
  • Hot partition:「今天」的 partition 承接全部新寫入 → 需 monitor 並預先 split 或 buffer 層削峰。
  • 跨時間聚合:報表走 OLAP / 預聚合表,避免線上 scatter-gather 全歷史 shard。
場景 建議 Key 策略 擴容痛點
多租戶 SaaS tenant_id Directory 大 tenant 遷移、map 一致性
電商訂單 user_id Hash / Consistent Hash 跨 shard 報表需二級索引
社交訊息 conversation_id Hash Hot user / celebrity
時序日誌 timestamp(+ device_id) Range by time 最新 partition hot spot

沒有通用 key:同一公司不同服務(訂單 vs 搜尋 vs 報表)可能用不同 sharding 策略。

一句話答法SaaS 用 Directory 管大 tenant;電商 / 社交用 hash 共 shard;時序用 time-range——選型從「80% 查詢長什麼樣」倒推。

03

Co-location 與資料模型

讓相關列落在同一 shard——分片後避免跨 shard JOIN 的第一道防線。

Co-location(共置)指刻意讓邏輯上相關的多筆資料,經 sharding key 計算後落在同一物理 shard。這樣本地 ACID、本地 JOIN、本地 transaction 仍然成立,不必引入 2PC 或 Saga。

Parent-Child 共 Shard

父表與子表使用相同的 sharding key(通常是父 ID 或共同的 user_id):

  • orders + order_items:key = user_id 或 order_id(若 order_id 含 user 前綴更佳)。
  • documents + document_versions:key = document_id。
  • accounts + account_settings:key = account_id。

Denormalize 換取單 Shard 讀

當正規化模型必然跨 shard 時,實務常冗餘一份到查詢側:

  • Feed:寫入時 fan-out 到各 follower 的 timeline shard(以 follower_user_id 為 key)。
  • 搜尋:主庫 shard 存 authoritative data;Elasticsearch 建全域 inverted index 承擔跨 shard 搜尋。
  • 報表:CDC 同步到 OLAP(BigQuery / ClickHouse),線上 OLTP shard 不做全表聚合。
陷阱Secondary Index 不等於解決跨 Shard

在 shard 內建 local secondary index 只能加速該 shard 內的查詢。若查詢條件不含 sharding key(例如「依 email 查用戶」而 key 是 user_id),仍須 scatter-gather 或維護全域 lookup 表(email → user_id → shard)。

設計原則Transaction Boundary = Shard Boundary

若一個 business transaction 需要原子更新多個實體,設計時應讓這些列共用 sharding key。跨 shard 原子性代價是 2PC 延遲、協調者 SPOF 與 partition 下的阻塞——能避免就避免。

一句話答法Co-location 讓相關列共 shard;跨 shard 需求用 denormalize、二級索引服務或 OLAP 卸載,不要假設「加個 distributed JOIN」就能優雅解決。

04

線上遷移與 Rebalance SOP

新增 shard、搬大 tenant、split hot range——Production 零 downtime 遷移的標準步驟。

分片不是「上線時切好就一勞永逸」。流量成長、tenant 膨脹、hot partition 出現時,都需要背景搬資料。以下 SOP 適用 Directory 與 Hash 擴容,細節因策略而異,但階段邏輯相通。

標準六階段遷移流程

  1. Plan:定義遷移範圍(哪些 key / tenant)、目標 shard、節流上限、回滾條件。
  2. Dual-write:Router 同時寫舊 shard 與新 shard(或寫舊 + async replicate),確保新寫入兩邊都有。
  3. Backfill:背景掃描舊 shard 歷史資料,批量複製到新 shard,追 incremental changelog 直到 lag 接近零。
  4. Verify:抽樣 checksum、row count、業務 invariant 比對;shadow read 新 shard 結果與舊 shard diff。
  5. Cutover:更新 shard map / ring version,讀寫流量切到新 shard;保留舊副本一段時間做 rollback window。
  6. Cleanup:確認無回滾需求後,停止 dual-write、刪除舊副本、釋放容量。
階段 風險 緩解
Dual-write 兩邊寫入不一致 Idempotent write、version column、compensation job
Backfill 與線上流量搶 I/O Rate limit、離峰執行、優先搬冷資料
Cutover Client cache 舊 map Map version + TTL + sticky routing
Cleanup 過早刪除無法回滾 保留舊副本 N 天 + 監控告警

遷移期間 observability 比速度更重要:lag、error rate、P99 與 checksum diff 必須可視。

Directory vs Hash 擴容差異

  • Directory:改 map 即可指向新 shard;遷移粒度是 tenant / key range,最靈活。map service 需高可用 + cache。
  • Hash (% N):N 改變幾乎全量 remap,Production 很少直接改 N;通常換 Consistent Hash 或做 logical shard → physical shard 兩層映射。
  • Consistent Hash:新增節點只搬相鄰 vnode range;需 ring version 讓 client 漸進切換(詳見一致性雜湊手冊)。
陷阱「停機遷移」在大型系統不可接受

面試若只答「dump → restore → 改 DNS」會失分。Production 預期是漸進式、可回滾、可節流的線上遷移;並說明 dual-write 窗口與 map version 如何保證 read-your-writes。

一句話答法零 downtime 遷移 = dual-write + backfill + verify + cutover + cleanup;Directory 改 map,Hash 擴容靠 consistent hash 或 logical shard 間接映射。

05

互動 Demo:擴容策略並排對照

同一組租戶與同一次擴容,餵給 Hash % N 與 Directory 兩種策略,比搬遷量、窗口與傾斜。

下方左右兩欄吃的是完全相同的輸入:同一組租戶(大小依傾斜參數與巨鯨倍數決定)、同一組擴容前落點(hash % N)、同一條搬遷頻寬與寫入速率。差別只在擴容時怎麼決定新落點——左欄把 N 改成 N+k 讓算式重算,右欄只改路由表。第 4 節的六階段 SOP(dual-write → backfill → verify → cutover)在兩欄用的是同一套成本公式,兩邊秒數不同純粹是因為要搬的量不同。

互動 · 擴容策略並排對照 同一組輸入,兩種策略的數字差
Hash % N 重分配N 一改,全體落點跟著算式重算
Directory 路由表只改 map:先清鄰居隔離巨鯨,再填平
原地未動 本次搬入 巨鯨(仍有鄰居) 巨鯨已獨佔一片
指標Hash % NDirectory差異

看兩欄的「搬遷資料量」與巨鯨那一格:Directory 省下的不是演算法優雅度,而是實際要過網路的位元組——而且它隔離巨鯨的方式是把鄰居清走,不是搬那隻 240 GB 的鯨魚。

一句話答法同一次擴容,Hash % N 要搬的是「算式重算後落點改變的全部資料」,Directory 只搬「為了隔離與填平所必需的那幾筆」;代價換成一張要進關鍵路徑的路由表,而回填能不能收斂,取決於雙寫吃掉多少頻寬。

06

面試考點與架構折衷

實務追問:大 tenant 怎麼搬?跨 shard 報表怎麼做?什麼時候不該分片?

考點 1「某 SaaS 有一個 tenant 流量是其他 100 倍的,怎麼辦?」

回答框架:
1. 確認 sharding 策略是否 Directory(可單獨遷移)。
2. 把大 tenant 搬到 dedicated shard / 獨立 cluster。
3. 遷移走 dual-write + backfill + cutover,全程節流。
4. 若仍不足:tenant 內再分 sub-shard(例如依 workspace_id hash)。

考點 2「分片後怎么做全局報表 / 搜尋?」

回答框架:
1. OLTP shard 不做全 cluster 聚合——延遲與成本不可控。
2. CDC → OLAP(ClickHouse / BigQuery)或搜尋引擎(Elasticsearch)建全域索引。
3. 即時性要求低時用 batch ETL;要求高時用 stream + materialized view。

考點 3「什麼時候不該分片?」

回答框架:
1. 資料量與 QPS 仍可在單機 + read replica + cache 內解決。
2. 業務強依賴跨表 JOIN / 複雜 transaction,且無法 co-locate 或 denormalize。
3. 團隊缺乏 rebalance、map 治理與跨 shard debug 的運維能力。
4. 過早分片會永久增加系統複雜度——應有明確的容量 trigger(例如單表 > 500GB 或寫入 P99 觸頂)。

實務信號 建議動作
單 shard CPU / 磁碟 > 70% 且 key 分布均勻 加 shard + rebalance
單 tenant 佔一 shard 過半 Directory 遷到 dedicated shard
跨 shard 查詢 P99 飆升 檢視 sharding key 是否與查詢對齊;考慮 denormalize 或二級索引
最新 time partition 持續 hot Buffer 層(Kafka)+ 預 split + 寫入 batch
Risk & Impact實務分片的系統代價

• Payload / Latency:scatter-gather 與 dual-write 期間寫入放大;Directory 每次請求多一次 map lookup(通常 cache)。
• Security:多租戶環境需確保 router 強制注入 tenant_id,防止跨 tenant 資料洩漏。
• Dependency:shard map / control plane 成為關鍵路徑;需 HA、版本化與 cache invalidation 策略。

一句話答法實務高分 = 場景藍圖 + co-location 設計 + 六階段遷移 SOP + 知道何時用 OLAP 卸載跨 shard 需求,以及何時根本不該分片。