如何避免在Cassandra中使用二级索引?

15

我一直听说在Cassandra中,二级索引只是为了方便而不是为了提高性能。唯一建议使用二级索引的情况是当您的基数很低时(例如gender column只有两个值:男或女)

考虑以下示例:

CREATE TABLE users ( 
userID uuid, 
firstname text, 
lastname text, 
state text, 
zip int, 
PRIMARY KEY (userID) 
);

现在我无法执行这个查询,除非我在users上创建一个辅助索引,即在firstname index上。

select * from users where firstname='john'

如何将这张表格去规范化,以便我可以使用以下查询: 这是唯一有效的方法吗?还有其他的替代方案或建议吗?

CREATE TABLE users ( 
    userID uuid, 
    firstname text, 
    lastname text, 
    state text, 
    zip int, 
    PRIMARY KEY (firstname,userID) 
    );
3个回答

19
为了得到一个好的数据模型,你需要首先识别出所有你想要执行的查询。如果你只需要按照用户的名字(或名字和用户ID)查找用户,那么第二个设计是可以的...
如果您还需要按用户的姓氏查找用户,则可以创建另一个具有相同字段但主键为(lastname,userID)的表。显然,您需要同时更新两个表。在Cassandra中,数据复制是可以接受的。
然而,如果您担心两个或更多表所需的空间,您可以创建一个由用户ID分区的单个用户表,以及其他要按字段查询的表:
CREATE TABLE users ( 
    userID uuid, 
    firstname text, 
    lastname text, 
    state text, 
    zip int, 
    PRIMARY KEY (userID) 
);

CREATE TABLE users_by_firstname (
    firstname text,
    userid uuid,
    PRIMARY KEY (firstname, userid)
);

这种解决方案的缺点是需要使用两个查询才能通过名字检索到用户:

SELECT userid FROM users_by_firstname WHERE firstname = 'Joe';
SELECT * FROM users WHERE userid IN (...);

希望这能有所帮助


4
我认为你问题的核心是“我如何对这个表进行反规范化以便进行查询”。正如之前提到的,除非通过分区键预过滤数据,否则二级索引的性能并不好。根据我在相对较小的集群(5个节点)上的测试,它们可能比按分区键查询慢10倍。在更大的集群上,差异可能会更大。根据我的经验,最好使用二次表模拟它们,就像我在users_by_firstname表中所做的那样。 - medvekoma
那么你的意思是说执行两个查询比在二级索引上进行单个查找更快,对吗? - brain storm
3
正确。原因在于行是按分区键进行分区的,所以在查找时Cassandra知道哪个节点保存了数据。使用二级索引时,每个节点都维护自己的索引,查询需要在所有节点上执行,然后将结果合并。因此,是的,通过分区键进行两次查询比通过二级索引进行一次查询更快。使用不同主键复制表也是一个好的解决方案。 - medvekoma
2
以您的示例为例,您可以将用户表的主键设置为(firstname,userid)。然后,您可以在lastname上创建一个辅助索引。在这种情况下,查询SELECT * from users where firstname='Joe' and lastname='Doe' 可以从lastname上的索引中受益,因为数据已经通过firstname进行了预过滤,所以它可以在单个节点上执行。另一个用例是当您不太关心性能,只想要一个简单的解决方案时。 - medvekoma
1
更改名字将在Cassandra中创建一行新记录。您必须删除或停用现有的记录。 - medvekoma
显示剩余4条评论

4
有几种方法可以实现这个目标,每种方法都有其优缺点。
  • 您的第二个查询将起作用,但它只是一个索引表。 http://wiki.apache.org/cassandra/SecondaryIndexes 辅助索引可能会有所帮助,如果您首先命中分区(在第一个表中无法执行此操作),则Cassandra的实现将为您节省麻烦并保持“本地原子性”。但是,如果没有命中分区,则具有索引的第一个表在查询方面效果不佳,因为它会在所有地方击中所有内容。

  • 您可以完全去规范化,但也可以创建查找表。即:您的第二个表只能存在以返回用户ID。然后,您可以执行第二个查询,仅获取相关分区的信息。如果您预计结果很少,则这可能很好。否则,您将在许多节点上击中许多分区(这取决于群集大小和热点避免标准,可能是好或坏)。通常做许多~1ms查询比做一个~1000ms查询要好。

  • 您可以进行人工分桶,并发出n = bucketcount查询。这具有额外的开销,但可以减少查询计数,这可能是一个不错的选择。

  • 您的索引可能是名字的前几个字符。或者它可以是一致性哈希到几个桶中。前者可以为您提供“开始于”语义。

这些只是一些选项。从逻辑数据模型到物理数据模型需要评估您希望进行哪些权衡。


请提供第2点和第3点的示例表格,我不太明白。 - brain storm
2:创建表lookup(firstname文本主键,userid uuid) 3:创建表foo(bucketid int,somecol int,... primary key(bucketid,your_cols)。 查询时,您可以执行... WHERE bucketid in(1,2,3)。 这将在3个分区上发出3个查询。 - ashic
你的第二点和我在帖子中提到的第一张表是相同的。这将不允许 where firstname="john" 这样的查询。BucketID 看起来很有趣,但我不完全清楚它是如何工作的。如果你能编辑答案并说明如何使用 BucketID,那将非常有帮助。谢谢。 - brain storm

0

还有自动更新的材料化视图,可以将数据分区到不同的列上,因此读取速度更快,完全避免了二级索引。这样做还有一些额外的好处。

避免热分区的总体思路仍然存在。

如果您在材料化视图主键上进行大量更新以避免墓碑,则还有SASI索引可用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接