背景
Pika 的 ZRemrangebyscore 命令的作用如下:
1 | redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示有序集内所有成员及其 score 值 |
该命令用于删除 score 在指定范围下的 key,该命令在 Pika 中的实现是以迭代器的方式对范围内的 key 进行迭代删除,这样做的缺点在于每次都需要遍历删除一个 score key 和 member key 效率非常低,ZRemrangebyscore 目前的实现方法如下:
可以看到在一个 for 循环里面对每个 key 进行遍历,然后用 batch 进行删除。最后统计出删的个数在 Meta-value中做更新。
通过一个 python 脚本的压测,我们每次往一个 Zset 里面插入 50w 条数据,然后进行折半删除,并循环进行这样的操作,可以看到最终测出平均 25w 元素在 0.6s 左右删除,平均每秒删除 41w 左右个元素。
这是在循环压测时 Pika 的一些 CPU,内存的占比,CPU 使用率在 100% 左右.
解决方案
在新的 ZRemrangebyscore 的实现方法中,我们采用 RocksDB 中的 DeleteRange 方法,这个函数可以根据给定的一个 min, max 的 key, 将范围内的 key 全部删除,不再进行迭代删除,具体实现方法如下:
可以看到,我们依然还是用 for 循环去遍历,这是因为 ZRemrangebyscore 是根据 score 的大小去删除 key, 而在 Zset 中每次 Zadd 插入元素时,我们都会存两份数据,一个是根据 score 大小排名的 key,还有一部分是根据member 大小(字典序)排名的 key,所以我们在进行删除时,根据 member 大小排名的 key 我们还是得进行遍历删除,而根据 score 大小排名的 key,我们可以直接调用 DeleteRange 函数进行删除,所以这种优化方案大概优化 50%。以下是新方法实现 ZRemrangebyscore 的压测数据:
可以看到同样是每次往一个 Zset 里面插入 50w 条数据,然后进行折半删除,并循环进行这样的操作,可以看到最终测出平均 25w 元素在 0.34s 左右删除,平均每秒删除 72w 左右个元素,比之前提升了差不多 50% 的性能
对比之前没改之前的 CPU 占比,我们发现在新方法实现下 CPU 和之前上涨了50%左右