進(jìn)行了一下Mongodb億級(jí)數(shù)據(jù)量的性能測(cè)試,分別測(cè)試如下幾個(gè)項(xiàng)目:
(所有插入都是單線程進(jìn)行,所有讀取都是多線程進(jìn)行)
1) 普通插入性能 (插入的數(shù)據(jù)每條大約在1KB左右)
2) 批量插入性能 (使用的是官方C#客戶端的InsertBatch),這個(gè)測(cè)的是批量插入性能能有多少提高
3) 安全插入功能 (確保插入成功,使用的是SafeMode.True開關(guān)),這個(gè)測(cè)的是安全插入性能會(huì)差多少
4) 查詢一個(gè)索引后的數(shù)字列,返回10條記錄(也就是10KB)的性能,這個(gè)測(cè)的是索引查詢的性能
5) 查詢兩個(gè)索引后的數(shù)字列,返回10條記錄(每條記錄只返回20字節(jié)左右的2個(gè)小字段)的性能,這個(gè)測(cè)的是返回小數(shù)據(jù)量以及多一個(gè)查詢條件對(duì)性能的影響
6) 查詢一個(gè)索引后的數(shù)字列,按照另一個(gè)索引的日期字段排序(索引建立的時(shí)候是倒序,排序也是倒序),并且Skip100條記錄后返回10條記錄的性能,這個(gè)測(cè)的是Skip和Order對(duì)性能的影響
7) 查詢100條記錄(也就是100KB)的性能(沒有排序,沒有條件),這個(gè)測(cè)的是大數(shù)據(jù)量的查詢結(jié)果對(duì)性能的影響
8) 統(tǒng)計(jì)隨著測(cè)試的進(jìn)行,總磁盤占用,索引磁盤占用以及數(shù)據(jù)磁盤占用的數(shù)量
并且每一種測(cè)試都使用單進(jìn)程的Mongodb和同一臺(tái)服務(wù)器開三個(gè)Mongodb進(jìn)程作為Sharding(每一個(gè)進(jìn)程大概只能用7GB左右的內(nèi)存)兩種方案
其實(shí)對(duì)于Sharding,雖然是一臺(tái)機(jī)器放3個(gè)進(jìn)程,但是在查詢的時(shí)候每一個(gè)并行進(jìn)程查詢部分?jǐn)?shù)據(jù),再有運(yùn)行于另外一個(gè)機(jī)器的mongos來匯總數(shù)據(jù),理論上來說在某些情況下性能會(huì)有點(diǎn)提高
基于以上的種種假設(shè),猜測(cè)某些情況性能會(huì)下降,某些情況性能會(huì)提高,那么來看一下最后的測(cè)試結(jié)果怎么樣?
備注:測(cè)試的存儲(chǔ)服務(wù)器是 E5620 @ 2.40GHz,24GB內(nèi)存,CentOs操作系統(tǒng),打壓機(jī)器是E5504 @ 2.0GHz,4GB內(nèi)存,Windows Server 2003操作系統(tǒng),兩者千兆網(wǎng)卡直連。
從這個(gè)測(cè)試可以看出,對(duì)于單進(jìn)程的方式:
1) Mongodb的非安全插入方式,在一開始插入性能是非常高的,但是在達(dá)到了兩千萬(wàn)條數(shù)據(jù)之后性能驟減,這個(gè)時(shí)候恰巧是服務(wù)器24G內(nèi)存基本占滿的時(shí)候(隨著測(cè)試的進(jìn)行mongodb不斷占據(jù)內(nèi)存,一直到操作系統(tǒng)的內(nèi)存全部占滿),也就是說Mongodb的內(nèi)存映射方式,使得數(shù)據(jù)全部在內(nèi)存中的時(shí)候速度飛快,當(dāng)部分?jǐn)?shù)據(jù)需要換出到磁盤上之后,性能下降很厲害。(這個(gè)性能其實(shí)也不算太差,因?yàn)槲覀儗?duì)三個(gè)列的數(shù)據(jù)做了索引,即使在內(nèi)存滿了之后每秒也能插入2MB的數(shù)據(jù),在一開始更是每秒插入25MB數(shù)據(jù))。Foursquare其實(shí)也是把Mongodb當(dāng)作帶持久化的內(nèi)存數(shù)據(jù)庫(kù)使用的,只是在查不到達(dá)到內(nèi)存瓶頸的時(shí)候Sharding沒處理好。
2) 對(duì)于批量插入功能,其實(shí)是一次提交一批數(shù)據(jù),但是相比一次一條插入性能并沒有提高多少,一來是因?yàn)榫W(wǎng)絡(luò)帶寬已經(jīng)成為了瓶頸,二來我想寫鎖也會(huì)是一個(gè)原因。
3) 對(duì)于安全插入功能,相對(duì)來說比較穩(wěn)定,不會(huì)波動(dòng)很大,我想可能是因?yàn)榘踩迦胧谴_保數(shù)據(jù)直接持久化到磁盤的,而不是插入內(nèi)存就完事。
4) 對(duì)于一列條件的查詢,性能一直比較穩(wěn)定,別小看,每秒能有8000-9000的查詢次數(shù),每次返回10KB,相當(dāng)于每秒查詢80MB數(shù)據(jù),而且數(shù)據(jù)庫(kù)記錄是2億之后還能維持這個(gè)水平,性能驚人。
5) 對(duì)于二列條件返回小數(shù)據(jù)的查詢,總體上性能會(huì)比4)好一點(diǎn),可能返回的數(shù)據(jù)量小對(duì)性能提高比較大,但是相對(duì)來說性能波動(dòng)也厲害一點(diǎn),可能多了一個(gè)條件就多了一個(gè)從磁盤換頁(yè)的機(jī)會(huì)。
6) 對(duì)于一列數(shù)據(jù)外加Sort和Skip的查詢,在數(shù)據(jù)量大了之后性能明顯就變差了(此時(shí)是索引數(shù)據(jù)量超過內(nèi)存大小的時(shí)候,不知道是否有聯(lián)系),我猜想是Skip比較消耗性能,不過和4)相比性能也不是差距特別大。
7) 對(duì)于返回大數(shù)據(jù)的查詢,一秒瓶頸也有800次左右,也就是80M數(shù)據(jù),這就進(jìn)一步說明了在有索引的情況下,順序查詢和按條件搜索性能是相差無(wú)幾的,這個(gè)時(shí)候是IO和網(wǎng)絡(luò)的瓶頸。
8) 在整個(gè)過程中索引占的數(shù)據(jù)量已經(jīng)占到了總數(shù)據(jù)量的相當(dāng)大比例,在達(dá)到1億4千萬(wàn)數(shù)據(jù)量的時(shí)候,光索引就可以占據(jù)整個(gè)內(nèi)存,此時(shí)查詢性能還是非常高,插入性能也不算太差,mongodb的性能確實(shí)很牛。
那么在來看看Sharding模式有什么亮點(diǎn):
1) 非安全插入和單進(jìn)程的配置一樣,在內(nèi)存滿了之后性能急劇下降。安全插入性能和單進(jìn)程相比慢不少,但是非常穩(wěn)定。
2) 對(duì)于一個(gè)條件和兩個(gè)條件的查詢,性能都比較穩(wěn)定,但條件查詢性能相當(dāng)于單進(jìn)程的一半,但是在多條件下有的時(shí)候甚至?xí)葐芜M(jìn)程高一點(diǎn)。我想這可能是某些時(shí)候數(shù)據(jù)塊位于兩個(gè)Sharding,這樣Mongos會(huì)并行在兩個(gè)Sharding查詢,然后在把數(shù)據(jù)進(jìn)行合并匯總,由于查詢返回的數(shù)據(jù)量小,網(wǎng)絡(luò)不太可能成為瓶頸了,使得Sharding才有出頭的機(jī)會(huì)。
3) 對(duì)于Order和Skip的查詢,Sharding方式的差距就出來了,我想主要性能損失可能在Order,因?yàn)槲覀儾]有按照排序字段作為Sharding的Key,使用的是_id作為Key,這樣排序就比較難進(jìn)行。
4) 對(duì)于返回大數(shù)據(jù)量的查詢,Sharding方式其實(shí)和單進(jìn)程差距不是很大,我想數(shù)據(jù)的轉(zhuǎn)發(fā)可能是一個(gè)性能損耗的原因(雖然mongos位于打壓機(jī)本機(jī),但是數(shù)據(jù)始終是轉(zhuǎn)手了一次)。
5) 對(duì)于磁盤空間的占用,兩者其實(shí)是差不多的,其中的一些差距可能是因?yàn)槎鄠€(gè)進(jìn)程都會(huì)多分配一點(diǎn)空間,加起來有的時(shí)候會(huì)比單進(jìn)程多占用點(diǎn)磁盤(而那些占用比單進(jìn)程少的地方其實(shí)是開始的編碼錯(cuò)誤,把實(shí)際數(shù)據(jù)大小和磁盤文件占用大小搞錯(cuò)了)。
測(cè)試最后的各個(gè)Sharding分布情況如下:
{
"sharded" : true,
"ns" : "testdb.test",
"count" : 209766143,
"size" : 214800530672,
"avgObjSize" : 1024.0000011441311,
"storageSize" : 222462757776,
"nindexes" : 4,
"nchunks" : 823,
"shards" : {
"shard0000" : {
"ns" : "testdb.test",
"count" : 69474248,
"size" : 71141630032,
"avgObjSize" : 1024.0000011515058,
"storageSize" : 74154252592,
"numExtents" : 65,
"nindexes" : 4,
"lastExtentSize" : 2146426864,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 11294125824,
"indexSizes" : {
"_id_" : 2928157632,
"Number_1" : 2832745408,
"Number1_1" : 2833974208,
"Date_-1" : 2699248576
},
"ok" : 1
},
"shard0001" : {
"ns" : "testdb.test",
"count" : 70446092,
"size" : 72136798288,
"avgObjSize" : 1024.00000113562,
"storageSize" : 74154252592,
"numExtents" : 65,
"nindexes" : 4,
"lastExtentSize" : 2146426864,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 11394068224,
"indexSizes" : {
"_id_" : 2969355200,
"Number_1" : 2826453952,
"Number1_1" : 2828403648,
"Date_-1" : 2769855424
},
"ok" : 1
},
"shard0002" : {
"ns" : "testdb.test",
"count" : 69845803,
"size" : 71522102352,
"avgObjSize" : 1024.00000114538,
"storageSize" : 74154252592,
"numExtents" : 65,
"nindexes" : 4,
"lastExtentSize" : 2146426864,
"paddingFactor" : 1,
"flags" : 1,
"totalIndexSize" : 11300515584,
"indexSizes" : {
"_id_" : 2930942912,
"Number_1" : 2835243968,
"Number1_1" : 2835907520,
"Date_-1" : 2698421184
},
"ok" : 1
}
},
"ok" : 1
}
雖然在最后由于時(shí)間的關(guān)系,沒有測(cè)到10億級(jí)別的數(shù)據(jù)量,但是通過這些數(shù)據(jù)已經(jīng)可以證明Mongodb的性能是多么強(qiáng)勁了。另外一個(gè)原因是,在很多時(shí)候可能數(shù)據(jù)只達(dá)到千萬(wàn)我們就會(huì)對(duì)庫(kù)進(jìn)行拆分,不會(huì)讓一個(gè)庫(kù)的索引非常龐大。在測(cè)試的過程中還發(fā)現(xiàn)幾個(gè)問題需要值得注意:
1) 在數(shù)據(jù)量很大的情況下,對(duì)服務(wù)進(jìn)行重啟,那么服務(wù)啟動(dòng)的初始化階段,雖然可以接受數(shù)據(jù)的查詢和修改,但是此時(shí)性能很差,因?yàn)閙ongodb會(huì)不斷把數(shù)據(jù)從磁盤換入內(nèi)存,此時(shí)的IO壓力非常大。
2) 在數(shù)據(jù)量很大的情況下,如果服務(wù)沒有正常關(guān)閉,那么Mongodb啟動(dòng)修復(fù)數(shù)據(jù)庫(kù)的時(shí)間非常可觀,在1.8中退出的-dur貌似可以解決這個(gè)問題,據(jù)官方說對(duì)讀取沒影響,寫入速度會(huì)稍稍降低,有空我也會(huì)再進(jìn)行下測(cè)試。
3) 在使用Sharding的時(shí)候,Mongodb時(shí)不時(shí)會(huì)對(duì)數(shù)據(jù)拆分搬遷,這個(gè)時(shí)候性能下降很厲害,雖然從測(cè)試圖中看不出(因?yàn)槲颐恳淮螠y(cè)試都會(huì)測(cè)試比較多的迭代次數(shù)),但是我在實(shí)際觀察中可以發(fā)現(xiàn),在搬遷數(shù)據(jù)的時(shí)候每秒插入性能可能會(huì)低到幾百條。其實(shí)我覺得能手動(dòng)切分?jǐn)?shù)據(jù)庫(kù)就手動(dòng)切分或者手動(dòng)做歷史庫(kù),不要依賴這種自動(dòng)化的Sharding,因?yàn)橐婚_始數(shù)據(jù)就放到正確的位置比分隔再搬遷效率不知道高多少。個(gè)人認(rèn)為Mongodb單數(shù)據(jù)庫(kù)存儲(chǔ)不超過1億的數(shù)據(jù)比較合適,再大還是手動(dòng)分庫(kù)吧。
4) 對(duì)于數(shù)據(jù)的插入,如果使用多線程并不會(huì)帶來性能的提高,反而還會(huì)下降一點(diǎn)性能(并且可以在http接口上看到,有大量的線程處于等待)。
5) 在整個(gè)測(cè)試過程中,批量插入的時(shí)候遇到過幾次連接被遠(yuǎn)程計(jì)算機(jī)關(guān)閉的錯(cuò)誤,懷疑是有的時(shí)候Mongodb不穩(wěn)定關(guān)閉了連接,或是官方的C#客戶端有BUG,但是也僅僅是在數(shù)據(jù)量特別大的時(shí)候遇到幾次。
最新補(bǔ)充:在之后我又進(jìn)行了幾天測(cè)試,把測(cè)試數(shù)據(jù)量進(jìn)一步加大到5億,總磁盤占用超過500G,發(fā)現(xiàn)和2億數(shù)據(jù)量相比,所有性能都差不多,只是測(cè)試6和測(cè)試7在超過2億級(jí)別數(shù)據(jù)之后,每400萬(wàn)記錄作為一個(gè)循環(huán),上下波動(dòng)30%的性能,非常有規(guī)律。
每天數(shù)據(jù)1kb是純文本的大小嗎?包括json tag嗎?數(shù)據(jù)和索引各有幾列?
1kb是包括tag后大概的大小,數(shù)據(jù)4列,其中3列做索引(2個(gè)數(shù)值和1個(gè)日期),另外一列存950字節(jié)。至于為什么Total Size(index+data)的值不是data size和index size之和?
我也清楚,這里的值取的是db.stats()中的storagesize/datasize和indexsize
總是搜索總數(shù)據(jù)范圍的前10%,在插入數(shù)據(jù)的時(shí)候數(shù)字字段隨機(jī)選取525600內(nèi)的一個(gè)值。
我覺得你這里說的“每秒能有8000-9000的查詢次數(shù)”,查詢到的是索引數(shù)據(jù),還沒有真正去讀數(shù)據(jù)回來,例如:如果你用java client api寫的測(cè)試程序,如果調(diào)用find(query)去做測(cè)試,其實(shí)這里面返回的是DBCursor,而不是Document,需要繼續(xù)調(diào)用next()才返回?cái)?shù)據(jù);如果調(diào)用findOne(query)去做的測(cè)試,它返回的則是Document。
所以我覺得你說每秒查詢80MB的數(shù)據(jù)有問題,因?yàn)槊看螞]有返回10KB回來。
不存在這個(gè)問題,我是ToList()的
謝謝你的回復(fù)~
因?yàn)槲疫@邊是用java寫的benchmark測(cè)試程序,當(dāng)我使用find(query)會(huì)有很高的qps,因?yàn)檫@個(gè)僅僅返回的是DBCursor信息,而不是真正的數(shù)據(jù),不是我想要的。當(dāng)我用findOne(query)的時(shí)候qps就慘目忍睹了。
所以還是要問幾個(gè)問題:
1) 你測(cè)試的讀操作是順序讀還是隨機(jī)讀?
2)關(guān)于測(cè)試5“查詢兩個(gè)索引后的數(shù)字列,返回10條記錄(每條記錄只返回20字節(jié)左右的2個(gè)小字段)的性能,這個(gè)測(cè)的是返回小數(shù)據(jù)量以及多一個(gè)查詢條件對(duì)性能的影響”,按照對(duì)應(yīng)圖形來看,每秒鐘大概讀的數(shù)據(jù)是10000qps * 20byte * 10條記錄 = 2M/s,這個(gè)速度和測(cè)試4比就相差很多了啊!
3)按照測(cè)試4給出的結(jié)果,在數(shù)據(jù)量遠(yuǎn)遠(yuǎn)大于內(nèi)存大小的時(shí)候,讀性能還是勝過寫性能。
4)測(cè)試讀的時(shí)候,client端開了多少個(gè)線程?
2) 一條記錄是1K而不是20字節(jié)哦
3)使用并行庫(kù),所以我估計(jì)是8個(gè)線程,對(duì)應(yīng)八核
下面是正文中的引用:
“5) 查詢兩個(gè)索引后的數(shù)字列,返回10條記錄(每條記錄只返回20字節(jié)左右的2個(gè)小字段)的性能,這個(gè)測(cè)的是返回小數(shù)據(jù)量以及多一個(gè)查詢條件對(duì)性能的影響”
另外我看到的關(guān)于mongodb的測(cè)試"MongoDB Performance for more data than memory", 他的測(cè)試結(jié)果在“https://spreadsheets.google.com/ccc?key=0At_QEIJCB2GbdEI3d3VBR2hFY1AyVTVNY0N6OGh5dXc&hl=en#”可以看看第六行,從1000w條記錄中讀取30000條大概花了74.09s的時(shí)間,那么qps就是30000/74.09 = 404
有什么問題,歡迎糾正。
性能上可能是會(huì)有差距
其實(shí)我的測(cè)試環(huán)境也是千兆直連的,讀性能不理想。
不知服務(wù)器配置是怎么樣的? 可能配置不是太高吧
另外不知道你看沒看讀操作時(shí)候的iostat信息,rMB/s
服務(wù)器的內(nèi)存肯定沒你這么大(我的是8G),我測(cè)試數(shù)據(jù)量是20G(2000w條記錄),但是我主要是想看一下當(dāng)數(shù)據(jù)量遠(yuǎn)遠(yuǎn)大于內(nèi)存的時(shí)候,性能如何。
雖然你的內(nèi)存是24G,但是你的記錄也達(dá)到了2億條,所以數(shù)據(jù)量也已經(jīng)遠(yuǎn)遠(yuǎn)大于內(nèi)存了。
你服務(wù)器用的是raid啊,我擦!!! 我的是SCSI磁盤!
當(dāng)Mongodb處理讀操作的時(shí)候,用iostat查看util%一直處于100%,說明mongodb一直在忙活I(lǐng)O操作呢,rMB/s很低!
所以我這里性能低主要是因?yàn)榇疟PIO太爛!
差距啊!謝謝你的回復(fù)!
如果追求高可靠性,性能肯定會(huì)有所下降,你說的功能可以通過SafeMode的Insert或Update實(shí)現(xiàn),確保所有修改應(yīng)用到多個(gè)Node
我本來是用SafeMode 反回的 我用的是C#
他有個(gè)SafeModelResult r = Coll.Insert(xxx);
if (r.Ok == true/false)
但是提示 未將對(duì)象引用設(shè)置到對(duì)象的實(shí)例...
數(shù)據(jù)也沒有插入進(jìn)去,但是直接寫成 Coll.Insert(xxx); 卻插入成功!
很奇怪.
什么驅(qū)動(dòng)? 懷疑是不是每一次都使用了新的連接池?
那有什么辦法提高效率呢?連接池做全局靜態(tài)變量?
我不知道你事什么驅(qū)動(dòng),應(yīng)該驅(qū)動(dòng)把連接池對(duì)開發(fā)透明,在內(nèi)部會(huì)維護(hù)一個(gè)連接字符串具有唯一連接池的!如果你不確定的話,把連接池或類似server的入口對(duì)象設(shè)置為靜態(tài)
public class MyMongoDb
{
private Mongo _mongo;
private IMongoDatabase _db;
public MyMongoDb() : this("Server=" + ConfigurationManager.AppSettings["MongoSevers"].ToString() + ";ConnectTimeout=500000;ConnectionLifetime=400000;MinimumPoolSize=10;MaximumPoolSize=500;Pooled=true", ConfigurationManager.AppSettings["MongoDB"].ToString())
{
}
/// <summary>
/// 獲取當(dāng)前連接數(shù)據(jù)庫(kù)的指定集合【依據(jù)類型】
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IMongoCollection<T> GetCollection<T>() where T : class
{
return this.CurrentDb.GetCollection<T>();
}
}
客戶端調(diào)用:
using (MyMongoDb mm = new MyMongoDb())
{
mm.GetCollection<T>().Insert(o);
}
如果是靜態(tài)變量的話,多線程并發(fā)會(huì)存在問題不?
zihuangning@gmail.com