«MySQL 主主配置 Mac 中开启 VIM 语法高亮 »
2012-05-08
进行了一下 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 操作系统, 两者千兆网卡直连.
从这个测试可以看出, 对于单进程的方式:
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% 的性能, 非常有规律.
来源: http://www.taocms.org/435.html