我想就我正在考虑使用 Redis 排序集实现可搜索索引的两种方法获得一些反馈和建议。
情况和目标
我们目前有一些键值表存储在 Cassandra 中,并且我们希望为其创建索引。例如,一张表将包含人员记录,Cassandra 表将以 id 作为其主键,将序列化对象作为值。该对象将具有诸如 first_name、last_name、last_updated 等字段。
我们想要的是能够进行搜索,例如 "last_name = 'Smith' AND first_name > 'Joel'" 、 "last_name < 'Aaronson'" 、 "last_name = 'Smith' AND first_name = 'Winston'" 等等. 搜索应该会产生匹配的 id,这样我们就可以从 Cassandra 中检索对象。我认为上述搜索可以使用单个索引完成,按字典顺序按 last_name、first_name 和 last_updated 排序。如果我们需要一些使用不同顺序的搜索(例如“first_name = 'Zeus'”),我们可以有一个类似的索引来允许这些搜索(例如 first_name、last_updated)。
我们正在考虑为此使用 Redis,因为我们需要能够每分钟处理大量写入。我已经阅读了一些使用 Redis 排序集的常用方法,并提出了两种可能的实现:
选项 1:每个索引一个排序集
对于我们的按 last_name、first_name、last_updated 的索引,我们将在 Redis 中的键 index:people:last_name:first_name:last_updated 下有一个排序集,其中包含格式为 last_name:first_name:last_updated:id 的字符串。例如:
史密斯:乔尔:1372761839.444:0azbjZRHTQ6U8enBw6BJBw
(对于分隔符,我可能会使用 '::' 而不是 ':' 或其他更好地使用字典顺序的东西,但现在让我们忽略它)
这些项目都将被赋予 0 分,因此已排序的集合将仅按字符串本身按字典顺序排序。然后,如果我想执行“last_name = 'smith' AND first_name < 'bob'”之类的查询,则需要获取列表中“smith:bob”之前的所有项目。
据我所知,这种方法有以下缺点:
- 没有 Redis 函数可以根据字符串值选择范围。此功能称为 ZRANGEBYLEX,由 Salvatore Sanfilippo 在https://github.com/antirez/redis/issues/324提出,但尚未实现,因此我必须使用二进制搜索找到端点并自己获取范围(可能使用 Lua,或者在应用程序级别使用 Python,这是我们用来访问 Redis 的语言)。
- 如果我们想包含索引条目的生存时间,似乎最简单的方法是定期安排任务,该任务遍历整个索引并删除过期项目。
选项 2:小排序集,按 last_updated 排序
这种方法是相似的,除了我们会有许多更小的排序集,每个集都有一个类似时间的值,例如分数的 last_updated。例如,对于相同的 last_name、first_name、last_updated 索引,我们将为每个 last_name、first_name 组合设置一个排序集。例如,键可能是 index:people:last_name=smith:first_name=joel ,对于我们称为 Joel Smith 的每个人,它都有一个条目。每个条目的名称为 id,其分数为 last_updated 值。例如:
值:0azbjZRHTQ6U8enBw6BJBw;评分:1372761839.444
这样做的主要优点是 (a) 我们知道除 last_updated 之外的所有字段的搜索将非常容易,并且 (b) 使用 ZREMRANGEBYSCORE 实现生存时间将非常容易。
对我来说似乎很大的缺点是:
- 以这种方式管理和搜索似乎要复杂得多。例如,我们需要索引来跟踪它的所有键(例如,如果我们想在某个时候清理)并以分层方式执行此操作。诸如“last_name < 'smith'”之类的搜索将需要首先查看所有姓氏的列表以找到在 smith 之前的那些,然后查找其中包含的所有名字的每个人,然后查找其中的每一个从其排序集中获取所有项目。换句话说,要构建和担心很多组件。
包起来
所以在我看来,第一种选择会更好,尽管它有缺点。我非常感谢有关这两个或其他可能的解决方案的任何反馈(即使它们是我们应该使用 Redis 以外的东西)。