如何選擇MongoDB片鍵?

熊崽Kevin發表於2014-05-26

【譯註】本文探討了如何合理設定MongoDB片鍵以發揮分片機制的優勢,作者為Bugsnag.com的工程師Conrad Irwin。Bugsnag為移動應用開發者提供實時的Bug追蹤及檢測服務,Bugsnag使用MongoDB儲存超過TB級的文件資料。

簡而言之,使用{_id: ‘hashed’}或{projectId: 1, _id: 1}來作為片鍵。

幾個月前,我們對MongoDB叢集進行分片(shard)處理,資料設定了兩個副本集合(replica set)。上週,我們新增了一個新的分片。首次分片花了一些功夫,不過我們仍然在沒有停機的情況下完成了這個工作,如今新增一個新的分片是很輕而易舉的事情。

MongoDB的分片是如何工作的?

MongoDB的分片機制能夠幫助你將你的資料庫劃分到多個伺服器,通常在生產環境中可以將資料集劃分到多個副本集中。但分片最好在資料庫建立早期劃分,因為一旦你的資料大於512GB那麼分片劃分就不是那麼容易了。這受到MongoDB縱向擴充套件能力的限制。

為了實現分片,你必須向MongoDB指定使用哪個索引作為片鍵,然後MongoDB會根據你的設定將你的資料劃分到有著相同片鍵的資料塊(Chunk)中。而後這些資料塊將根據片鍵的大致順序分散到副本集中。

sharding

正如你所見,分片之後資料的存放位置依賴於片鍵,所以合理的選擇片鍵十分重要。

好片鍵的要素

MongoDB的內部機制保證了每個副本集(RS)包含了同樣數量的塊,在上圖中一個RS包含兩個塊,而在Bugsnag.com的叢集中,每個RS包含6300個塊。但這幾乎是唯一的保證機制了。

片鍵的選擇決定了三個重要的方面:

1. 讀和寫的分佈

其中最重要的一點是讀和寫的分佈。如果你總是朝一臺機器寫,那麼這臺機器將會成為寫瓶頸,則你的叢集的寫效能將會降低。這無關乎你的叢集有多少個節點,因為所有的寫操作都只在一個地方進行。因此,你不應該使用單調遞增的_id或時間戳作為片鍵,這樣將會導致你一直往最後一個副本集中新增資料。

相類似的是如果你的讀操作一直都在同一個副本集上,那麼你最好祈求你的任務能在機器記憶體所能承受的範圍之內。通過副本集將讀請求劃分開能夠使你的工作資料集大小隨著分片數線性擴充套件。這樣的話你能夠將負載壓力均分到各臺機器的記憶體和磁碟之上。

2. 資料塊的大小

其次是資料塊的大小。MongoDB能夠將大的資料塊劃分成更小的,但這種情況僅僅在片鍵不同的情況下發生。如果你有巨量的資料文件都使用了同樣的片鍵,那麼你相應的會得到巨大的資料塊。出現巨大塊是非常不好的,不僅僅因為它會導致資料的不平均分佈,還因為一旦這個資料塊的大小超過某個值,那麼你就不能夠在分片之間移動它了。

3. 每個查詢命中的分片數目

最後一點,如果能夠保證大部分的查詢請求都能夠命中儘可能少的分片那就最好了。對於一個查詢請求來說,其延遲直接取決於最慢的那個命中伺服器的延遲;所以你命中的分片越少,那麼理論上來說查詢將會越快。這一點並不是硬性的規定,不過如果能夠做到充分考慮那麼應該是很有利的。因為資料塊在分片上的分佈僅僅是近似的遵循片鍵的順序,而並不是嚴格的強制指定。

好片鍵是如何煉成的?

上面說了這麼多,那麼怎麼才能設計一個好的片鍵呢?

Hashed id

作為第一個方案,你可以使用資料文件_id的雜湊作為片鍵。

這個方案能夠是的讀和寫都能夠平均分佈,並且它能夠保證每個文件都有不同的片鍵所以資料塊能夠很精細。

似乎還是不夠完美,因為這樣的話對多個文件的查詢必將命中所有的分片。雖說如此,這也是一種比較好的方案了。

多租戶混合索引(Multi-tenant compound index)

如果想擊敗雜湊索引模式,那麼你需要將關聯的文件在索引中儘可能聚集在一起的方法。在Bugsnag,我們通過project聚合文件,因為在我們的業務場景中,我們的app大部分的查詢請求都在project範圍內。所以對於你的app來說你得指定適合你的聚合方式。

但是我們不能簡單地使用projectID作為片鍵,因為那會導致巨大塊的產生,所以我們引入了_id來將大project打散到多個塊中。這些打散的塊仍舊是索引連續的,所以仍然會分佈在用一個分片上。

這個方案很適合我們,因為對於一個project來說,讀和寫幾乎是獨立於project存在時間的,並且舊的project通常都會被刪除掉。如果情況改變,我們可能會看到在新的project會有微小的負載上升情況。

為了避免這種問題,我們未來可能會在當MongoDB支援雜湊值的混合索引之後,將索引設定為{projectId: ‘hashed’, _id: 1}。相關文件(SERVER-10220)

總結

找一個好的片鍵是很難的,不過這真的只有兩種方案。如果在應用中找不出一個好的聚合鍵,那麼對_id做雜湊吧。如果你能夠找到,那麼將它與_id聚合以避免巨大塊的產生。請記住無論你使用何種聚合鍵,它都需要能夠將讀和寫平均分佈以充分利用叢集中的每個節點。

相關文章