分片,唯一索引與upsert

前言

分片,唯一索引和upsert,表面上看似沒有直接聯系的幾個東西,到底存在怎樣的瓜葛呢?

分片

為了保持水平擴展的有效性,分片功能必須保證各個片之間沒有直接關聯,不需要與其他分片交互就可以獨立做出決策。如果不能滿足這一點,隨著分片數量不斷增加,需要交互的分片越來越多,勢必會越來越慢,那么就違背了分片的初衷了。比如JOIN就是一種典型的破壞分片獨立性的功能。在一個n個分片的集群中,為了得到笛卡爾積,每個分片必須與其他n-1個分片交互來得到結果。雖然不見得是線性的延遲增長(因為n-1個請求可以并行),但是可想而知對資源將是極大的消耗,并且隨著分片數量的增長影響會越來越顯著,最終會到達“增加一個分片可能對性能完全沒有幫助”,或者“增加一個分片反而降低性能”的地步。

唯一索引

唯一索引是另外一個顯著破壞分片獨立性的特性。前面對JOIN的分析完全適用于唯一索引,并且更糟的情況是唯一索引還有有更進一步的惡劣影響,那就是在寫入數據的時候必須占用一個跨分片的全局鎖,否則無法保證其唯一性,可想而知對性能有怎樣的影響。這也是MongoDB為什么不打算去實現全局唯一索引的原因。

有一種特殊情況卻可以改變這種不利狀況,那就是唯一索引的鍵正好是片鍵的時候。片鍵一旦確定,文檔該去哪個分片就確定了,那么只要保證該鍵在這一個片上唯一就可以了,不再需要去與其他分片協商。

upsert

從語義上講,我們使用upsert一般是希望一個鍵只出現一次的(不然每次insert就好了)。這一點恰恰是唯一索引要干的事情,而唯一索引又存在上面的所說的問題,因此唯一有意義的情況則是upsert使用的條件正好是片鍵,且片鍵唯一。
滿足了上面這些條件就高枕無憂了嗎?并不是。在決定一個鍵是不是存在,到執行update/insert之間,是存在空隙的。即,檢測和執行并不在一個原子操作中,也不可能在一個原子操作中,否則將是一個很大粒度的鎖。再說,MongoDB對文檔級別并沒有真正通過加鎖來控制,而是通過“樂觀并發控制”(optimistic concurrency control)來進行的。
因此,出于效率考慮,不是原子操作是正確的選擇,而解決這個問題也不是特別麻煩的事情,實際上只需要在遇到duplicate key異常的時候重試該操作就可以了,因為重試的時候理論上就應該變成update而不再是insert,自然避免了問題。或者,在4.2中直接實現了這類錯誤的自動重試(SERVER-37124)。

參考資料

作者簡介

張耀星,MongoDB亞太區首席技術咨詢服務顧問。在MongoDB的開發、應用和咨詢服務上有多年實踐經驗。作為MongoDB認證專家,曾經為不同行業的各類大型客戶提供過培訓、性能調優、架構設計等各類MongoDB相關技術服務。

發表評論