近期MongoDB在Hack News上是頻繁中槍。許多人更是聲稱恨上了MongoDB,David mytton就在他的博客中揭露了MongoDB許多現存問題。然而恨的人有之偏心的也一樣不少,做爲回擊:Russell Smith帶來了多年工做經驗的總結。Russell Smith曾擔任Ops和大型網站縮放顧問而且幫助過Guardian、Experian等多家公司,MongoDB London User Group的聯合創始人。做爲MongoDB Master(MongoDB官方承認的MongoDB核心貢獻者組織,並經過社區分享本身的專業技術),其參與工做的基礎設施單服務器每秒查詢超過3萬次,天天活躍數據更在1TB以上。 linux
下面來看Russell對MongoDB一些常見及生僻的問題作出分析: git
32位 vs 64位 github
如今大多數的服務器都對32位操做系統實現支持,更有許多新型硬件支持着容許更多RAM的64位操做系統。 正則表達式
MongoDB也同時發佈了32位及64位兩個版本的數據庫。歸結於MongoDB使用的內存映射文件,32位版本只支持2G數據的存儲。對於標準的Replica Set,MongoDB只擁有單一的處理策略 —— mongod。若是你想在將來儲存2G以上的數據,請使用64位版本的MongoDB。若是擁有分片安裝,那麼32位版本一樣可使用。 mongodb
總結:使用64位版本或者理解32位版本的限制。 數據庫
文件大小限制 ubuntu
不一樣於RDBMS把數據儲存在行與列中,MongoDB的數據是儲存在文件中的。這些文件使用二進制存儲形式,其格式爲相似JSON格式的BSON格式。 centos
和其它的數據庫同樣,單個文件的儲存大小是有限制的。在舊版本的MongoDB中,單個文件都限制在4M之內。而新版本的MongoDB單文件已經支持到16M大小。這樣的限制也許是使人厭煩的,可是10gen的意見是:若是這項設置不停的困擾到你,那麼是否你的設計模式存在着問題;或者你可使用文件無大小限制的GridFS。 設計模式
這種狀況一般的建議是避免存儲過大的文件,不按期的更新數據庫中存儲的各類對象。而像Amazon S3或者Rackspace Cloudfiles這樣的服務一般可能會是更好的選擇,而非必要狀況下最好別讓基礎設施陷入過載。 安全
總結:把每一個文件保持在16M如下,那麼一切都好。
寫入失敗
MongoDB在默認的狀況下容許高速的寫入和更新,而付出的代價就是沒有明確的錯誤通知。默認狀況下多數的驅動都在作異步、「不安全」寫入 —— 這就意味着驅動程序不能當即反饋錯誤信息,相似於MySQL的INSERT DELAYED。若是你想知道某個事情是否成功,你必須使用getLastError手動的檢查錯誤信息。
某些狀況下若是你須要在錯誤發生後馬上獲得錯誤信息,即:大多數的驅動中都很容易實現同步「安全」查詢。這將謀殺掉MongoDB不一樣於傳統數據庫的優勢。
若是對比「徹底安全」的同步寫入你須要多一點性能,同時還想要必定程度的安全,那麼你可使用getLastError with‘j’讓MongoDB只到一份日誌提交後再發出錯誤報告通知。那麼日誌將以100毫秒一次的速度輸出到磁盤,而不是60秒。
總結:若是必需要寫入確認,你可使用安全寫入或getLastError。
數據結構模型的弱化不等於沒有數據結構模型
RDBMS通常都擁有一個預約義的數據結構模型:表格的行和列,每一個字段都擁有名稱和數據類型。若是你想給其中一行加一列,那麼你必須給整個表格都添加一列。
MongoDB則是移除了這個設置,對於Collection和文件沒有強制的模型限定。這有益於快速開發及簡易修改。
固然這不意味着你就能夠無視結構模型的設計,一個合適的結構模型可讓你得到MongoDB的最佳性能。趕快閱讀MongoDB文檔,或者觀看這些結構模型設計的相關視頻吧!
總結:設計結構模型並充分利用MongoDB的特點。
默認狀況下修改語句修改的只是單個文件
在傳統的RDBMS中除非使用LIMIT子句,修改語句做用的將是全部匹配的地方。然而MongoDB每一個查詢上都默認使用等價「LIMIT 1」的設置。雖然沒法作到「LIMIT 5」,可是你能夠經過下面的語句整個的移除限制:
db.people.update({age: {$gt: 30}}, {$set: {past_it: true}}, false, true)
一樣在官方的驅動中還有相似的選項 —— ‘multi’。
總結:能夠經過指定多個文件的multi爲true來完成多文件修改
查詢區分大小寫
字符串的查詢可能不按預期的那樣發展 —— 這歸結於MongoDB默認區分大小寫。
例如:db.people.find({name: ‘Russell’})與db.people.find({name: ‘ russell‘})是不一樣的。在這裏最理想的解決方案就是對須要查詢數據進行確認。你也能夠經過正則表達式進行查詢,好比:db.people.find({name:/Russell/i}),可是這樣會影響到性能。
總結:查詢是區分大小寫的,在犧牲速度的狀況下能夠利用正則表達式。
對輸入的數據無容錯性
當你嘗試向傳統數據庫插入錯誤類型的數據,傳統的數據庫通常會把數據轉換成預約義的類型。然而這在MongoDB中是行不通的,由於MongoDB的文件是沒有預約義數據模型的。這樣的話MongoDB會插入你輸入的任何數據。
總結:使用準確的數據類型。
關於鎖
當資源被代碼的多個部分所共享時,須要確信鎖必需要確保這處資源只能在一個地方被操做。
舊版本的MongoDB (pre 2.0)擁有一個全局的寫入鎖。這就意味貫穿整個服務器中只有一個地方作寫操做。這就可能致使數據庫由於某個地方鎖定超負載而停滯。這個問題在2.0版本中的獲得了顯著的改善,而且在當前2.2版本中獲得了進一步的增強。MongoDB 2.2使用數據庫級別的鎖在這個問題上邁進了一大步。一樣值得期待的Collection級別的鎖也計劃在下一個版本中推出。
儘管如此,Russell仍是認爲:大多數受此限制的應用程序於其說是受MongoDB影響,還不如說是程序自己的問題來的更直接。
總結:使用最新的穩定版本才能得到最高的性能。
關於包
在類Ubuntu和Debian系統上安裝時,許多人都出現過「過期版本」這樣的問題。解決方案很簡單:使用10gen官方庫,那麼在Ubuntu和Debian上安裝也會像在Fedora和Centos上安裝同樣流暢。
總結:使用擁有大多數最新版本的官方包。
使用偶數個Replica Set成員
Replica Set是增長冗餘及提高MongoDB數據集羣性能的有效途徑。數據在全部的節點中被複制,並選出一個做爲主節點。假如主節點出故障,那麼會在其餘的節點中票選一個做爲新的主節點。
在同一個Replica Set中使用兩臺機器是頗有誘惑的,它比3臺機器來的便宜而且也是RDBMS的標準行事風格。
可是到了MongoDB這裏,同一個Replica Set中的成員數量只能是奇數個。假如你使用了偶數個成員,那麼當主節點發生故障時那麼其它的節點都會變成只讀。發生這種狀況是由於剩下待選節點的數目不知足票選主節點的規定。
若是你想節約成本,同時還但願支持故障轉移和冗餘的加強,那麼你可使用Arbiter。Arbiter是一種特殊的Replica Set成員,它不儲存任何用戶數據(這就意味着他們可使用很是小的服務器)。
總結:只可使用偶數個Replica Set成員,可是可使用Arbitter來削減成本。
沒有join語句
MongoDB不支持join:若是你想在多個Collection中檢索數據,那麼你必須作屢次的查詢。
若是你以爲你手動作的查詢太多了,你能夠重設計你的數據模型來減小總體查詢的數量。MongoDB中的文件能夠是任何類型,那麼能夠輕易的對數據進行De-Normalize。這樣就可讓它始終和你的應用程序保持一致。
總結:沒有join不妨看一下如何設計數據結構模型。
Journaling
MongoDB使用內存映射文件而且每60秒向磁盤輸出一次通知,這就意味着最大程度上你可能丟失60秒加上向硬盤輸出通知這段時間內全部的數據。
爲了不數據丟失,MongoDB從2.0版本起就添加了Journaling(默認狀況下開啓)。Journaling把時間從60秒更改成100ms。若是數據庫意外的停機,在啓動以前它將會被重啓用以確保數據庫處於一致狀態。這也是MongoDB與傳統數據庫最接近的地方。
固然Journaling會輕微的影響到性能,大約5%。可是對於多數人來講額外帶來的安全性確定是物有所值的。
總結:最好別關閉Journaling。
默認狀況下沒有身份認證
MongoDB在默認設置下並無身份驗證。MongoDB會認爲自身處在一個擁有防火牆的信任網絡。可是這不表明它不支持身份驗證,若是須要能夠輕鬆的開啓。
總結:MongoDB的安全性能夠經過使用防火牆和綁定正確的接口來保證,固然也能夠開啓身份驗證。
Replica Set中損失的數據
使用Replica Set是提升系統可靠性及易維護的有效途徑。這樣的話,弄清節點間故障的發生及轉移機制就變得相當重要。
Replica Set中的成員通常經過oplog(記錄了數據中發生增、刪、改等操做的列表)來傳遞信息,當其中一個成員發生變化修改oplog後,其餘的成員也將按照oplog來執行。若是你負責處理新數據的節點在出錯後恢復運行,它將會被回滾至最後一個oplog公共點。然而在這個過程當中:丟失的「新數據」已經被MongoDB從數據庫中轉移並存放到你的數據目錄‘rollback’裏面等待被手動恢復。若是你不知道這個特性,你可能就會認爲數據被弄丟了。因此每當有成員從出錯中恢復過來都必需要檢查這個目錄。而經過MongoDB發佈的標準工具來恢復這些數據是件很容易的事情。查看官方文檔以瞭解更多相關信息。
總結:故障恢復中丟失的數據將會出如今rollback目錄裏面。
分片太遲
分片是把數據拆分到多臺機器上,一般被用於Replica Set運行過慢時進行性能提高。MongoDB支持自動分片。然而若是你讓分片進行太遲的話,問題就產生了。由於對數據的拆分和塊的遷移須要時間和資源,因此若是當服務器資源基本上耗盡時極可能會致使在你最須要分片時卻分不了片。
解決的方法很簡單,使用一個工具對MongoDB進行監視。對你的服務器作最準確的評估,而且在佔總體性能的80%前進行分片。相似的監視工具備:MMS、Munin(+Mongo Plugin)和CloudWatch。
若是你肯定從一開始就要分片處理,那麼更好的建議會是選用AWS或者相似的雲服務進行分片。而在小型服務器上,關機或者是調整機器明顯比轉移成千上萬條數據塊來的更直接一點。
總結:儘早的分片纔能有效的避免問題。
不能夠更改文件中的shard key
對於分片設置,shard key是MongoDB用來識別分塊對應文件的憑證。當你插入一個文件後,你就不能夠對文件的shard key進行更改。而這裏的解決方案是把文檔刪除而後從新創建,這樣就容許把它指定到對應的分塊了。
總結:shard key不能夠修改,必要的時候能夠刪除文件從新創建。
不能夠對256G以上的Collection進行分片
從新回到分片太遲的問題上來 —— MongoDB不容許對增加到256G以上的Collection進行分片,以前版本的設置尚未256G。這個限定在之後確定會被移除,而這裏也沒有更好的解決方案。只能進行重編譯或者把大小控制在256G如下。
總結:在Collection達到256G之前進行分片。
惟一性索引與共享
索引的惟一性約束只能經過shard key來保證。
選擇了錯誤的shard key
MongDB須要你選擇一個shard key來將數據分片。若是選擇了錯誤的shard key,更改起來將是件很麻煩的事情。
與MongoDB通訊的未經加密
與MongoDB的鏈接默認狀況下都是非加密的,這就意味你的數據可能被第三方記錄和使用。若是你的MongoDB是在本身的非廣域網下使用,那麼這種狀況是不可能發生的。
然而若是你是經過公網訪問MongoDB的話,那麼你確定會但願你的通訊是通過加密的。公版的MongoDB是不支持SSL的。慶幸的是能夠很是簡單的定製本身的版本。10gen的用戶則擁有特別定製的加密版本。幸運的是大部分的官方驅動都支持SSL,可是小麻煩一樣是不可避免的。點擊查看文檔。
總結:當用公網鏈接時,要注意和MongoDB的通訊是未加密的。
事務
不像MySQL這些支持多行數據原子操做的傳統數據庫,MongoDB只支持單文件的原子性修改。解決這個問題的方法之一是在應用程序中使用異步提交的方式;另外一個是:創建一個以上的數據存儲。雖然第一種方法並不適用於全部狀況,可是很顯然比第二個來的要好。
總結:不支持對多文件事務。
日誌預分配慢
MongDB可能會告訴你已經準備就緒,但事實上它還在對日誌進行分配。若是你選擇了讓機器自行分配,而恰巧你的文件系統和磁盤速度又很慢,那麼煩惱的事情發生了。一般狀況下這不會成爲問題,可是一旦出現了能夠使用undocumented flag –nopreallocj來關閉預分配。
總結:若是機器文件系統和磁盤過慢的話,那麼日誌的預分配也可能很慢。
NUMA + Linux +MongoDB
Linux、NUMA與MongoDB遇到一塊兒的時候運行老是不會很好。若是你在NUMA硬件上運行MongoDB的話,這裏建議是直接關掉。由於各類奇怪的問題隨之而來,好比:速度會階段性或者在CPU佔用率很高的時候大幅降低。
總結:禁NUMA。
Linux裏面的進程限制
若是你在MongoDB未滿載的時候出過SEGMENTATION FAULT錯誤,你可能會發現這是由於使用了太低或者默認的打開文件或用戶進程限制。10gen建議把限制設置在4K+,然而設置的大小該取決具體狀況。閱讀ulimit瞭解更多。
總結:長久的爲MongoDB在Linux加上軟或硬的打開文件或用戶進程限制。