为什么我们不做 Redis 与其他数据库的对比基准测试
原文由 Salvatore Sanfilippo 于 发布,订阅该博客
Redis 的速度本可以成为吸引新用户的一大卖点,所以顺应那种对比式“宣传”的潮流,在 Redis.io 上放几组对比数据似乎是顺理成章的事。但这样做有两个问题。一是目标问题:我并不想说服开发者去采用 Redis,我们只是尽力提供一个好用的产品,如果大家能用它完成工作,我们就很满足了——我对营销的期待也就到此为止。更重要的是:要以公平的方式比较不同的系统,几乎是不可能的。
要比较两个数据库并得出公平的数字,它们需要在*很多*方面保持一致:数据模型、确切的持久化保证、数据复制的安全性、分区期间的可用性等等——常常会出现一个系统得分低于另一个系统,仅仅是因为它为了提供那些不那么“吸睛”却同样至关重要的特性而牺牲了速度。此外,除非不同的数据库系统使用完全相同的协议,否则测试套件本身也是一个复杂的问题:仅客户端库的差异就可能带来巨大的性能差距。
当然,也有人持不同意见,认为比较不同数据库系统的速度仍然是个好主意。例如,昨天就有人在这里发布了一份 Redis 与 AerospikeDB 的基准测试:http://lynnlangit.com/2015/01/28/lessons-learned-benchmarking-nosql-on-the-aws-cloud-aerospikedb-and-redis/。
我将以这份基准测试为例,来说明基准测试是多么容易误导人。在这份测试中不知出于何种原因使用了巨大的 EC2 实例,这些实例配备了 244GB 内存(!)。那是 R3.8xlarge 实例。而我的测试将使用更贴近现实的 m3.medium 实例。
在那种庞大的实例上,Redis 在单节点情况下达到了每秒 12.8 万次操作。我的 EC2 实例要小得多,通过另一台 EC2 实例使用 Redis benchmark 进行测试,在不使用 pipelining、同样使用 100 字节数据大小的情况下,我测得的结果是每秒 3.2 万次操作,所以在单进程情况下,我的实例大概慢了 4 倍。
让我们通过 Redis 的 INFO 命令来看看这次基准测试期间系统是如何使用 CPU 的:
# CPU
used_cpu_sys:181.78
used_cpu_user:205.05
used_cpu_sys_children:0.12
used_cpu_user_children:0.87
127.0.0.1:6379> info cpu……10 秒测试之后……
# CPU
used_cpu_sys:184.52
used_cpu_user:206.42
used_cpu_sys_children:0.12
used_cpu_user_children:0.87Redis 花了约 3 秒的系统时间,而在用户态只花了约 1.5 秒。这里发生的情况是,对于每个请求,大部分工作都是在执行 read() 和 write() 调用。同时,由于每个客户端都是一问一答的工作模式,每个请求都要为每个客户端付出一次完整的往返时延。
现在来看看如果改用 pipelining 会发生什么,这是一个为 Redis 用户所熟知并被广泛利用的特性,因为它是最大化服务器利用率的唯一途径,而且在应用中通常也有不少场景可以一次性执行多个操作。
使用包含 32 个操作的 pipeline 后,数字发生了剧变。我的这个小实例仅用单核就能达到每秒 25 万次操作,这相当于前述基准测试中使用 32 个(每个都更快的)核心所取得的*最高*成绩的 25%。
再来看看 CPU 时间:
# CPU
used_cpu_sys:189.16
used_cpu_user:216.46
used_cpu_sys_children:0.12
used_cpu_user_children:0.87
127.0.0.1:6379> info cpu……10 秒测试之后……
# CPU
used_cpu_sys:190.60
used_cpu_user:224.92
used_cpu_sys_children:0.12
used_cpu_user_children:0.87这一次,我们的 CPU 是真正在用数据库引擎来处理查询,而不是把大量时间浪费在上下文切换上。我们用了约 1.5 秒的系统时间,而有 8.46 秒都花在了 Redis 进程本身。
使用更小的 pipeline 长度,结果会介于两者之间。pipeline 为 4 时是每秒 10 万次操作(换算到基准测试中使用的更大实例上大约相当于每秒 40 万次),pipeline 为 8 时是每秒 18 万次,以此类推。
所以说,以这种方式对 Redis 和 AerospikeDB 进行基准测试会得到惊人相似的结果,并非巧合。或多或少,你测试的根本不是数据库本身,而是网络协议栈和内核。如果数据库只需通过一次 read 和一次 write 系统调用就能完成查询,且没有其他巨大开销,得到的就是这样的结果,而此时实际执行查询的时间是很短的,因为我们讨论的是完全能放入内存的数据(顺便提一句,在 Redis 中 1000 万个 100 字节的键所占用的内存,只是那些实例所分配内存的一小部分)。
然而,问题还不止于此。我们能执行的操作有哪些呢?只用 GET/SET 来测试 Redis,就像测试一辆法拉利时只看它在下雨时擦后视镜的能力好不好一样。
Redis 架构的一个根本特点是,截然不同的操作其开销却大致相当,那么,比如我们那款拥有海量用户的 Facebook 游戏,需要提交已完成游戏的分数来生成排行榜,这样的场景又如何呢?
同一个单进程在执行这样的查询时:ZADD myscores <random-int> <random-int>,也能达到每秒 11 万次操作。
但我们还可以要求更高,比如用 HyperLogLog 来估算基数,同时用两个 redis-benchmark 进程一边添加新元素一边读取当前的估算值,会怎样?集合大小同样是 1000 万。所以在这次测试中,我启动了一个执行 PFADD、向集合中添加随机元素的 benchmark,同时另一个在同一个 HyperLogLog 上执行 PFCOUNT。两者同时都达到了每秒 25 万次操作,单个 Redis 进程合计每秒 50 万次操作。
在 Redis 中执行复杂操作就类似于使用 pipelining。你希望每次 read/write 能*做更多*的事情,否则性能就会被 I/O 所主导。
好了,总结几点有用的看法。1)用 GET/SET 做基准测试不是比较不同数据库系统的好方法。2)更好的性能比较方式是按用例来比。也就是说,对于某个具体用例,使用不同的数据模型、模式、查询和策略,用两种不同的数据库系统来支撑同一个应用的相同流量,各自需要多少台实例?3)要用大多数人实际会使用的实例类型来测试,巨大的实例类型可能会掩盖某些数据库系统的低效,而且反正也不是大多数人会用的配置。
我们会继续为速度优化 Redis,也会继续避免发布对比性的基准测试。
[感谢 Amazon AWS 为我免费提供 EC2 访问权限]
随机一篇博客
评论
登录后参与讨论