近期MongoDB在Hack News上是頻繁中槍。許多人更是聲稱恨上了MongoDB,David mytton就在他的部落格中揭露了MongoDB許多現存問題。然而恨的人有之偏愛的也同樣很多,作為回擊:Russell Smith帶來了多年工作經驗的總結。Russell Smith曾擔任Ops和大型網站縮放顧問並且協助過Guardian、Experian等多家公司,MongoDB London User Group的聯合創始人。作為MongoDB Master(MongoDB官方認可的MongoDB核心貢獻者組織,並通過社區分享自己的專業技術),其參與工作的基礎設施單伺服器每秒查詢超過3萬次,每天活躍資料更在1TB以上。
下面來看Russell對MongoDB一些常見及生僻的問題做出分析:
32位 vs 64位
現在大多數的伺服器都對32位作業系統實現支援,更有許多新型硬體支援著允許更多RAM的64位作業系統。
MongoDB也同時發布了32位及64位兩個版本的資料庫。歸結於MongoDB使用的記憶體對應檔,32位版本只支援2G資料的儲存。對於標準的Replica Set,MongoDB只擁有單一的處理策略 —— mongod。如果你想在未來儲存2G以上的資料,請使用64位版本的MongoDB。如果擁有分區安裝,那麼32位版本同樣可以使用。
總結:使用64位版本或者理解32位版本的限制。
檔案大小限制
不同於RDBMS把資料儲存在行與列中,MongoDB的資料是儲存在檔案中的。這些檔案使用二進位儲存形式,其格式為類似JSON格式的BSON格式。
和其它的資料庫一樣,單個檔案的儲存大小是有限制的。在舊版本的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文檔,或者觀看這些結構模型設計的相關視頻吧!
- Schema Design Basics
- Schema Design at Scale
- Schema Design Principles and Practice
總結:設計結構模型並充分利用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‘})是不同的。在這裡最理想的解決方案就是對需要查詢資料進行確認。你也可以通過Regex進行查詢,比如:db.people.find({name:/Russell/i}),但是這樣會影響到效能。
總結:查詢是區分大小寫,在犧牲速度的情況下可以利用Regex。
對輸入的資料無容錯性
當你嘗試向傳統資料庫插入錯誤類型的資料,傳統的資料庫一般會把資料轉換成預定義的類型。然而這在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,更改起來將是件很麻煩的事情。
點擊查看如何更改
總結:選擇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加上軟或硬的開啟檔案或使用者進程限制。
原文連結:MongoDB Gotchas & How To Avoid Them