2012-11-30 137 views
0

我試圖建立一個需要非常高的寫入吞吐量和合理的讀取吞吐量的數據庫。我有一套分佈式系統,將「事件」數據添加到數據庫中。MongoDb複合索引優化

目前,事件記錄的ID是Guid。我一直在閱讀的指南不傾向於創建很好的索引,因爲它們的隨機分佈意味着最近的數據將分散在磁盤中,這可能導致分頁問題。

所以這裏是我想要驗證的第一個假設: 我假設我不想選擇創建一個正確平衡樹的_id,例如類似自動編號的東西。這將是有益的,因爲最近的兩個事件基本上在磁盤上彼此相鄰。這是一個正確的假設嗎?

假設(1)是正確的,那麼我試圖找出產生這樣一個id的最佳方法。我知道Mongo本身支持ObjectId,這對於可以將數據綁定到Mongo的應用程序很方便,但我的應用程序不是這樣的。由於有多個系統產生數據,模擬「自動編號」字段有點問題,因爲mongo不支持服務器端的自動編號,所以生產者必須分配id,如果他們不願意不知道其他系統在做什麼。

爲了解決這個問題,我正在考慮做的是使_id字段成爲{localId,producerId}上的複合鍵,其中本地id是生產者可以生成的自動編號,因爲producerId將使其唯一。 ProducerId是我可以在生產者之間進行談判的事情,以便他們能夠提出獨特的ID。

所以這裏是我的下一個問題: 如果我的目標是從所有生產者獲取最新的數據,那麼{localId,producerId}應該是首選的關鍵順序,因爲localId將是正確的,並且producerId將是一個小羣,我寧願2個最近的事件保持本地對方。如果我倒順序,那麼我如何樹最終將看推理會像下面這樣:

   root 
     /  |   \ 
     p0  p1   p2 
    /  |   \ 
    e0..n  e0..n  e0..n 

其中p#是製造端ID,以及e#是一個事件。這好像會將我的索引分割成p#數據簇,而新事件不一定會彼此相鄰。我對首選訂單的假設應該(請驗證)看起來像這樣:

   root 
    /  |   \ 
    e0   e1   e2 
    /   |   \ 
    p0..n   p0..n  p0..n 

這似乎使最近的事件彼此靠近。 (我知道Mongo使用B樹作爲索引,但我只是試圖在這裏簡化視覺)。

唯一需要注意{LOCALID,producerId}我可以看到的是,由用戶常用的查詢將列出了由生產者,其{producerId,LOCALID}實際上會處理好很多最近的事件。爲了使這個查詢與{localId,producerId}一起工作,我想我還需要將producerId作爲字段添加到文檔中,並對其進行索引。

爲了明確我在這裏的問題的真實性,我想知道我是否正確思考這個問題,或者是否有明顯更好的方法來解決這個問題。

感謝

+0

如果您打算開發一個項目,請考慮Apache Cassandra。對於繁重的寫作來說,它非常驚人 –

+0

該應用程序已經相當成熟,我們已經對Mongo有所承諾。我只需要經歷一個優化階段,因爲我們由於磁盤抖動而不能保持高速運轉。我會看看卡桑德拉,謝謝。 – Mranz

+0

說實話,我目前面臨類似的問題。即使我的應用程序已經非常成熟,我覺得在某些時候,mongo可能不如cassandra所能做的那麼好。雖然mongo 2.2很酷 –

回答

1

要回答你的問題:這樣的化合物:{A,B}將分散的查詢結束,如果你只是用b查詢,然後排序由。但它會使用索引進行排序。

如果使用Document而不是ObjectId,_id將被索引但未使用,但它不是複合索引!

例子:

鑑於此文檔中集 '一' 並沒有額外指數:

{ "_id" : { "e" : 1, "p" : 1 } } 
{ "_id" : { "e" : 1, "p" : 2 } } 
{ "_id" : { "e" : 2, "p" : 1 } } 
{ "_id" : { "e" : 1, "p" : 3 } } 
{ "_id" : { "e" : 2, "p" : 3 } } 
{ "_id" : { "e" : 2, "p" : 2 } } 
{ "_id" : { "e" : 3, "p" : 1 } } 
{ "_id" : { "e" : 3, "p" : 2 } } 
{ "_id" : { "e" : 3, "p" : 3 } } 

這樣的查詢:

db.a.find({'_id.p' : 2}).sort({'_id.e' : 1}).explain() 

將不會使用索引:

{ 
    "cursor" : "BasicCursor", 
    "nscanned" : 9, 
    "nscannedObjects" : 9, 
    "n" : 3, 
    "scanAndOrder" : true, 
    "millis" : 0, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "isMultiKey" : false, 
    "indexOnly" : false, 
    "indexBounds" : { 
    } 
} 

僅僅因爲文檔已編入索引。

如果你創建這樣一個指標:

db.a.ensureIndex({'_id.e' : 1, '_id.p' : 1}) 

,然後再次查詢:

db.a.find({'_id.p' : 2}).sort({'_id.e' : 1}).explain() 

{ 
    "cursor" : "BtreeCursor _id.e_1__id.p_1", 
    "nscanned" : 9, 
    "nscannedObjects" : 3, 
    "n" : 3, 
    "millis" : 0, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "isMultiKey" : false, 
    "indexOnly" : false, 
    "indexBounds" : { 
     "_id.e" : [ 
      [ 
       { 
        "$minElement" : 1 
       }, 
       { 
        "$maxElement" : 1 
       } 
      ] 
     ], 
     "_id.p" : [ 
      [ 
       2, 
       2 
      ] 
     ] 
    } 
} 

它會在索引查詢(nscanned:9),因爲排序,然後取了對象:3,優於_id排序(nscanned和nscannedObjects爲9)。

Documentation .explain()

所以對於高寫入吞吐量(超過15K寫入秒),你就可能碎片。如果選項設置,兩個索引都將保證唯一性。但只有複合分片鍵可以幫助您進行直接查詢並且不會分散收集。使用({'_id.e':1,'_id.p':1})作爲分片鍵將直接路由所有的「_id.e」查詢,但不是「_id.p」(不含'e' )查詢,所以這些查詢將發送給每個主機,並以索引查找結束,但可能會很快(取決於網絡等)。如果你想用「p」羣集這些查詢你必須把「_id.p」作爲複合鍵的第一部分,像這樣:

{'_id.p' : 1, '_id.e' : 1} 

因此,所有的「P」查詢是直接查詢。但是,是的,這會分散整個集羣中的最近事件。因此,使用基於時間的密鑰的單獨索引可能會加速這些分散查詢。

我會生成一些示例數據,並在開發系統中使用兩個分片的設置中使用它並使用.explain()來選擇分片鍵+索引。

+0

感謝您的迴應。在我問這個問題之後,我實際上遇到了mongo索引文件而不是字段問題。這令我感到驚訝,我希望有一種方式讓我告訴mongo我希望_id指數複合,也許在未來。感謝您的建議,我會和他們一起玩。 – Mranz