MongoDB性能测试
Nodejs编写的应用,后端连MongoDB进行持久化存储。Mongodb分别有两个版本,分别跑在虚拟机和kubernetes里面,进行读写性能测试
测试基线
分别在虚拟机和K8S中安装Mongodb数据库,版本为4.0.18,测试目标为考察mongodb跑在虚拟机和K8S中的性能差异
- 虚拟机配置:2C4G
- K8S中的pod配额(limits)为2C4G,mongodb后端挂接ceph rbd存储
压力测试使用的软件为wrk,地址为:https://github.com/wg/wrk。这里推荐一下wrk,可以以极少的线程模拟出高并发的场景,比之前用的ab好用了不是一点。
Node性能测试
首先我们来看下node的基础性能测试,众所周知nodejs是异步非阻塞的编程模型,在处理高并发场景有天然优势,这边我们测试一下,在不接后端数据库的情况下,纯nodejs处理(比如请求校验错误),性能可以到多少。node应用也是跑在k8里的,设置的配额(limits)为6C,并发数设置为200:
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s -s demo.lua http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 61.84ms 26.29ms 479.18ms 94.73%
Req/Sec 1.65k 209.52 2.03k 85.00%
32905 requests in 10.01s, 8.38MB read
Non-2xx or 3xx responses: 32905
Requests/sec: 3287.93
Transfer/sec: 857.30KB
可以看到QPS在3200多,而且这个数值是线性增长的,即如果起两个实例,则QPS可以到6000多,3个实例的话可以到9000多。记住这个数据,这是后面进行比较的基础(单个node的最高性能,也就是加上数据库以后理论上也不会超过这个QPS)
虚拟机Mongo实例测试
node后端接入跑在虚拟机上的mongodb实例(mongo本身均为单实例,未做集群),我们来看一下:
写入测试
单node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s -s demo.lua http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 810.15ms 1.62s 9.80s 88.46%
Req/Sec 317.73 113.81 727.00 71.36%
6323 requests in 10.02s, 3.41MB read
Requests/sec: 631.35
Transfer/sec: 348.95KB
cpu使用率为20%
2个node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s -s demo.lua -d 30s http://22.196.66.200:31010/users
Running 30s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 176.34ms 266.36ms 3.79s 97.12%
Req/Sec 710.32 168.50 1.01k 63.83%
42445 requests in 30.03s, 22.91MB read
Requests/sec: 1413.36
Transfer/sec: 781.11KB
cpu使用率为40%
5个node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s -s demo.lua -d 30s http://22.196.66.200:31010/users
Running 30s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 60.30ms 35.27ms 1.06s 96.79%
Req/Sec 1.71k 236.41 2.18k 76.83%
102166 requests in 30.02s, 55.12MB read
Requests/sec: 3403.58
Transfer/sec: 1.84MB
cpu使用率为60%
写入测试总结
总体来说随着副本数增加,整体性能呈线性增加,5副本时mongo数据库的cpu也只有60%,应该说还有余量。应该是mongodb默认的连接池的限制起了作用(默认连接数为5个)
读取测试
数据库中放置测试数据780万条,根据ObjectId关键字进行查询:
单node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s http://22.196.66.200:31010/users/5ecb83b3c5299b002bf8bd09
Running 10s test @ http://22.196.66.200:31010/users/5ecb83b3c5299b002bf8bd09
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 726.33ms 1.46s 8.79s 88.23%
Req/Sec 524.42 158.59 1.00k 72.00%
10448 requests in 10.01s, 5.59MB read
Requests/sec: 1043.39
Transfer/sec: 572.14KB
cpu利用率16%
5个node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s -d 30s http://22.196.66.200:31010/users/5ecb83b3c5299b002bf8bd09
Running 30s test @ http://22.196.66.200:31010/users/5ecb83b3c5299b002bf8bd09
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 33.17ms 18.24ms 584.77ms 87.45%
Req/Sec 3.10k 303.76 3.64k 85.17%
184886 requests in 30.01s, 98.91MB read
Requests/sec: 6159.80
Transfer/sec: 3.30MB
cpu利用率63%
读取测试总结
读取方面,因为有缓存的关系(命中率较高),整体的QPS比写入还略高,基本也是随副本数呈线性增长的态势。
kubernetes mongo 实例测试
接下来我们测试一下,mongodb跑在k8s中,后端挂ceph块存储的性能(k8的work与ceph集群连接为千兆网卡)
写入测试
单node副本
[root@dce304-cal-vm3 ~]# wrk -c 500 -s demo.lua -T 30s http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 500 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 470.51ms 1.13s 9.77s 93.67%
Req/Sec 398.62 150.61 0.91k 71.81%
7654 requests in 10.02s, 4.13MB read
Requests/sec: 763.54
Transfer/sec: 422.03KB
cpu使用率 13.7%
2个node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -s demo.lua -T 30s http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 281.70ms 628.13ms 5.50s 93.31%
Req/Sec 738.18 142.56 1.14k 79.50%
14710 requests in 10.01s, 7.94MB read
Requests/sec: 1468.87
Transfer/sec: 811.73KB
cpu使用率25%
5个node 副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -s demo.lua -T 30s http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 61.27ms 29.67ms 453.58ms 83.79%
Req/Sec 1.66k 233.53 2.09k 93.00%
33091 requests in 10.02s, 17.85MB read
Requests/sec: 3303.79
Transfer/sec: 1.78MB
cpu使用率57%
写入测试总结
可以看到使用网络存储和本地磁盘,在写入性能上基本没有区别,在单副本情况下,使用rbd的性能甚至还好于本地磁盘。
读取测试
单node 副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s http://22.196.66.200:31010/users/5ecba669f41c9b002b91cfbc
Running 10s test @ http://22.196.66.200:31010/users/5ecba669f41c9b002b91cfbc
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 564.36ms 1.19s 7.58s 89.28%
Req/Sec 610.83 224.26 1.01k 58.38%
12119 requests in 10.02s, 6.49MB read
Requests/sec: 1210.00
Transfer/sec: 663.37KB
cpu使用率20%
5个node副本
[root@dce304-cal-vm3 ~]# wrk -c 200 -T 30s http://22.196.66.200:31010/users/5ecba669f41c9b002b91cfbc
Running 10s test @ http://22.196.66.200:31010/users/5ecba669f41c9b002b91cfbc
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 36.44ms 26.26ms 542.40ms 96.53%
Req/Sec 2.91k 498.88 3.58k 87.00%
58009 requests in 10.01s, 31.03MB read
Requests/sec: 5792.89
Transfer/sec: 3.10MB
cpu使用率91%
测试总结
可以看到,不管是跑在虚拟机中,还是k8s里,测试的结果是基本接近的。而按照我们的一般认知,本地磁盘的读写性能肯定还是要高于网络存储的,这里面的关键,还是要说到mongodb的读写机制。mongo作为一款典型的nosql数据库,其绝大部分的操作都是在内存中完成的。MongoDB把文档写进内存之后就返回了,所以说QPS基本不受后端存储性能的影响。
这边简单说一下mongodb数据存储的原理:
内存映射机制
Mongo使用了内存映射技术(mmap) - 写入数据时候只要在内存里完成就可以返回给应用程序,而保存到硬盘的操作则在后台异步完成。这也就是说,MongoDB并不对RAM和磁盘这两者进行区别对待,只是将文件看作一个巨大的数组,然后按照字节为单位访问其中的数据,剩下的都交由操作系统(OS)去处理。先了解一下Memeory-Mapped Files:
1、内存映射文件是OS通过mmap在内存中创建一个数据文件,这样就把文件映射到一个虚拟内存的区域。
2、虚拟内存对于进程来说,是一个物理内存的抽象,寻址空间大小为2^64
3、操作系统通过mmap来把进程所需的所有数据映射到这个地址空间(红线),然后再把当前需要处理的数据映射到物理内存(灰线)
4、当进程访问某个数据时,如果数据不在虚拟内存里,触发page fault,然后OS从硬盘里把数据加载进虚拟内存和物理内存
5、如果物理内存满了,触发swap-out操作,这时有些数据就需要写回磁盘,如果是纯粹的内存数据,写回swap分区,如果不是就写回磁盘。

Storage View
Mongodb有三个storage view:Share view,private view,journal日志,前两个位于内存中,后一个位于磁盘上。
- Share view:位于内存上,会存储已经改变的要刷新到磁盘上的数据(脏数据)。Share view是唯一一个直接连映射到数据库文件上的view。当你启用mongoDB的日志功能时,mongod会请求操作系统把磁盘上的数据文件指向share view内存视图上。操作系统不会数据文件加载到share view中。Mongdb在需要时自己把数据文件加载到share view上。
- Private view:位于内存上,存储用于读请求的数据,更改请求最先在这执行。MongDB把Private view指向share view。
- Journal view:存储已经在private cache上发生更改的数据,但是会在更改数据刷新到 share view(cache) 之前存储。Journal 确保了数据的持久化。如果更改的数据没有刷新到磁盘上的数据文件里,mongodb crash了,当mongodb 起来以后,mongodb会把journallog中没应用到数据文件中的数据回放到 share view(cache) 中,最终会应用到数据文件中。
当一个写请求发生时:
1、更改 private view (cache)中的数据
2、默认每100毫秒刷新到journal log。journal log有一个记录当前日志点的pointer
3、应用journal log中的写操作到share view ,这时share view 就和数据文件不一致
4、默认每隔60秒,mongodb会请求操作系统刷新shared view中更改的数据到数据文件
5、mongdb会把journal log中记录更改数据日志点的pointer,以前的数据删除掉。
6、为了数据的一致性,Mongodb通常会请求操作系统重新把share view 指向private view。

所以说,在客户端连接数一定的情况下(使用mongoose情况下连接池默认为5),单个node实例占用的mongodb资源是固定的,性能也是接近的(都是操作内存),实测将k8s中的mongodb实例的内存配额调低到2GB,测出的性能没有太大差距,所以说mongo的性能和内存的关系不大(当然大内存会降低cpu的资源消耗)。
补充测试
既然前面提高了使用node连接mongo,默认连接池大小为5(数据库连接相关设置,请参考http://www.mongoosejs.net/docs/connections.html
),那么我们来测试一下,如果调整参数,是否会对于性能产生影响。仍然还是单node副本,数据库连接配置如下:
config.mongoose = {
client: {
url: 'mongodb://22.196.66.200:31669/test',
options: {
user: 'test',
pass: 'test',
poolSize: 50,
},
},
};
调整为50,测试结果如下:
[root@dce304-cal-vm3 ~]# wrk -c 200 -s demo.lua http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 188.41ms 111.62ms 1.87s 90.22%
Req/Sec 316.79 126.13 626.00 70.90%
6236 requests in 10.02s, 3.37MB read
Socket errors: connect 0, read 0, write 0, timeout 85
Requests/sec: 622.50
Transfer/sec: 344.11KB
cpu使用率为13%
结果:增强pooSize大小对于QPS并没有实质性影响,最有效的增加性能的方式还是添加实例数。从测试结果来看,影响QPS的不是mongodb的连接数,而是nodejs的并发数限制,因为node本身是单进程模型,单进程能处理的并发数有限,不能充分利用cpu多核的优势。
为了进一步验证,我们把eggjs的工作线程workers的数量设置为4,重启pod进行测试:
[root@dce304-cal-vm3 ~]# wrk -c 200 -s demo.lua http://22.196.66.200:31010/users
Running 10s test @ http://22.196.66.200:31010/users
2 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 82.35ms 29.02ms 347.47ms 76.04%
Req/Sec 1.21k 285.20 1.76k 81.50%
24194 requests in 10.02s, 13.06MB read
Requests/sec: 2415.47
Transfer/sec: 1.30MB
可以看出来QPS马上变成原来的4倍,所以说影响nodejs性能的主要因素还是**线程数**
参考资料:
[http://blog.chinaunix.net/uid-25135004-id-3810200.html](http://blog.chinaunix.net/uid-25135004-id-3810200.html)