[mongodb翻譯]選擇合適的shard key

爲一個集合(collection)選擇合適的shard key非常重要。如果這個集合非常龐大,那麼將來再來修改shard key將會很困難。如有任何疑問請到論壇或者IRC尋求幫助。


示例文檔

{
server : "ny153.example.com" ,
application : "apache" ,
time : "2011-01-02T21:21:56.249Z" ,
level : "ERROR" ,
msg : "something is broken"
}

基數(cardinality)

一個集合中的所有數據會被分裂爲多個數據塊(chunk),一個數據塊包含了某個範圍shard key的數據。請選擇合適的shard key,否則你將會得到很大的不能分裂的數據塊。

使用上面的日誌例子,如果你選擇了:

{server:1}

作爲shard key,那麼所有關於某個server的數據會存在一個數據塊中,你可以很容易想到一個server的數據會超過64MB(默認數據塊大小)。如果shard key是:

{server:1,time:1}

你可以將單個服務器的數據分裂到毫秒級。只要你的單個服務器不會有200MB/S,就不會有不能分裂的數據塊。

保持數據塊在一個合適大小是很重要的,這樣數據就可以在集羣中均衡分佈並且移動一個數據塊代價也不會太大。


水平寫

使用分片的一個主要原因就是分發寫操作。爲了達到這個目的,寫操作應當儘可能的分散到不同的數據塊。

再次使用前面的例子,選擇:

{ time : 1 }

作爲shard key,會導致所有的寫操作都集中到最新的數據塊中。如果shard key選擇:

{server:1,application:1,time:1}

那麼每個服務器:應用映射都會被寫到不同的地方。如果這裏有100種服務器:應用映射,和10臺服務器,那麼每臺服務器將會分配約1/10的寫操作。

需要注意的是,由於ObjectId中很重要的一部分是基於時間生成的,使用ObjectId作爲shard key等同於直接使用時間值。


查詢隔離

另外一個考慮就是任何一個查詢需要分發到多少個shard。理想情況下,一個查詢操作經mongos直接分發到擁有期望數據的mongod。如果你知道大部分的查詢使用了那些條件,那麼使用這些條件屬性作爲shard key可以提高很多效率。

即使查詢條件中沒有包含shard key,查詢依然可以工作。由於mongos不知道哪個shard擁有期望的數據,mongos會將這個請求順序分發到所有的shard中,這會增加響應時間和網絡數據流量及服務器負載。


排序

如果查詢中包含了排序請求,這個查詢請求會同以前沒有排序要求時一樣分發到需要的shard中。每一個shard執行查詢然後在本地做排序(如果有索引的話會使用索引)。mongos會合並從shard返回的已經排序好的結果,然後返回合併後的數據給客戶端。這樣的話,mongos只需要做少量的工作和很少的RAM。


可靠性

分片的一個重要方面就是如果整個shard不能訪問了(即使使用了可靠的複製組),這將會對整個系統帶來多大的影響。

例如你有一個類似於twitter的系統,評論記錄類似於:

{
_id: ObjectId("4d084f78a4c8707815a601d7"),
user_id : 42 ,
time : "2011-01-02T21:21:56.249Z" ,
comment : "I am happily using MongoDB",
}

由於系統對寫操作是非常敏感的,如果你希望將寫操作分散到各個服務器中,你需要使用"_id"或者"user_id"作爲shard key。"_id"可以給你較好的顆粒度和寫擴散性。但是一旦某個shard宕機了,它將會影響到幾乎所有的用戶(有些數據丟失了)。如果你使用"user_id"作爲shard key,此時一小部分用戶會受到影響(比如在5個shard組成的集羣中,這個百分比是20%),即使這些用戶再也看不到他們的任何數據了。


索引最佳化

正如前面章節關於索引的描述,通常經常對一部分索引做讀/更新會帶來較好的性能表現,這是因爲這“活躍”的部分可以大多數時間都駐留在RAM。前面介紹的shard key雖然可以將寫操作分散到各個shard中,但是他們還是屬於每個mongod的索引的。作爲替代,將時間戳分解爲某種形式並作爲shard key的前綴可以帶來一些好處,這樣可以減小經常訪問的索引大小。

例如你有一個圖片存儲系統,圖片記錄類似於:

{
_id: ObjectId("4d084f78a4c8707815a601d7"),
user_id : 42 ,
title: "sunset at the beach",
upload_time : "2011-01-02T21:21:56.249Z" ,
data: ...,
}

你可以定製一個包含了上傳時間的月份和唯一的標示符(如ObjectId,數據的md5值等)的_id來替代默認的_id,新的記錄類似於:

{
_id: "2011-01_4d084f78a4c8707815a601d7",
user_id : 42 ,
title: "sunset at the beach",
upload_time : "2011-01-02T21:21:56.249Z" ,
data: ...,
}

使用它作爲shard key,同時也是訪問一個文檔使用的_id.它可以很好的將寫操作分散到所有shard中。並且它減小了大部分查詢需要訪問的索引的大小。

進一步註釋:

  •  在每個月的開始,只有一臺shard被訪問知道均衡器開始分裂數據塊。爲了避免這種潛在的低性能和數據遷移,建議在時間前面增加了一個範圍值(比如5或者更大的範圍值如果你有5臺服務器)。
  • 更進一步的改善,你可以將用戶名(user id)納入到圖片id中,這樣可以保持同一個用戶的文檔都存儲到同一個shard中,例如:"2011-01_42_4d084f78a4c8707815a601d7"

GirdFS

根據不同的需要,這裏有多種不同的方法可以對GridFS進行分片。一種基於已經存在的索引的分片方法是:

  • "files"集合不做分片。所有的文件記錄都將存儲在一個shard中,強烈建議保證該shard非常可靠(使用至少3節點的複製組)。
  • "chunks"集合應當使用索引"files_id:1"進行分片。由驅動創建的已經存在的"files_id,n"索引不能被使用在"files_id"上面進行分片(這是一個限制,將來會被fix)。所以你需要單獨爲"files_id"創建一個索引。使用"files_id"進行分片開始保證某個文件存儲到同一個shard中,這樣"filemd5"命令就可以工作了。運行命令:
    > db.fs.chunks.ensureIndex({files_id: 1});
    > db.runCommand({ shardcollection : "test.fs.chunks", key : { files_id : 1 }})
    { "collectionsharded" : "test.fs.chunks", "ok" : 1 }
files_id默認使用ObjectId,因此files_id是遞增的,所有新的GridFS數據塊會被送往同一個shard。如果你的寫負載太大以至於單臺服務器無法應付,你可能需要考慮使用其他的shard key或者使用其他的值作爲file的_id.








發佈了5 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章