我希望一年前就知道 MongoDB 的那些事兒

jopen 11年前發布 | 21K 次閱讀 MongoDB

Heyzap 和 Bugsnag 我已經使用MongoDB超過一年了,我發現它是一個非常強大的數據庫。和其他的數據庫一樣,它有一些缺陷,但是這里有一些東西我希望有人可以早一點告訴我的。

即使建立索引選擇性計數還是很緩慢

舉個例子,當對用戶feed進行分頁時,你可能會看到類似的東西,

db.collection.count({username: "my_username"}); 

MongoDB,這種計數采取的數量級的時間比你希望的要長。有一個open ticke,目前為2.4,在這里,我希望希望他們可以將它弄出來。在那之前,你可以留下自己的數據匯總。在插入一個新的文檔的時候,你可以在mongo中用$inccommand存儲數據匯總。

副本集讀取不一致

當你開始利用副本集跨集群分發讀數結果,就會使自己卷入麻煩的漩渦。例如,如果你將數據寫到主設備,隨后的讀操作可能會被指引到從設備,而從設備尚有待拷貝數據。可以用類似下面的例子來說明這一點,

// Writes the object to the primary
db.collection.insert({_id: ObjectId("505bd76785ebb509fc183733"), key: "value"});

// This find is routed to a read-only secondary, and finds no results db.collection.find({_id: ObjectId("505bd76785ebb509fc183733")});</pre>

如果還有性能問題的話這個問題會更復雜,因為這會導致主設備與從設備之間的復制滯后,使其增長到數分鐘乃至某些情況下的數小時。

你既可以控制一次查詢是否運行于從設備,也可以控制在插入操作中有多少從設備執行復制操作,但是這將影響到性能,在某些情況甚至會引起永久阻塞!

范圍查詢被以不同順序索引

我發現范圍查詢使用的索引與其他的查詢有些許不同。一般在一次復合查詢中,你會用關鍵字作為索引操作的后一個參數。然而,在使用諸如$in之類的范圍查詢時,Mongo在應用該范圍前就應用了索引。這會導致對文檔的索引是在內存中完成,而這相當的慢!

// This doesn't use the last element in a compound index to sort
db.collection.find({_id: {$in : [
  ObjectId("505bd76785ebb509fc183733"), 
  ObjectId("505bd76785ebb509fc183734"),
  ObjectId("505bd76785ebb509fc183735"),
  ObjectId("505bd76785ebb509fc183736")
]}}).sort({last_name: 1});

在Heyzap,我們通過為這個查詢在Redis中創建一個緩存層,從而繞過了這個問題。但如果在$in聲明中你只有兩個數值,你也可以執行同樣的查詢兩次,或者如果你有可用的RAM,你也可以調整這個索引。

你可以讀到更多關于這個問題的內容,或者瀏覽此標簽

Mongo的 BSON ID 很好用

Mongo的 BSON ID給你提供了大量有用的功能,但當我我第一次使用Mongo時,我都沒有了解到可以用它所做事情的二分之一。例如,BSON ID的創建時間是被存儲于ID的。你可以提取出這個時間,輕松的擁有一個created_at字段!

// Will return the time the ObjectId was created
ObjectId("505bd76785ebb509fc183733").getTimestamp();

BSON ID會隨時間遞增,因此按id排序也就是按創建日期排序。這一列是自動索引的,所以查詢起來會相當快。你可以在10gen網站 讀到更多關于其內容。

索引所有的查詢

當我第一次使用Mongo時,我有時候會在特定的基礎環境上或從一個定時任務中執行查詢。最初那些查詢并沒有被索引,因為不需要面對用戶也不需要經常運行。然而這給別的索引查詢帶來了性能問題,因為未索引的查詢會做大量的磁盤讀操作,這影響到對未緩存文檔的檢索。于是我下決心要保證那些查詢至少是部分索引的,以免這樣的事情再次發生。

始終對新建的查詢執行explain操作

顯然,如果你有關系數據庫的使用背景,你一定熟悉 explain操作,這在Mongo里面同樣重要。當你為一個應用新增一個查詢時,你應該在生產數據上運行一下查詢以檢查其速度。你也可以要求Mongo執行explain 來解釋查詢到底怎么執行的,以便于你可以檢查索引之類的參數等等。

db.collection.find(query).explain()
{
    // BasicCursor means no index used, BtreeCursor would mean this is an indexed query
    "cursor" : "BasicCursor",

// The bounds of the index that were used, see how much of the index is being scanned
"indexBounds" : [ ],

// Number of documents or indexes scanned
"nscanned" : 57594,

// Number of documents scanned
"nscannedObjects" : 57594,

// The number of times the read/write lock was yielded
"nYields" : 2 ,

// Number of documents matched
"n" : 3 ,

// Duration in milliseconds
"millis" : 108,

// True if the results can be returned using only the index
"indexOnly" : false,

// If true, a multikey index was used
"isMultiKey" : false

}</pre>

我曾見過新部署的應用程序就因為其中的查詢語句沒有在生產環境中做驗證而導致的整個站點down掉。做個驗證是相對快速和容易做到的,所以不要找拒絕!

分析器

MongoDB自帶一個非常有用的分析器。你可以根據你的需要,設定分析器只分析超過一定時間的查詢。我設定是記錄所有超過100微秒的查詢。

// 分析所有超過100微秒的查詢
db.setProfilingLevel(1, 100);

// 分析所有的查詢 db.setProfilingLevel(2);

// 關閉分析器 db.setProfilingLevel(0);</pre>

分析器保存所有的分析數據到加了前綴的system.profile集。這和其他的數據集很像,你可以對他執行查詢,例如:

// 知道最近的分析
db.system.profile.find().sort({$natural:-1});

//找到超過5毫秒的查詢 db.system.profile.find( { millis : { $gt : 5 } } );

// 找到最慢的查詢 db.system.profile.find().sort({millis:-1});</pre>

你可以可以通過show profile  helper顯示最近分析輸出。

分析器本身會給每一個查詢增加一些開銷,但是我認為這是值得的。沒有他,你會是摸眼瞎。我寧可犧牲一點點的速度開銷,來讓我能看到是那些查詢導致問題的。沒有它你根本意識不到你的查詢對用戶來說到底有多慢。

有用的 Mongo命令行

這兒是一些有用的命令行,你可以在mongo shell中運行它們,在監視你服務器狀態。這些都可以腳本化,所以你可以用它獲得數據,用來監控或者繪制圖表。

  • db.currentOp() - 顯示你所有當前正在運行操作
  • db.killOp(opid) - 可以用來殺掉長查詢
  • db.serverStatus() - 顯示你整個服務器的狀態,對監控非常有用
  • db.stats() - 顯示你選中庫的狀態
  • db.collection.stats() -  特定集的狀態
  • </ul>


    監控

    經過過去一年對Mongo生產實例的監控,我列出了系列需要監控的關鍵閾值。

    索引大小

    根據你你的工作需要調整MongoD內存中合適的大小,這是必須的。以Heyzap為例,我們需要把所有的索引都放到內存里,當瀏覽老游戲或者用戶配置時候,我們要很頻繁地查詢整個數據集。

    通過圖表化的索引大小,讓Heyzap能準確的預知何時需要增加服務器,何時刪除索引或者其他的方法處理來增加索引的大小。我們能夠在大約一天內預知是否需要處理當前索引增長的問題。

    當前操作值

    圖形化你mongo數據庫的當前操作值,可以讓你了解何時你數據庫開始變慢了。如果你注意到你當前操作值的一個異常值,我就可以查找其他數據尋找導致問題的原因。是不是當時有一個慢查詢?是不是訪問增加了?我怎么減緩這個壓力?當操作值出現異常時,對一個復制集來說常常會導致復制延遲,所以當務之急是趕緊解決由于復制集導致讀到的數據不一致的問題。

    索引偏差數

    索引偏差產生是在MongoDB開始命中硬盤去載入一個索引,這通常意味著你的工作集不再能命中內存數據了。這個值理論上應該為0,取決于你的使用,它實際上達不到0。偶爾從硬盤載入一個索引對整體性能不會有很大的影響。你應該讓你的索引偏差數盡可能的低。

    復制延遲

    如果你使用復制作為備份,或者你使用復制從庫提供讀訪問,你就應該監控你復制延遲。如果你的備份滯后于主節點幾個小時的話,那將非常危險。同時從從庫讀到延遲與主庫幾個小時也會讓你的用戶混亂的。

    I/O 性能

    如果你的云端上運行MongoDB,比如使用亞馬遜的EBS,查看你磁盤運行的怎么樣,非常有用。你可以得到I/O性能的隨機下降,而且你需要把它關聯到性能指標中去,比如作為一個當前操作數據峰值的解釋。一些監控工具,比如iostat可以告訴你所有你需要的信息,讓你了解你硬盤行為。

    監控命令

    有很多非常酷的應用可以用來監控你的數據庫實例