情况和目标
我们目前有一些键值表存储在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中拥有一个排序集,其键为indexes:people:last_name:first_name:last_updated,其中包含格式为last_name:first_name:last_updated:id的字符串。例如:
smith:joel: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组合拥有一个排序集。例如,键可能是indexes:people:last_name=smith:first_name=joel,它将为我们称为Joel Smith的每个人都有一个条目。每个条目的名称为id,其分数为last_updated值。例如:
value: 0azbjZRHTQ6U8enBw6BJBw ; score: 1372761839.444
主要优点是(a) 搜索我们知道除last_updated之外的所有字段将非常容易,(b) 使用ZREMRANGEBYSCORE实现生存时间将非常容易。
缺点是,对我来说似乎非常大:
- 以这种方式管理和搜索似乎有很多复杂性。例如,我们需要索引以分层方式跟踪其所有键(如果我们想要在某个时候进行清理),并且需要在层次结构中执行此操作。搜索如“last_name < 'smith'”需要首先查看所有姓氏列表,以找到那些出现在smith之前的姓氏,然后对于每个姓氏,查看它包含的所有名字,然后对于每个名字从其排序集合中获取所有项目。换句话说,需要构建和担心很多组件。
总结
因此,尽管存在缺点,第一种选择似乎更好。如果您对这两种或其他可能的解决方案(即使是我们应该使用Redis之外的东西)有任何反馈,我会非常感激。