性能文章>【译】比较缓存数据库Redis与MongoDB的性能>

【译】比较缓存数据库Redis与MongoDB的性能转载

2月前
247536

在这篇文章中,我们将比较两个最流行的NoSQL 数据库:Redis 和 MongoDBPercona内存存储引擎)。

Redis 是一种https://db-engines.com/en/ranking流行且非常快速的缓存数据库,主要用作缓存或消息代理。在内存中,当响应时间胜过其他一切时,它是首选的数据存储。

MongoDB 是一个磁盘上的文档存储,它为数据提供 JSON 接口,并具有非常丰富的查询语言。它以其速度、效率和可扩展性而闻名,是目前最流行的 NoSQL 数据库。但是,作为一个磁盘数据库,它在绝对性能方面无法与 Redis 这样的内存数据库相比。直到 MongoDB 存储引擎的出现,它们之间的比较也变得有意义。

MongoDB 的 Percona 内存存储引擎

从版本 3.0 开始,MongoDB 提供了一个 API 来插入你选择的 存储引擎。来自 MongoDB 上下文的存储引擎是数据库的组件,负责管理数据在内存中和磁盘上的存储方式。MongoDB 支持内存存储引擎,但目前仅限于产品的企业版。2016 年,Percona 为 MongoDB 社区版发布了一个开源的内存引擎,称为 Percona Memory Engine for MongoDB。与 MonogDB 的内存引擎一样,它也是WiredTiger存储引擎的变体,但没有持久化到磁盘。

有了内存中的 MongoDB 存储引擎,我们就可以在 Redis 和 MongoDB 之间建立一个公平的竞争环境。那么,为什么我们需要比较两者呢?让我们看看它们各自作为缓存解决方案的优势。

我们先来看看 Redis。

Redis 作为缓存的优势

  • 多年来作为一个流行的缓存解决方案,已经非常成熟;
  • Redis 不是一个普通的缓存解决方案——它具有 先进的数据结构,提供了许多强大的方法来保存和查询数据,而这些方法是普通键值缓存无法实现的;
  • Redis 的设置、使用和学习相当简单;
  • Redis 提供了您可以选择设置的持久性,因此不用担心发生崩溃时缓存预热问题。

Redis的缺点:

  • 它没有线上的内置加密。
  • 没有基于角色的帐户控制 ( RBAC )。
  • 没有无缝、成熟的集群解决方案。
  • 在大规模云部署中会非常繁琐。

MongoDB 作为缓存的优势

  •  MongoDB 是一个更传统的数据库,具有高级数据操作功能(想想聚合和 map-reduce)和丰富的查询语言。
  • 内置 SSL、RBAC 和横向扩展。
  • 如果您已经在使用 MongoDB 作为主数据库,那么您的运营和开发成本就会下降,因为您只需学习和管理一个数据库。

 (Peter Zaitsev 的这篇文章,专门介绍了 MongoDB 内存引擎可能适合的地方)

MongoDB的缺点:

  • 使用内存引擎,在将其部署为副本集并在只读副本上配置持久性之前,它不提供持久性。

在这篇文章中,我们将专注于量化 Redis 和 MongoDB 之间的性能差异。后续更新定性比较和操作差异内容。

Redis 与内存中的 MongoDB

表现

  • Redis对于各种工作负载的读取性能要好得多,随着工作负载的增加,对于写入的性能也更好。
  • 尽管 MongoDB 使用了系统的所有核心,但它相对较早地获得了 CPU 限制。虽然它仍然有可用的计算能力,但它在写入方面比 Redis 更好。
  • 两个数据库最终都受计算约束。尽管 Redis 是单线程的,但它(大多数情况下)在一个内核上运行比 MongoDB 在所有内核饱和时完成的工作更多。
  • Redis对于非平凡的数据集,与 MongoDB 相比使用更多的 RAM来存储相同数量的数据。

配置

我们使用 YCSB 来衡量性能,并且过去一直使用它来 比较和基准测试 MongoDB 在各种 云提供商和 配置上的性能。我们假设对测试台描述中的YCSB 工作负载功能有基本的了解。

  • 数据库实例类型:   AWS EC2 c4.xlarge 具有 4 核、7.5 GB 内存和增强的网络,以确保我们没有任何网络瓶颈。
  • 客户端机器:   AWS EC2 c4.xlarge 位于与数据库服务器相同的虚拟私有云 (VPC) 中。
  • Redis:  版本 3.2.8,关闭了 AOF 和 RDB。独立。
  • MongoDB:  基于 MongoDB 版本 3.2.12 的 Percona 内存引擎。独立。
  • 网络吞吐量:按照AWS 的建议通过iperf测量:
    Test Complete. Summary Results:
    [ ID] Interval           Transfer     Bandwidth       Retr
    [  4]   0.00-60.00  sec  8.99 GBytes  1.29 Gbits/sec  146             sender
    [  4]   0.00-60.00  sec  8.99 GBytes  1.29 Gbits/sec                  receiver
  • 工作负载详细信息
    1. 插入工作量: 100% 写入 – 250 万条记录
    2. 工作负载 A:更新繁重的工作负载 – 50%/50% 读取/写入 – 2500 万次操作
    3. 工作负载 B:主要读取工作负载 - 95%/5% 读取/写入 - 2500 万次操作
  • 客户端负载: 通过客户端生成的增量负载测量的吞吐量和延迟。这是通过增加 YCSB 客户端负载线程的数量来完成的,从 8 开始并以 2 的倍数增长

结果

工作负载 B 性能

由于内存数据库的主要用例是缓存,让我们先看看工作负载 B。

以下是 2500 万次操作工作负载的吞吐量/延迟数字,读/写比率为 95%/5%。这将是一个有代表性的缓存读取工作负载:

工作负载B25M

注意:吞吐量是针对主轴(左)绘制的,而延迟是针对辅助轴(右)绘制的。

工作负载 B 运行期间的观察结果:

  • 对于 MongoDB,CPU 由 32 个线程开始饱和。超过 300% 的使用率和个位数的空闲百分比。
  • 对于 Redis,CPU 利用率从未超过 95%。因此,在单线程上运行时,Redis 的性能始终优于 MongoDB,而 MongoDB 则使机器的所有内核饱和。
  • 对于 Redis,在 128 个线程上,运行经常会因读取超时异常而失败。

工作负载 A 性能

以下是 2500 万次操作工作负载的吞吐量/延迟数字。读/写比率为 50%/50%:

工作量A25M

注意:吞吐量是针对主轴(左)绘制的,而延迟是针对辅助轴(右)绘制的。

工作负载 A 运行期间的观察结果:

  • 对于 MongoDB,CPU 由 32 个线程开始饱和。超过 300% 的使用率和个位数的空闲百分比。
  • 对于 Redis,CPU 利用率从未超过 95%。
  • 对于 64 线程及以上的 Redis,运行经常因读取超时异常而失败。

插入工作负载性能

最后,这是来自 250 万条记录插入工作负载的吞吐量/延迟数字。选择记录数是为了确保在 Redis 事件中使用的总内存不超过 80%(因为 Redis 是内存大户)。

插入2-5M

注意:吞吐量是针对主轴(左)绘制的,而延迟是针对辅助轴(右)绘制的。

插入工作负载运行期间的观察结果:

  • 对于 MongoDB,CPU 由 32 个线程开始饱和。超过 300% 的使用率和个位数的空闲百分比。
  • 对于 Redis,CPU 利用率从未超过 95%。

附录

A:单线程性能

我有一种强烈的想要找出答案的冲动——尽管它在现实世界的条件下并不是很有用:当从单个线程对它们中的每一个应用相同的负载时,谁会更好。也就是说,单线程应用程序将如何执行?

B:数据库大小

YCSB 插入记录的默认格式为:每条记录有 10 个字段,每个字段为 100 字节。假设每条记录大约为 1KB,内存中的总预期大小将超过 2.4GB。与数据库中的实际尺寸形成鲜明对比。

MongoDB

> db.usertable.count()
2500000
> db.usertable.findOne()
{
    "_id" : "user6284781860667377211",
    "field1" : BinData(0,"OUlxLllnPC0sJEovLTpyL18jNjk6ME8vKzF4Kzt2OUEzMSEwMkBvPytyODZ4Plk7KzRmK0FzOiYoNFU1O185KFB/IVF7LykmPkE9NF1pLDFoNih0KiIwJU89K0ElMSAgKCF+Lg=="),
    "field0" : BinData(0,"ODlwIzg0Ll5vK1s7NUV1O0htOVRnMk53JEd3KiwuOFN7Mj5oJ1FpM11nJ1hjK0BvOExhK1Y/LjEiJDByM14zPDtgPlcpKVYzI1kvKEc5PyY6OFs9PUMpLEltNEI/OUgzIFcnNQ=="),
    "field7" : BinData(0,"N155M1ZxPSh4O1B7IFUzJFNzNEB1OiAsM0J/NiMoIj9sP1Y1Kz9mKkJ/OiQsMSk2OCouKU1jOltrMj4iKEUzNCVqIV4lJC0qIFY3MUo9MFQrLUJrITdqOjJ6NVs9LVcjNExxIg=="),
    "field6" : BinData(0,"Njw6JVQnMyVmOiZyPFxrPz08IU1vO1JpIyZ0I1txPC9uN155Ij5iPi5oJSIsKVFhP0JxM1svMkphL0VlNzdsOlQxKUQnJF4xPkk9PUczNiF8MzdkNy9sLjg6NCNwIy1sKTw6MA=="),
    "field9" : BinData(0,"KDRqP1o3KzwgNUlzPjwgJEgtJC44PUUlPkknKU5pLzkuLEAtIlg9JFwpKzBqIzo2MCIoKTxgNU9tIz84OFB/MzJ4PjwoPCYyNj9mOjY+KU09JUk1I0l9O0s/IEUhNU05NShiNg=="),
    "field8" : BinData(0,"NDFiOj9mJyY6KTskO0A/OVg/NkchKEFtJUprIlJrPjYsKT98JyI8KFwzOEE7ICR4LUF9JkU1KyRkKikoK0g3MEMxKChsL10pKkAvPFRxLkxhOlotJFZlM0N/LiR4PjlqJ0FtOw=="),
    "field3" : BinData(0,"OSYoJTR+JEp9K00pKj0iITVuIzVqPkBpJFN9Myk4PDhqOjVuP1YhPSM2MFp/Kz14PTF4Mlk3PkhzKlx3L0xtKjkqPCY4JF0vIic6LEx7PVBzI0U9KEM1KDV4NiEuKFx5MiZyPw=="),
    "field2" : BinData(0,"Njd8LywkPlg9IFl7KlE5LV83ISskPVQpNDYgMEprOkprMy06LlotMUF5LDZ0IldzLl0tJVkjMTdgJkNxITFsNismLDxuIyYoNDgsLTc+OVpzKkBlMDtoLyBgLlctLCxsKzl+Mw=="),
    "field5" : BinData(0,"OCJiNlI1O0djK1BtIyc4LEQzNj9wPyQiPT8iNE1pODI2LShqNDg4JF1jNiZiNjZuNE5lNzA8OCAgMDp2OVkjNVU3MzIuJTgkNDp0IyVkJyk6IEEvKzVyK1s9PEAhKUJvPDxyOw=="),
    "field4" : BinData(0,"OFN1I0B7N1knNSR2LFp7PjUyPlJjP15jIUdlN0AhNEkhMC9+Lkd5P10jO1B3K10/I0orIUI1NzYuME81I0Y1NSYkMCxyI0w/LTc8PCEgJUZvMiQiIkIhPCF4LyN6K14rIUJlJg==")
}
> db.runCommand({ dbStats: 1, scale: 1 })
{
    "db" : "ycsb",
    "collections" : 1,
    "objects" : 2500000,
    "avgObjSize" : 1167.8795252,
    "dataSize" : 2919698813,
    "storageSize" : 2919698813,
    "numExtents" : 0,
    "indexes" : 1,
    "indexSize" : 76717901,
    "ok" : 1
}

因此,占用的空间约为 2.7GB,非常接近我们的预期。

Redis

现在让我们看看 Redis。

> info keyspace
# Keyspace
db0:keys=2500001,expires=0,avg_ttl=0
127.0.0.1:6379> RANDOMKEY
"user3176318471616059981"
127.0.0.1:6379> hgetall user3176318471616059981
 1) "field1"
 2) "#K/<No\"&l*M{,;f;]\x7f)Ss'+2<D}7^a8I/01&9.:)Q71T7,3r&\\y6:< Gk;6n*]-)*f>:p:O=?<:(;v/)0)Yw.W!8]+4B=8.z+*4!"
 3) "field2"
 4) "(9<9P5**d7<v((2-6*3Zg/.p4G=4Us;N+!C! I50>h=>p\"X9:Qo#C9:;z.Xs=Wy*H3/Fe&0`8)t.Ku0Q3)E#;Sy*C).Sg++t4@7-"
 5) "field5"
 6) "#1 %8x='l?5d38~&U!+/b./b;(6-:v!5h.Ou2R}./(*)4!8>\"B'!I)5U?0\" >Ro.Ru=849Im+Qm/Ai(;:$Z',]q:($%&(=3~5(~?"
 7) "field0"
 8) "+\"(1Pw.>*=807Jc?Y-5Nq#Aw=%*57r7!*=Tm!<j6%t3-45L5%Cs#/h;Mg:Vo690-/>-X}/X#.U) )f9-~;?p4;p*$< D-1_s!0p>"
 9) "field7"
10) ":]o/2p/3&(!b> |#:0>#0-9b>Pe6[}<Z{:S}9Uc*0<)?60]37'~'Jk-Li',x!;.5H'\"'|.!v4Y-!Hk=E\x7f2;8*9((-09*b#)x!Pg2"
11) "field3"
12) " C; ,f6Uq+^i Fi'8&0By\"^##Qg\":$+7$%Y;7Rs'\"d3Km'Es>.|33$ Vo*M%=\"<$&j%/<5]%\".h&Kc'5.46x5D35'0-3l:\"| !l;"
13) "field6"
14) "-5x6!22)j;O=?1&!:&.S=$;|//r'?d!W54(j!$:-H5.*n&Zc!0f;Vu2Cc?E{1)r?M'!Kg'-b<Dc*1d2M-9*d&(l?Uk5=8,>0.B#1"
15) "field9"
16) "(Xa&1t&Xq\"$((Ra/Q9&\": &>4Ua;Q=!T;(Vi2G+)Uu.+|:Ne;Ry3U\x7f!B\x7f>O7!Dc;V7?Eu7E9\"&<-Vi>7\"$Q%%A%1<2/V11: :^c+"
17) "field8"
18) "78(8L9.H#5N+.E5=2`<Wk+Pw?+j'Q=3\"$,Nk3O{+3p4K?0/ 5/r:W)5X}#;p1@\x7f\"+&#Ju+Z97#t:J9$'*(K).7&0/` 125O38O)0"
19) "field4"
20) "$F=)Ke5V15_)-'>=C-/Ka7<$;6r#_u F9)G/?;t& x?D%=Ba Zk+]) ($=I%3P3$<`>?*=*r9M1-Ye:S%%0,(Ns3,0'A\x7f&Y12A/5"
127.0.0.1:6379> info memory
# Memory
used_memory:6137961456
used_memory_human:5.72G
used_memory_rss:6275940352
used_memory_rss_human:5.84G
used_memory_peak:6145349904
used_memory_peak_human:5.72G
total_system_memory:7844429824
total_system_memory_human:7.31G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:7516192768
maxmemory_human:7.00G
maxmemory_policy:noeviction
mem_fragmentation_ratio:1.02
mem_allocator:jemalloc-3.6.0

在高峰使用时,Redis 似乎占用了大约 5.72G 的内存,即MongoDB 占用的内存的两倍。现在,由于两个数据库的差异,这种比较可能并不完美,但是这种内存使用量的差异太大而不容忽视。YCSB 在 Redis 的哈希中插入记录,并在有序集中维护一个索引。由于单个条目大于 64,因此散列被正常编码并且没有节省空间。Redis 的性能是以增加内存占用为代价的。

在我们看来,这可能是在 MongoDB 和 Redis 之间进行选择的一个重要数据点——MongoDB 可能更适合那些关心降低内存成本的用户。

C:网络吞吐量

内存数据库服务器可能会受到计算限制或网络 I/O 限制,因此在整个这些测试集中确保我们永远不会受到网络限制是很重要的。在运行应用程序吞吐量测试时测量网络吞吐量会对整体吞吐量测量产生不利影响。因此,我们在观察到最高写入吞吐量的线程数处使用iftop运行后续网络吞吐量测量。发现 Redis 和 MongoDB 在各自的峰值吞吐量下约为 440 Mbps。鉴于我们最初测量的最大网络带宽约为 1.29 Gbps,我们确信我们从未达到网络边界。事实上,它只支持这样的推论,如果 Redis 是多核的,我们可能会得到更好的数字。

 

点赞收藏
willberthos

keep foolish!

请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

记一次 Druid 超时配置的问题 → 引发对 Druid 时间配置项的探究

记一次 Druid 超时配置的问题 → 引发对 Druid 时间配置项的探究

技术分享 | 幽灵攻击与编译器中的消减方法介绍

技术分享 | 幽灵攻击与编译器中的消减方法介绍

【译】记一次数据库连接泄漏导致的响应迟缓

【译】记一次数据库连接泄漏导致的响应迟缓

【全网首发】不经意的两行代码把CPU使用率干到了90%+

【全网首发】不经意的两行代码把CPU使用率干到了90%+

【全网首发】Tablestore-OTSClient连接池连接无法复用分析

【全网首发】Tablestore-OTSClient连接池连接无法复用分析

如何修改 Nginx 源码实现 worker 进程隔离

如何修改 Nginx 源码实现 worker 进程隔离

6
3