分片上的次級索引 (Local vs Global)

★★★★Reviewed

FromDDIA

📚 From the Books

分片上的次級索引 (Local vs Global)Partitioning

資料用主鍵分片後,次級索引怎麼擺是另一個獨立決策。索引可以跟著資料走(local,document-partitioned),也可以自成一套照索引值分片(global,term-partitioned)。差別在於:你想讓寫快還是讀快

🧠 Intuition

TIP

一句話:local index 是「每個分片各自記自己的索引」——寫只動本地,但讀要問遍所有人;global index 是「整個叢集共用一套按索引值分片的索引」——讀只問一處,但寫要去更新別人家的索引。

⚖️ Tradeoffs

Document-partitioned (Local Index) — 寫快,讀需 scatter/gather
  • 每個分片維護「只包含自己文件」的次級索引(DDIA 稱 local index)。
  • 寫入:新增/改一筆文件時,只需更新該文件所在分片的本地索引——寫入侷限在單一分片,快且簡單
  • 讀取:用次級索引查詢(如「找所有 color=red」)時,無法預知符合的文件散在哪些分片,必須問遍所有分片再合併結果(scatter/gather)。
  • 風險:尾延遲被最慢的分片拖累;分片越多,scatter/gather 放大越嚴重。
  • 代表:MongoDB、Cassandra、Elasticsearch、Riak——大多數系統的預設。
Term-partitioned (Global Index) — 讀快,寫需跨分片
  • 索引本身按「索引詞(term)」分片,與資料的主鍵分片無關(如 color 在 a–r 的索引項放分片 1,s–z 放分片 2)。
  • 讀取:查 color=red 直接定位到持有該 term 的分片,一次命中、不必 scatter/gather,讀取效率高、可做覆蓋多文件的範圍查詢。
  • 寫入:一筆文件可能含多個被索引欄位,這些 term 可能落在不同分片,因此一次寫入要更新多個分片上的索引——寫入變慢、且牽涉跨分片協調。
  • term 本身也可選 range(支援索引值範圍查詢)或 hash(索引負載均勻)分片。
非同步更新 (Asynchronous Index Update)
  • Global index 的寫入若要同步更新所有相關分片,需要跨分片的分散式交易,成本高、延遲大。
  • 實務折衷:索引非同步更新——寫入主資料後立刻回應,索引在背景補上。
  • 代價:索引短暫落後(stale),剛寫入的文件可能一瞬間查不到(read-your-writes 不保證)。
  • 多數採 global index 的系統(如 DynamoDB Global Secondary Index)正是用這種最終一致的非同步更新換取可用的寫入延遲。
怎麼選 — 看讀寫比與查詢形態
  • 寫重、查詢條件多變:選 local index。寫入便宜,scatter/gather 的讀成本可用更多分片/快取分攤。
  • 讀重、查詢條件固定且選擇性高:選 global index。省下 scatter/gather,讀取延遲穩定。
  • 折衷現實:global index 寫入複雜又常需非同步,因此多數系統預設 local,只在特定熱查詢上才額外建 global index。

🔑 Takeaways

✍️ My Notes

2026-07-01

  • Q: Local index 寫入只動本地、聽起來全面更好,為什麼還要 global index?
  • A: 因為成本只是被搬到讀取端。Local index 的「讀」要 scatter/gather 問遍所有分片,分片越多、放大越嚴重,且整體延遲被最慢的那台決定(尾延遲問題)。如果你的負載是「讀遠多於寫、而且某個次級查詢非常頻繁」,把那一次讀的成本從 N 個分片降到 1 個分片,划算得多——這正是 global / term-partitioned index 存在的理由。代價是寫入要跨分片更新,所以通常搭配非同步更新、容忍索引短暫落後。

📖 Further Reading

🔗 Dive Deeper