Hello World
Spiga

MongoDB与Tokyo Tyrant性能比较(2):并发写入操作

2010-02-26 19:38 by 老赵, 14512 visits

上一次的测试中我们比较了MongoDB与Tokyo Tyrant的Table Database两种存储方式的性能。不过由于条件限制,我只能在自己的MBP上测试,而这至少会带来两个问题。首先,真实环境下客户端和服务器是通过内网连接的,它的性能比本地回环要慢不少,一些和网络传输性能有关的问题可能会体现不出。其次,由于无法进行并发测试(并发测试的客户端资源占用较高,放在同一台机器上准确性较差),这又和生产环境有很大区别了。因此,我前两天向同事借了台性能测试用的机器,希望可以得到更可靠的结果。

测试环境与数据

这次我使用了新的环境进行性能测试:

  • OS:CentOS release 5.3 (Final)
  • RAM:4GB
  • CPU:Intel(R) Xeon(R) CPU E5405 @ 2.00GHz (64 bit, 4 cores * 2)
  • 其他:SCSI硬盘,ext3文件系统

客户端与服务器端配置相同,两台机器使用百兆网相连,测试时服务器保持空闲。

测试数据的结构与之前相同(包括索引),数据分布同样保持一致,每个线程插入的数量较少,但每次测试的总数均不低于上次(110万条)。

为了测试并发插入的性能,我稍微改写了测试脚本。首先,我为它增加init参数,它的作用是初始化存储结构(清除数据,建立索引),拿MongoDB的测试脚本为例:

# ruby mdb-insert.rb init
index for CreateTime created.
index for CategoryID created.
index for UserID created.
index for Tags created.
initialize completed.

此外,脚本还支持“分类范围”的指定,例如:

# ruby mdb-insert.rb 101 200

这表示我们将插入CategoryID为101至200的新闻,由于每个分类中均匀分布10至100条记录,因此只要范围的上下限保持10的倍数,这样平均每个分类有55条新闻,于是新闻ID也可以此计算出来。例如分类101至200的新闻,其ID便为5501至11000。您可以通过阅读代码了解这些细节。

为了并行测试,每次我就将同时执行多个脚本进行插入,每个脚本提供不同的参数。我使用类似这样的shell脚本来启动并行任务:

#!/bin/bash

# mdb-insert.sh

echo "=== initialize ==="
ruby mdb-insert.rb init

for((i=1;i<=5;i++))
    do

    let begin=$((4000 * ($i - 1) + 1))
    let end=$((4000 * $i))

    echo === start task $i ===
    ruby mdb-insert.rb $begin $end > logs/mdb-insert-$begin-$end.log &
done

这段脚本将启动5个任务,每个任务将插入4000个分类,即22万条记录。5个任务总共插入110万条记录,与前次持平。接下来的测试中,我在增加任务数量的同时也会适当降低每个任务插入的记录数目。最多我将启用100个任务,每个任务插入1000个分类,即所有任务共计插入550万条记录,是之前的5倍。

在所有的测试中,无论多少个任务都是同时启动,且几乎同时结束(与总耗时相比很小)。因此,在接下来的数据中我不会列出每个任务的开始及结束时间,如果您关心这部分数据,可以在文章结尾处可以获得本次测试生成的所有记录文件。

Tokyo Tyrant性能测试

第1次测试启动5个任务,每个任务4000个分类,共计插入110万条记录。结果如下(第1行粗体表示“万条记录”,每个数字的单位为“秒”,下同):

与之前的现象类似,当数据库中的数据越多时,插入速度也会随之减慢:在每个任务插入前1万条记录时,耗时大约为5、6秒。但是到了中后期,每插入1万条数据则需要等待15至20,甚至30秒的时间。值得注意的是,虽然使用了较好的服务器,但是并行插入110万条记录的时间却比上次要来的多(这次耗时340秒,而之前是)。原因可能是客户端与服务器端的分离导致网络速度的下降。另一种可能我猜测是,根据Tokyo Cabinet的文档中提到文件读写锁的使用,因此每次只能插入一条记录,且插入(或更新)时无法读取数据,因而在并发环境下Tokyo Tyrant(它其实是Tokyo Cabinet上层的TCP服务器)的表现并不会有所提高。不过据读过Tokyo Cabinet代码的同事说,Tokyo Cabint在使用Table Database的时候锁粒度不会那么大,因此关于这点还需要寻找进一步的资料。

第2次测试启动10个任务,每个任务还是4000个分类,因此共计插入220万条记录。结果如下:

第2次测试的总数据量为第1次的2倍,而耗时却是第1次的3.5倍,这应该还是由于数据量增大而导致的插入性能降低。但是,我们目前还不能排除这和并发连接数有关的可能——虽然我们有理由相信这个性能问题只和数据量相关(稍后再说)。为了查明并发程度和性能是否有关系,我们再进行第3次测试。

第3次测试启动20个任务,每个任务2000个分类,因此共计仍旧是220万条记录。结果如下:

这个结果实在是非常能够说明问题:第3次的耗时与上一次几乎完全相同,这意味着加大并发量并不会影响TT的性能。这是因为TT在服务器端维护了一个线程池(现在的测试,也是默认情况下为8个线程),因此请求再多,同时进行的任务也是有限的,而累积的任务会排在队列中。这也是使用队列和固定数量工作线程的好处:即使压力再大,服务器端的吞吐量也能够一直保持较高水准,而客户端请求的响应时间随着请求数量增大而线性增长。如果没有队列,随着压力增大,服务器端吞吐量会剧烈下降(一般至少也是线性的),而客户端请求的响应时间增长更快,直至超时。

为此,我们也没有必要继续测试更多的并发数量了,因为即便是再多并发,TT的吞吐量(即插入记录的数目)也只是和数据库中记录数量相关。根据测试,我们可以总结出:

  • 插入110万条记录的平均吞吐量为:大约3225条/秒
  • 插入220万条记录的平均吞吐量为:大约1833条/秒
  • 当数据库包含100万条数据时吞吐量为:1700~1800条/秒
  • 当数据库包含200万条数据时吞吐量为:150~160条/秒

似乎随着数据量的增加,TT的吞吐量下降得也非常明显。这其中可能有索引的因素在里面,因此如果您想要得到适合自己的结果,最好可以亲自进行试验。

MongoDB性能测试

与TT相同,第1次测试启动5个任务,每个任务4000个分类,共计插入110万条记录。结果如下:

在5个并发任务的情况下,MongoDB的表现较TT要出彩不少。首先,随着数据库中已有记录的增加,插入速度并没有降低的迹象,可以说十分平稳。此外,在同样的客户端和服务器环境下,MongoDB插入110万条数据的耗时比TT的一半还要略少一些。而且,虽然网速带来的一定负面影响,但可能是由于服务器配置的提高,再加上并发写入的性能优势,此次测试比上次单机环境下的表现也要好上许多。

那么在数据量和并发继续增大的情况下MongoDB表现如何呢?于是第2次测试启动10个任务,每个任务还是4000个分类,共计插入220万条记录。结果如下:

第2次的数据量为第1次的2倍,而耗时则大约为2.08倍,两者基本保持一致,这说明了MongoDB在数据量和并发量增加的情况下,吞吐量几乎没有改变。

第3次测试依旧与TT相同,启用20个任务,每个任务2000个分类,共计插入220万条记录。结果如下:

在总数据量不变的情况下,我们又将并发量提高了一倍,但是MongoDB依旧表现出的稳定的吞吐量,总耗时与第2次测试几乎完全相同。

这样看来,似乎在MongoDB在内部也有个类似于TT的队列机制以保证一定的吞吐量。为了验证这个看法,我再次加大了数据量和并发量。在第4次测试中我启用了50个任务,每个任务还是2000个分类,这样插入的记录数据则达到了550万。结果如下:

在50个并发任务下,MongoDB终于略显疲态。第4次测试的数据量为第3次的2.5倍,但耗时却接近3.5倍。为了验证这是由于数据量增加,还是并发量提高引起的问题,我又进行了第最后一次测试。

第5个测试中启用了100个任务,但每个任务只写入1000个分类,则总条目数还是和第4次相同,为550万。结果如下:

请注意,由于每个任务插入1000个分类,即5.5万条记录,因此上表中最后一栏为5.5而不是6。随着并发量的增大,MongoDB的耗时继续增加了。由于这个表现和之前的预测不同,我又把第4次和第5次测试各执行了一遍,结果并没有太大区别,基本可以排除“环境异常”这样的可能。

经过5次测试的结果,我们可以得出MongoDB的性能指标:

  • 5个并发,插入110万条记录的平均吞吐量:大约6600条/秒
  • 10个并发,插入220万条记录的平均吞吐量:大约6300条/秒
  • 20个并发,插入220万条记录的平均吞吐量:大约6300条/秒
  • 50个并发,插入550万条记录的平均吞吐量:大约4500条/秒
  • 100个并发,插入550万条记录的平均吞吐量:大约4100条/秒

总体来说,虽然在数据量和并发量增大的情况下MongoDB的吞吐量有所下降,但是MongoDB的表现比TT还是要抢眼不少,而且在绝对数值上还是相当出彩的。

其他信息

其他还有一些信息可能值得参考。

在进行测试时,我发现TT进程的CPU占有率比较低,除了少量时候出现80~150%以外(top命令的结果,8核服务器),绝大部分时间CPU占有率只有20~40%。MongoDB的CPU占有率比TT要高出不少,基本上保持在80~110%之间(数据量和并发量在这里影响不大)。但是总体来说CPU的占有率都很低,毕竟现在的测试属于IO密集型操作。

不过在内存占有率方面,两者的区别很大。在测试过程中(服务器内存4G),TT进程的内存占有率一直保持在20%一下,而MongoDB会缓步增加,最后保持在80%上下。

此外,在插入了550万条数据之后,MongoDB的数据文件增长到了6G。这个数字比上次测试的2G/110万稍有缓解,但还是比TT要大上许多。

最后再来谈谈为什么仅仅测试了“并发插入”而没有“更新”和“删除”:其实这也是通过使用场景而设计出来的测试用例。因为对于此类存储来说,大量并发插入的场景比较多,例如日志记录,用户行为记录,或是SNS应用中常见的News Feed等等。这些场景的特点就是需要数据的不断增长,而大量密集的更新则很少会出现──删除就更不用说了。至于并行的更新,我接下来也会进行测试的,只是可能会模拟一些实际的场景,例如在更新的同时进行不断地读取和插入吧。

所有测试代码,及完整测试结果:http://github.com/JeffreyZhao/mdb-tt-benchmark

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

22 条回复

  1. 麒麟
    *.*.*.*
    链接

    麒麟 2010-02-26 19:56:00

    22

  2. Jeffz Fans[未注册用户]
    *.*.*.*
    链接

    Jeffz Fans[未注册用户] 2010-02-26 20:08:00

    怎么个测试的?

  3. shihao
    *.*.*.*
    链接

    shihao 2010-02-26 20:45:00

    说实话,基本不懂,但还是要顶一下

  4. yishh[未注册用户]
    *.*.*.*
    链接

    yishh[未注册用户] 2010-02-26 21:57:00

    按照mongodb官方的说法,mongodb完全由系统内核进行内存管理,会尽可能的占用系统空闲内存,用free可以看到,大部分内存都是作为io cache被占用的,而这部分内存是可以释放出来给应用使用的。根据我们现在生产环境的运行情况来看,也确实是这样,8G的内存被完全用掉,但是始终还没有使用swap空间,所以mongodb的内存问题应该不用担心。

  5. 老赵
    admin
    链接

    老赵 2010-02-26 22:27:00

    @yishh
    我可不是担心,呵呵。
    在我看来只要代码没写错,内存是用得越多越好,这对性能很关键啊。

  6. 老赵
    admin
    链接

    老赵 2010-02-26 22:29:00

    @shihao
    其实没啥不好懂的,最简单的写代码再分析日志。

  7. robbin[未注册用户]
    *.*.*.*
    链接

    robbin[未注册用户] 2010-02-27 01:17:00

    Mongo多用内存是好事情。

    在你的测试当中,我觉得少了一项参考数据:测试服务器的硬盘IO状况。可以用iostat和vmstat观测一下。看看Mongo的性能下降曲线是否和硬盘IO的吞吐量,以及cpu的io wait有密切的关联关系。我个人猜测可能无论多少并发,硬盘IO都不会有太大波动,可能比较平稳。Jeff有兴趣的话,可以再补下这个数据。

    我个人猜测50个并发的时候Mongo性能突然下降很可能和内存已经占用的差不多了有关系。假设物理内存再double,可能你50和100个并发的时候,性能还不会下降。

    因此可以设计另外一个测试,disable掉swap,然后限定进程使用的内存上限,例如1G或者2G,然后再测试,看看是否还没有倒50个并发性能就会下降。

    这样全面测试下来,就会对Mongo的并发性能有很清楚的了解了。我想确定的一点就是Mongo的并发插入性能是否和硬盘IO关系不大,而和内存占用有直接的正相关性。

    Mongo自己的文档说并发写的性能有1.5万每秒,Jeff的测试结果离这个还有一定差距,当然这个和测试环境,测试数据都有很大关系,但对比同样测试环境和数据下TT的性能,已经可以说明问题了:mongo的并发插入性能是很高的,至少对我的应用场景来说,已经完全OK了。

    其实对于不需要关联查询,不需要子查询,只做单表操作的应用场景来说,Mongo已经可以取代MySQL了。

  8. Davies[未注册用户]
    *.*.*.*
    链接

    Davies[未注册用户] 2010-02-27 11:12:00

    TT 的性能与数据量和系统内容紧密相关,如果是Hash数据库,也与数据文件所使用的bucket大小很有关系,数据量是220万,将 bucket 设定成400万应该能改善性能。

    另外,TC的写是锁整个数据文件的,因为使用多个数据文件也能够改善并发写的性能。beansdb 就是使用多个数据文件。

    TT 的只是异步写,用异步写也能够提升批量插入的性能。

    建议对TT的性能进行适当调优,否则这个数字实在不能接受。。。

  9. 老赵
    admin
    链接

    老赵 2010-02-27 12:56:00

    @Davies
    从测试上来看,TT随着数据量增加性能急剧下降的有些变态,而且事实上220万数据是又算的了什么呢?
    不过我还不清楚这是不是Table Database的特定问题,因为我这样去用TT可能不是很正确。
    社区里用TT大都就是典型的key/value,这样用Hash Database这种性能会很高。我弄成了Table Database,设了N个Column加上索引,可能走了TT的弱项。
    当然,我现在也是在探索把TT和MongoDB用作主要存储方式的能力,我现在还是抱着这样的观点,就是说TT不适合做主要存储,它适合做一种辅助的优化的手段。

  10. 老赵
    admin
    链接

    老赵 2010-02-27 12:58:00

    @Davies
    还有接下来的测试我看来不能用默认参数裸奔了,可能到时候我会写email来问你一些问题。

  11. 老赵
    admin
    链接

    老赵 2010-02-27 13:01:00

    @robbin:
    有机会我在按你说的的试试看。

    其实对于不需要关联查询,不需要子查询,只做单表操作的应用场景来说,Mongo已经可以取代MySQL了。


    而且其实,就单表能力来说,MongoDB提供的查询能力比MySQL要高一些,一些场景(比如一些系统中根据Tag找记录)可以直接利用MongoDB了,而不像MySQL那样分表再JOIN等等。

  12. 蛙蛙王子
    *.*.*.*
    链接

    蛙蛙王子 2010-02-28 23:08:00

    老赵能用中文给介绍一下NOSQL的原理吗,
    http://www.slideshare.net/guestdfd1ec/design-patterns-for-distributed-nonrelational-databases
    http://horicky.blogspot.com/2009/11/nosql-patterns.html
    好多东西应该不能拿来就用,还是知道原理一些比较好一些,用也可以更灵活的用,有所取舍的用。

  13. 老赵
    admin
    链接

    老赵 2010-03-01 11:58:00

    @蛙蛙王子
    没啥原理,每个存储的机制也都不一样的。
    其实说白了就是使用比关系型索引更简单的存储结构来提高性能。

  14. 魔尊年少时
    *.*.*.*
    链接

    魔尊年少时 2010-03-08 18:03:00

    刚发现老赵的文章,我也详细测试过tt的性能,测试结果与您的几乎一样。
    但是经过阅读tt源代码发现,tt是同时写入硬盘中的。而MongoDB则不详(没仔细研究过)。所以考虑到数据可靠性,还是使用了tt。

  15. hanwoody@gmail.com[未注册用户…
    *.*.*.*
    链接

    hanwoody@gmail.com[未注册用户] 2010-03-23 22:58:00

    mongodb的durable问题,可以参考blog.mongodb.org上的讨论。mongodb的写不是及时写入磁盘的,所以写入性能会比tt高。mongodb的blog上也有说明解决durable问题的方法。

  16. colin
    218.5.2.*
    链接

    colin 2010-04-19 20:39:13

    文章写的不错,很详尽,不过有些问题值得商榷。

    对TTserver的配置还是有部分问题,例如bnum可以设大一些,xmsiz(指定mmap使用内存的大小)也可以指定大一些,不过原作者的文档写的比较含糊,挺难理解意义...先看看代码可能靠谱些。

    对TTserver的测试可能还得针对一下使用的场景,例如在内存比较充裕时,如果对热点数据的存取都是走的mmap系统调用,这样的性能也是比较可观的,这点以前对hash database做过测试。

    不过没用过TTserver的B+ Tree Database,最近准备看看那部分代码,做个测试,再和老赵交流

  17. 打酱油的
    114.255.40.*
    链接

    打酱油的 2010-04-28 14:24:48

    将TT的bnum和xmsiz设大以后,其性能还是很强大的,在我们的机器上测得的结果要比mongo好啊

  18. chenlb
    119.145.139.*
    链接

    chenlb 2010-05-13 14:52:54

    我比较关注查询的性能.

    在百W级别,查询性能还是可以的, 到了KW级别, 并发查询性能不让人满意.

    ioWait 50% 左右.

    不知你有没对并发查询做过测试对比?

  19. iammutex
    202.108.14.*
    链接

    iammutex 2010-06-25 16:46:44

    不知道赵姐夫对Mongodb的数据定时刷入磁盘功能怎么看。

    一是在写量大时(单条记录大,数据量大)造成的同步到磁盘会很慢。而且这个后台的同步会影响操作的性能。

    二是写时刷入磁盘的机制下的会存在断电时丢数据问题。(默认的60s 一同步丢失还是比较严重,设置太小对性能影响又大。)

  20. lin
    206.15.64.*
    链接

    lin 2010-07-09 09:52:55

    在做一个Mongodb的测试的时候,发现一个读操作会让所有的写操作停止,想请教一下这是Mongodb的瓶颈还是设定的问题。

    一个客户端不停地插入数据, 另外一个客户端每隔1分钟做一个比较慢的检索db.tracking.count({url:'http://foo.com/'})

    当数据量达到100万条记录后,明显地看到插入操作在等待读操作结束。

    db.version() 1.5.4

    db.currentOp()

        {
            "opid" : 8684094,
            "active" : true,
            "lockType" : "read",
            "waitingForLock" : false,
            "secs_running" : 1,
            "op" : "query",
            "ns" : "cdc",
            "query" : {
                "count" : "tracking",
                "query" : {
                    "url" : "http://foo.com/"
                },
                "fields" : {
    
                }
            },
            "client" : "127.0.0.1:36016",
            "desc" : "conn"
        },
        {
            "opid" : 8684095,
            "active" : false,
            "lockType" : "write",
            **"waitingForLock" : true,**
            "op" : "insert",
            "ns" : "?dc.tracking",
            "client" : "102.18.3.112:37716",
            "desc" : "conn"
        }
    
  21. southsiberia
    110.153.141.*
    链接

    southsiberia 2010-09-25 03:21:47

    可以这么理解吗?MongoDB将数据大量存入内存中以便加快加载,当数据量超过内存承受范围时,他将数据存入硬盘中,如果将服务器机械硬盘改为固态硬盘,性能是否有较大的提高?

  22. KylinHuang
    114.80.164.*
    链接

    KylinHuang 2010-10-18 15:33:33

    看不到图啊老赵

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我