from:http://www.cnblogs.com/lovecindywang/archive/2011/03/02/1969324.html 
进行了一下Mongodb亿级数据量的性能测试,分别测试如下几个项目:

(所有插入都是单线程进行,所有读取都是多线程进行)

1) 普通插入性能 (插入的数据每条大约在1KB左右)

2) 批量插入性能 (使用的是官方C#客户端的InsertBatch),这个测的是批量插入性能能有多少提高

3) 安全插入功能 (确保插入成功,使用的是SafeMode.True开关),这个测的是安全插入性能会差多少

4) 查询一个索引后的数字列,返回10条记录(也就是10KB)的性能,这个测的是索引查询的性能

5) 查询两个索引后的数字列,返回10条记录(每条记录只返回20字节左右的2个小字段)的性能,这个测的是返回小数据量以及多一个查询条件对性能的影响

6) 查询一个索引后的数字列,按照另一个索引的日期字段排序(索引建立的时候是倒序,排序也是倒序),并且Skip100条记录后返回10条记录的性能,这个测的是Skip和Order对性能的影响

7) 查询100条记录(也就是100KB)的性能(没有排序,没有条件),这个测的是大数据量的查询结果对性能的影响

8) 统计随着测试的进行,总磁盘占用,索引磁盘占用以及数据磁盘占用的数量

并且每一种测试都使用单进程的Mongodb和同一台服务器开三个Mongodb进程作为Sharding(每一个进程大概只能用7GB左右的内存)两种方案

其实对于Sharding,虽然是一台机器放3个进程,但是在查询的时候每一个并行进程查询部分数据,再有运行于另外一个机器的mongos来汇总数据,理论上来说在某些情况下性能会有点提高

基于以上的种种假设,猜测某些情况性能会下降,某些情况性能会提高,那么来看一下最后的测试结果怎么样?

备注:测试的存储服务器是 E5620  @ 2.40GHz,24GB内存,CentOs操作系统,打压机器是E5504 @ 2.0GHz,4GB内存,Windows Server 2003操作系统,两者千兆网卡直连。

image

从这个测试可以看出,对于单进程的方式:

1) Mongodb的非安全插入方式,在一开始插入性能是非常高的,但是在达到了两千万条数据之后性能骤减,这个时候恰巧是服务器24G内存基本占满的时候(随着测试的进行mongodb不断占据内存,一直到操作系统的内存全部占满),也就是说Mongodb的内存映射方式,使得数据全部在内存中的时候速度飞快,当部分数据需要换出到磁盘上之后,性能下降很厉害。(这个性能其实也不算太差,因为我们对三个列的数据做了索引,即使在内存满了之后每秒也能插入2MB的数据,在一开始更是每秒插入25MB数据)。Foursquare其实也是把Mongodb当作带持久化的内存数据库使用的,只是在查不到达到内存瓶颈的时候Sharding没处理好。

2) 对于批量插入功能,其实是一次提交一批数据,但是相比一次一条插入性能并没有提高多少,一来是因为网络带宽已经成为了瓶颈,二来我想写锁也会是一个原因。

3) 对于安全插入功能,相对来说比较稳定,不会波动很大,我想可能是因为安全插入是确保数据直接持久化到磁盘的,而不是插入内存就完事。

4) 对于一列条件的查询,性能一直比较稳定,别小看,每秒能有8000-9000的查询次数,每次返回10KB,相当于每秒查询80MB数据,而且数据库记录是2亿之后还能维持这个水平,性能惊人。

5) 对于二列条件返回小数据的查询,总体上性能会比4)好一点,可能返回的数据量小对性能提高比较大,但是相对来说性能波动也厉害一点,可能多了一个条件就多了一个从磁盘换页的机会。

6) 对于一列数据外加Sort和Skip的查询,在数据量大了之后性能明显就变差了(此时是索引数据量超过内存大小的时候,不知道是否有联系),我猜想是Skip比较消耗性能,不过和4)相比性能也不是差距特别大。

7) 对于返回大数据的查询,一秒瓶颈也有800次左右,也就是80M数据,这就进一步说明了在有索引的情况下,顺序查询和按条件搜索性能是相差无几的,这个时候是IO和网络的瓶颈。

8) 在整个过程中索引占的数据量已经占到了总数据量的相当大比例,在达到1亿4千万数据量的时候,光索引就可以占据整个内存,此时查询性能还是非常高,插入性能也不算太差,mongodb的性能确实很牛。

那么在来看看Sharding模式有什么亮点:

1) 非安全插入和单进程的配置一样,在内存满了之后性能急剧下降。安全插入性能和单进程相比慢不少,但是非常稳定。

2) 对于一个条件和两个条件的查询,性能都比较稳定,但条件查询性能相当于单进程的一半,但是在多条件下有的时候甚至会比单进程高一点。我想这可能是某些时候数据块位于两个Sharding,这样Mongos会并行在两个Sharding查询,然后在把数据进行合并汇总,由于查询返回的数据量小,网络不太可能成为瓶颈了,使得Sharding才有出头的机会。

3) 对于Order和Skip的查询,Sharding方式的差距就出来了,我想主要性能损失可能在Order,因为我们并没有按照排序字段作为Sharding的Key,使用的是_id作为Key,这样排序就比较难进行。

4) 对于返回大数据量的查询,Sharding方式其实和单进程差距不是很大,我想数据的转发可能是一个性能损耗的原因(虽然mongos位于打压机本机,但是数据始终是转手了一次)。

5) 对于磁盘空间的占用,两者其实是差不多的,其中的一些差距可能是因为多个进程都会多分配一点空间,加起来有的时候会比单进程多占用点磁盘(而那些占用比单进程少的地方其实是开始的编码错误,把实际数据大小和磁盘文件占用大小搞错了)。

测试最后的各个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
}

虽然在最后由于时间的关系,没有测到10亿级别的数据量,但是通过这些数据已经可以证明Mongodb的性能是多么强劲了。另外一个原因是,在很多时候可能数据只达到千万我们就会对库进行拆分,不会让一个库的索引非常庞大。在测试的过程中还发现几个问题需要值得注意:

1) 在数据量很大的情况下,对服务进行重启,那么服务启动的初始化阶段,虽然可以接受数据的查询和修改,但是此时性能很差,因为mongodb会不断把数据从磁盘换入内存,此时的IO压力非常大。

2) 在数据量很大的情况下,如果服务没有正常关闭,那么Mongodb启动修复数据库的时间非常可观,在1.8中退出的-dur貌似可以解决这个问题,据官方说对读取没影响,写入速度会稍稍降低,有空我也会再进行下测试。

3) 在使用Sharding的时候,Mongodb时不时会对数据拆分搬迁,这个时候性能下降很厉害,虽然从测试图中看不出(因为我每一次测试都会测试比较多的迭代次数),但是我在实际观察中可以发现,在搬迁数据的时候每秒插入性能可能会低到几百条。其实我觉得能手动切分数据库就手动切分或者手动做历史库,不要依赖这种自动化的Sharding,因为一开始数据就放到正确的位置比分隔再搬迁效率不知道高多少。个人认为Mongodb单数据库存储不超过1亿的数据比较合适,再大还是手动分库吧。

4) 对于数据的插入,如果使用多线程并不会带来性能的提高,反而还会下降一点性能(并且可以在http接口上看到,有大量的线程处于等待)。

5) 在整个测试过程中,批量插入的时候遇到过几次连接被远程计算机关闭的错误,怀疑是有的时候Mongodb不稳定关闭了连接,或是官方的C#客户端有BUG,但是也仅仅是在数据量特别大的时候遇到几次。

最新补充:在之后我又进行了几天测试,把测试数据量进一步加大到5亿,总磁盘占用超过500G,发现和2亿数据量相比,所有性能都差不多,只是测试6和测试7在超过2亿级别数据之后,每400万记录作为一个循环,上下波动30%的性能,非常有规律。

作者:lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: 开源
10
0
(请您对文章做出评价)
« 博主前一篇:浅谈网络客户端的类库编写和使用
» 博主后一篇:Redis千万级的数据量的性能测试
posted @ 2011-03-02 22:09 lovecindywang 阅读(15352) 评论(44编辑 收藏
评论列表
  
#1楼 2011-03-02 23:14 blackcat  
存储性能如何?
  
#2楼 2011-03-03 08:21 曾哲  
  
#3楼 2011-03-03 08:38 Kain  
非常好,正需要这方面的资料。
  
#4楼 2011-03-03 09:29 RealDigit  
冲着性能测试来的
  
#5楼 2011-03-03 09:43 new 维生素C.net()  
想问一下,Insert操作的数据是在调用客户端里得出的还是在MongoDB上得出的?
  
#6楼 2011-03-03 09:52 姜敏  
楼主:您文章中的图是用什么工具生成的?
  
#7楼[楼主2011-03-03 09:53 lovecindywang  
数据是客户端生成的,图是asp.net chart控件生成的
  
#8楼 2011-03-03 10:18 skyaspnet  
建议用表格来对结果进行总结,文字总结效果不太好
  
#9楼 2011-03-03 11:15 SeanC  
为什么Total Size(index+data)的值不是data size和index size之和?
每天数据1kb是纯文本的大小吗?包括json tag吗?数据和索引各有几列?
  
#10楼[楼主2011-03-03 11:31 lovecindywang  
@SeanC
1kb是包括tag后大概的大小,数据4列,其中3列做索引(2个数值和1个日期),另外一列存950字节。至于为什么Total Size(index+data)的值不是data size和index size之和?
我也清楚,这里的值取的是db.stats()中的storagesize/datasize和indexsize
  
#11楼 2011-03-03 19:55 forchenyun  
请教,查询有热点吗?还是2亿多内随机?或者说热点范围是怎么样的?
  
#12楼[楼主2011-03-03 19:59 lovecindywang  
@forchenyun
总是搜索总数据范围的前10%,在插入数据的时候数字字段随机选取525600内的一个值。
  
#13楼 2011-03-08 19:04 shawny  
4) 对于一列条件的查询,性能一直比较稳定,别小看,每秒能有8000-9000的查询次数,每次返回10KB,相当于每秒查询80MB数据,而且数据库记录是2亿之后还能维持这个水平,性能惊人。

我觉得你这里说的“每秒能有8000-9000的查询次数”,查询到的是索引数据,还没有真正去读数据回来,例如:如果你用java client api写的测试程序,如果调用find(query)去做测试,其实这里面返回的是DBCursor,而不是Document,需要继续调用next()才返回数据;如果调用findOne(query)去做的测试,它返回的则是Document。

所以我觉得你说每秒查询80MB的数据有问题,因为每次没有返回10KB回来。
  
#14楼[楼主2011-03-08 21:29 lovecindywang  
@shawny
不存在这个问题,我是ToList()的
  
#15楼 2011-03-08 22:25 shawny  
@lovecindywang
谢谢你的回复~
因为我这边是用java写的benchmark测试程序,当我使用find(query)会有很高的qps,因为这个仅仅返回的是DBCursor信息,而不是真正的数据,不是我想要的。当我用findOne(query)的时候qps就惨目忍睹了。

所以还是要问几个问题:

1) 你测试的读操作是顺序读还是随机读?
2)关于测试5“查询两个索引后的数字列,返回10条记录(每条记录只返回20字节左右的2个小字段)的性能,这个测的是返回小数据量以及多一个查询条件对性能的影响”,按照对应图形来看,每秒钟大概读的数据是10000qps * 20byte * 10条记录 = 2M/s,这个速度和测试4比就相差很多了啊!
3)按照测试4给出的结果,在数据量远远大于内存大小的时候,读性能还是胜过写性能。
4)测试读的时候,client端开了多少个线程?
  
#16楼[楼主2011-03-09 09:23 lovecindywang  
1) 顺序读
2) 一条记录是1K而不是20字节哦
3)使用并行库,所以我估计是8个线程,对应八核
  
#17楼 2011-03-09 09:43 shawny  
@lovecindywang
下面是正文中的引用:
“5) 查询两个索引后的数字列,返回10条记录(每条记录只返回20字节左右的2个小字段)的性能,这个测的是返回小数据量以及多一个查询条件对性能的影响”

另外我看到的关于mongodb的测试"MongoDB Performance for more data than memory", 他的测试结果在“https://spreadsheets.google.com/ccc?key=0At_QEIJCB2GbdEI3d3VBR2hFY1AyVTVNY0N6OGh5dXc&hl=en#”可以看看第六行,从1000w条记录中读取30000条大概花了74.09s的时间,那么qps就是30000/74.09 = 404

有什么问题,欢迎纠正。
  
#18楼[楼主2011-03-09 09:57 lovecindywang  
我的测试网络延迟以及带宽都不考虑的,是千兆直连
性能上可能是会有差距
  
#19楼 2011-03-09 10:12 shawny  
@lovecindywang
其实我的测试环境也是千兆直连的,读性能不理想。
  
#20楼[楼主2011-03-09 10:17 lovecindywang  
@shawny
不知服务器配置是怎么样的? 可能配置不是太高吧
  
#21楼 2011-03-09 10:18 shawny  
@lovecindywang
另外不知道你看没看读操作时候的iostat信息,rMB/s
  
#22楼 2011-03-09 10:44 shawny  
@lovecindywang
服务器的内存肯定没你这么大(我的是8G),我测试数据量是20G(2000w条记录),但是我主要是想看一下当数据量远远大于内存的时候,性能如何。

虽然你的内存是24G,但是你的记录也达到了2亿条,所以数据量也已经远远大于内存了。
  
#23楼[楼主2011-03-09 10:54 lovecindywang  
测试机HP DL180 G6,RAID 10两组,6块 600G SAS一组,可能IO性能好很多的关系
  
#24楼 2011-03-09 11:11 shawny  
@lovecindywang
你服务器用的是raid啊,我擦!!! 我的是SCSI磁盘!
当Mongodb处理读操作的时候,用iostat查看util%一直处于100%,说明mongodb一直在忙活IO操作呢,rMB/s很低!
所以我这里性能低主要是因为磁盘IO太烂!
  
#25楼[楼主2011-03-09 11:16 lovecindywang  
我测试的时候util% 没有超过30%过 呵呵
  
#26楼 2011-03-09 11:19 shawny  
@lovecindywang
差距啊!谢谢你的回复!
  
#27楼 2011-03-21 10:41 一线风  
这么牛吗?回头找个项目用一下看看。
  
#28楼 2011-04-14 09:36 Weiseditor  
奇怪,怎么Mongodb的插入跟更新,都不用判断成功或失败!!!
  
#29楼[楼主2011-04-14 09:59 lovecindywang  
@Weiseditor
如果追求高可靠性,性能肯定会有所下降,你说的功能可以通过SafeMode的Insert或Update实现,确保所有修改应用到多个Node
  
#30楼 2011-04-16 10:20 Weiseditor  
@lovecindywang
我本来是用SafeMode 反回的 我用的是C#

他有个SafeModelResult r = Coll.Insert(xxx);

if (r.Ok == true/false)

但是提示 未将对象引用设置到对象的实例...

数据也没有插入进去,但是直接写成 Coll.Insert(xxx); 却插入成功!

很奇怪.
  
#31楼 2011-08-11 13:55 木乃伊  
学习了,图真不错。
  
#32楼 2011-08-30 10:51 itmuse  
在生产环境中,mongodb安装在red hat 64位的linux中,client端是windows 2003的官方的c#的driver,mongodb的数据达到了40g以上,链接经常会被mongod的服务端自动关闭,查看日志是:SocketException in connThread, closing client connection。另外,写入的并发大概10/second,查询的并发大概25/second,请问有没有遇到这样的情况,这问题纠结了很久,一直找不到原因。
  
#33楼 2011-09-05 19:04 酷&酷  
mongod 集群能不能用户认证 ?
  
#34楼 2011-09-23 09:07 zsea  
如何数据不多可以用这个,我试过插入10亿条记录,最后系统崩溃了。
  
#35楼 2011-10-24 19:28 小虾米  
很好的测试哦,对mongodb充满信心
  
#36楼 2011-11-16 17:19 寒风吹过  
为什么我在做MONGODB的压力测试时(页面直连MONGDB),测试结果的写入性能很底啊。只有每秒10个左右的写入。为什么差距这大?有可能是哪方面的原因引起的,求指教!
  
#37楼[楼主2011-11-19 17:56 lovecindywang  
@寒风吹过
什么驱动? 怀疑是不是每一次都使用了新的连接池?
  
#38楼 2011-11-21 10:15 寒风吹过  
是的,每次都使用了新的连接池。一个页面访问产生一个连接池

那有什么办法提高效率呢?连接池做全局静态变量?
  
#39楼[楼主2011-11-21 10:25 lovecindywang  
@寒风吹过
我不知道你事什么驱动,应该驱动把连接池对开发透明,在内部会维护一个连接字符串具有唯一连接池的!如果你不确定的话,把连接池或类似server的入口对象设置为静态
  
#40楼 2011-11-21 11:08 寒风吹过  
//封装类
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>
/// 获取当前连接数据库的指定集合【依据类型】
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IMongoCollection<T> GetCollection<T>() where T : class
{
return this.CurrentDb.GetCollection<T>();
}
}

客户端调用:
using (MyMongoDb mm = new MyMongoDb())
{
mm.GetCollection<T>().Insert(o);
}
  
#41楼 2011-11-21 11:10 寒风吹过  
需要把这种方式改成连接字符串为静态变量的方式吗?
如果是静态变量的话,多线程并发会存在问题不?
  
#42楼 2011-12-06 20:22 和歌  
请问可以将源码给一份吗,谢了!
zihuangning@gmail.com
  
#43楼 2012-02-02 18:17 javacty  
非常感谢您的博文,源代码能提供个附件下载或者发我邮箱(javac112358@163.com)吗?
  
#44楼 2012-02-20 12:07 研究员  
MongoDB的优势在于内存与分布式处理,没有个十几台机器,测不出它的优势的。