標籤:mongodb 片鍵選擇
1、選擇片鍵
選擇一個好的片鍵非常關鍵,如果選擇了一個糟糕的片鍵,它可以立馬或者在訪問量變大時毀了你的應用程式,也有可能潛伏著,等待著,沒準什麼時候突然毀了你的應用程式。
另外一方面,如果你選擇了一個好片鍵,只要應用程式還在正常運行,而且只要發現訪問量提高就趕緊添加伺服器,MongoDB就會確保一直正確地運行下去。
正如在前面所學的,片鍵決定了資料在叢集中的分布情況,因此你會希望存在這樣一個片鍵,它既能把讀寫分散開來,又能把正在使用的資料保持在一起,這些看似互相矛盾的目標在現實中卻往往是可以實現的。
我們先挑片鍵的幾個反面例子找茬,然後再拿幾個較好的例子來琢磨一番,MongoDB的wiki上也有一頁與選擇片鍵相關且很不錯的內容,可以看看。
1.1、小基數片鍵
一些人並不真正理解或者信任MongoDB自動分配資料的方式,所以他們總是沿著這麼個思路來向:我有4個分區,所以應該用一個由4個可能的欄位來做片鍵,這是一個非常糟糕的想法,為什麼呢?
假設我們有一個儲存使用者資訊的應用程式,每個文檔有一個continent欄位代表使用者所在地區,其欄位值可以是"Africa"、"Antarctica"、"Asia"、"Australia"、"Europe"、"North Ameraica"或者"south America",考慮到我們在每個大洲都有一個資料中心--或許不包括南極洲,並且想從人們所在當地的資料中心為其提供使用者資料,我們決定按該欄位進行分區。
集合開始於某個資料中心的一個分區的初始化塊(負無窮,正無窮),所有的插入和讀書都落在這一個塊上,一旦它變的足夠大,就會被劃分成兩個塊(區間分別為(負無窮,“Europe”)和["Europe",正無窮)),這樣一來,所有來自非洲(Africa)、南極洲(Antarctica)、亞洲(Asia)和澳洲(Australia)的文檔都會被分到第一個塊上,而所有來自歐洲(Europe)、北美(North America)或者南美(South America)的資料都會被分到第二塊上,隨著更多文檔被添加進來,集合最終會變成7個塊,如下所示:
每個大洋洲一個分區
然後呢?
MongoDB不能再進一步分割這些塊了,塊只能變得越來越大,雖然暫時不會出問題,但是當伺服器磁碟空間開始主鍵消耗盡時問題就會付出睡眠了,除了買塊更大的磁碟,你什麼也做不了。
由於片鍵值資料量有限,因此這種片鍵成為小基數片鍵(low-cardinality shard key)。如果選擇了一個基數很小的片鍵,到頭來肯定會得到一堆即巨大又無法移動,還不能分割的塊,它們會讓你極為不塊,這樣的生活你懂的。
如果這麼做是為了手動分配資料,那就不要再用MongoDB內建的分區機制了,否則它會和你一直抗爭到底,當然不管則樣你還是可以手動對集合進行分區,編寫自己的路由器,並將讀寫路由到任何一台伺服器上,只不過選擇一個號的片鍵並讓MongoDB來替你做這一切更加容易一些。
這個規則適用於任何取值個數有限的鍵,一定要記得,如果在某集合中一個鍵有N個值,那就只能有N個資料區塊,因此也只能有N個分區。如果打算採用小基數片鍵的原因是需要在那個欄位上進行大量查詢,請使用組合片鍵(一個片鍵包含兩個欄位),並確保第二個欄位有非常多不同的值可以供MongoDB用來進行分割。
如果一個集合有生命週期(比如,每周都建立一個新的集合而且你知道,在一周時間裡資料量不會接近任何分區的最大容量),就可以選擇這個生命週期為片鍵。
這個例子不僅僅是關於選擇小基數片鍵的,還與在MongoDB分區機制中添加資料中心感知(data-center awareness)支援的嘗試有關,到目前為止,分區上不支援資料中心感知,如果對此感興趣可以查看或者相關的問題投票。靠使用者自己實現的問題在於其擴充性並不是很好,如果你的應用最近在日本很流行怎麼辦?你可能會想添加第二個分區來應付亞洲地區的訪問量。但是你打算如何遷移資料呢?一個資料區塊增長到好幾GB大,你就沒法遷移它了,而且也不能再分割塊,因為整個塊就只有一個片鍵值,由於片鍵值無法更新,因此也不可能通過更新所有文檔來使用一個更獨特的片鍵值,可以刪除每個文檔,更新片鍵值,然後再把它重新儲存回去,但是對於大型資料庫而言那並不是一個能迅速完成的操作。你所能做的最好的事情就是在插入文檔時開始用Asia,Japan替代簡單的Asia,這樣一來會有一批舊文檔,其片鍵值應該Asia,Japan但卻是Asia,因此應用程式邏輯就不得不同時支援兩種情況,另外一旦開始擁有更細粒度的塊,就不能保證MongoDB還會把它們放在你所期望的地方(除非關閉平衡器並手動處理所有事情)。資料中心感知對於大型應用非常重要,而且它對MongoDB的開發人員有很高的優先順序,在這期間選擇一個小技術片鍵絕不對是一個好的解決方案。
1.2、升序片鍵從RAM中讀取資料要比從磁碟中讀取快,所以目標是儘可能多地訪問記憶體中的資料,因此,如果有些資料總是被一起訪問,我 們就希望片鍵能夠把它們保持到遺棄,對大部分應用程式而言,新資料被訪問的次數總比老資料多,所以人們往往會嘗試使用諸如時間戳記或者ObjectId一類的欄位來做片鍵,但是這並不像它們所期望的那樣可行。比如說我們有一個類似微博的服務,其中每個文檔都包含一條簡訊息、發送人以及發送時間,我們按發送時間欄位來分區,取值為自公元元年起經過的秒數。和往常一樣,還是從一個資料庫(負無窮、正無窮)開始,全部插入都會落在這個分區上至到它分裂成兩個塊,由於是從片鍵中點把塊分開來的,所以在我們分隔塊的那一刻,時間戳記很可能已經遠大於中間值了,這意味著再往後所有的插入都會落到第二個塊上,不會再有插入操作命中第一塊,一旦第二個塊填滿了,它就會分裂成為另外兩個塊,如[1294516901,1294930163)和[1294930163,正無窮)兩個塊,但是因為從現在起時間都在1294930163之後,所有新的插入都會被添加到區間為[1294930163,正無窮)的塊上,這個模式會持續下去,所有的資料總是被添加到最後,一個資料區塊上,即所有資料都會被添加到一個分區上,這個片鍵創造了一個單一且不可分散的熱點。
這條規則適用於任何升序排列的鍵值,而並不必須是時間戳記,其他例子包括ObjectId、日期、自增主鍵,只要鍵值趨向於無窮大,你就會面臨這個問題。
基本上,這種片鍵總是一個壞主意,因為它導致熱點必須存在,如果訪問量不大且用一個分區就能承受所有讀寫,那還行的通,當然如果遇到一個訪問量尖峰或者應用開始變得更受歡迎,那它會終止停止工作並且難以修複。除非你非常清楚自己在幹什麼,否則不要使用升序片鍵,肯定還有更好的片鍵存在,應該避免使用這一個。
1.3、隨機片鍵有時為了避免熱點,人們會選擇一個取值隨機的欄位來分區,採用這種片鍵一開始還不錯,但是隨著資料量越變越大,它會變得越來越慢。假設我們在分區集合中儲存照片的縮圖,每個文檔都包含了照片的位元據、位元據的MD5散列圖值,以及一段描述、拍照時間和拍照人,我們決定在MD5散列值上做分區。隨著集合的增長,我們最終會得到一組均勻分布各分區的資料區塊,目前為止一切順利,現在,假設我們非常忙而分區2上的而一個塊填滿並分裂了,設定管理員注意到分區2比分區1多出了10個塊並判定應該抹平分區間的差距,這樣MongoDB就需要隨機載入5個塊得資料到記憶體中並將其發送給分區1,考慮到資料序列的隨機性。一般情況下這些資料可能不會出現在記憶體中,所以此時的MongoDB會給RAM帶來更大的壓力,而且還會引發大量磁碟IP。除此之外,片鍵上必須有索引,因此如果選擇了從不依據它進行的隨機鍵,基本上可以說是浪費了一個索引,另外考慮到每增加一個索引都會讓寫操作變得更慢,所以保持索引數量儘可能低也是非常重要的。
1.4、好片鍵我們真正需要的是一種將訪問模式也考慮進去的方案,如果應用會規律地訪問25GB的資料,我們就希望所有的分割和歉意都發生在這25GB資料上,而不是隨機訪問資料以至於不斷地有新資料唄從磁碟中複製到記憶體裡。因此我們希望能找到這樣一個片鍵,它具備有良好的資料局部性特徵,但有不會因為太局部而導致熱點出現。
許多應用訪問新資料比老資料更頻繁,所以我們希望資料大致上按照時間排序,但是同時也要均勻分布,這樣一來既能把我們正在讀寫的資料保持在記憶體匯總,又可以使負載平衡地分散在叢集中。舉個例子,比如說有個剖析器,使用者會定期通過它訪問過去一個月的資料,而我 們希望能儘可能保持資料便於使用,因此可以在{month:1,user:1}上分區,其中month是一個粗粒度的升序欄位,即每個月它都會有一個更新更大的值,user適合作為第二個欄位,因為我們會經常查詢某個特定使用者的資料。
search欄位可不可以也是升序欄位?不可以,如果是,則該片鍵會降級成一個升序片鍵,進而使你同堂面臨普通升序鍵帶來的熱點問題。search欄位應該是什嗎?search欄位最好是應用程式可以用於查詢的東西,比如使用者資訊(比如上面的例子)、檔案名稱欄位或者GUID等,它應該具備非升序、分布隨機且基數適當的特點。
第五部分 架構篇 第二十章 MongoDB Sharding 架構( 片鍵選擇)