資料用主鍵分片後,次級索引怎麼擺是另一個獨立決策。索引可以跟著資料走(local,document-partitioned),也可以自成一套照索引值分片(global,term-partitioned)。差別在於:你想讓寫快還是讀快。
🧠 Intuition
TIP
一句話:local index 是「每個分片各自記自己的索引」——寫只動本地,但讀要問遍所有人;global index 是「整個叢集共用一套按索引值分片的索引」——讀只問一處,但寫要去更新別人家的索引。
- 主鍵分片決定「資料在哪」;次級索引分片決定「索引本身在哪」——兩者可以、也通常需要不同的分片方式。
- Local(document-partitioned):索引只涵蓋本分片的文件。寫入只碰本地,讀取要 scatter/gather 問所有分片。
- Global(term-partitioned):索引按「索引詞(term)」自己分片。讀取直達持有該 term 的分片,但寫一筆文件可能要更新多個分片上的索引項。
- 沒有免費的午餐:你只是在「寫放大」與「讀放大」之間選邊站。
⚖️ 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
- 次級索引分片是與主鍵分片獨立的決策,本質是在寫放大 ↔ 讀放大之間選邊。
- Local(document-partitioned):寫只動本地,讀需 scatter/gather——大多數系統的預設,寫重首選。
- Global(term-partitioned):讀直達單分片,寫需跨分片更新——讀重、查詢固定時的首選。
- Global index 的跨分片寫入常以非同步更新換取延遲,代價是索引短暫 stale、不保證 read-your-writes。
2026-07-01
- Q: Local index 寫入只動本地、聽起來全面更好,為什麼還要 global index?
- A: 因為成本只是被搬到讀取端。Local index 的「讀」要 scatter/gather 問遍所有分片,分片越多、放大越嚴重,且整體延遲被最慢的那台決定(尾延遲問題)。如果你的負載是「讀遠多於寫、而且某個次級查詢非常頻繁」,把那一次讀的成本從 N 個分片降到 1 個分片,划算得多——這正是 global / term-partitioned index 存在的理由。代價是寫入要跨分片更新,所以通常搭配非同步更新、容忍索引短暫落後。