快速回答
根据您的用例和预测的查询模式,我会为您的表推荐以下之一:
PRIMARY KEY (Stock_Symbol, Timestamp)
分区键由Stock_Symbol组成,Timestamp是唯一的聚类列。这将允许WHERE 与这两个字段一起使用。如果要过滤其中任何一个,查询中将需要过滤Stock_Symbol,并且必须作为WHERE的第一个条件。
或者,对于您列出的第二种情况:
PRIMARY KEY ((Exchange_ID, Stock_Symbol), Timestamp)
分区键由Exchange_ID和Stock_Symbol组成,Timestamp是唯一的聚类列。这将允许WHERE 与这三个字段一起使用。如果要过滤这三个中的任何一个,则查询中将需要同时过滤 Exchange_ID 和 Stock_Symbol,并且必须按照该顺序作为 WHERE 的前两个条件。
请参阅此答案的最后一部分,了解其他一些也可以根据您的需要应用的变体。
长答案和解释
主键、分区键和聚类列
Cassandra 中的主键,类似于它们在关系数据库中的作用,用于识别记录并为它们编制索引,以便快速访问它们。然而,由于 Cassandra 中记录的分布式特性,它们的次要目的是也确定给定记录应该存储在哪个节点上。
Cassandra 表中的主键进一步分为两部分 - Partition Key,这是必需的,默认情况下是主键中的第一列,以及可选的 clustering column(s),它们都是在不属于分区键的主键中。
这里有一些例子:
PRIMARY KEY (Exchange_ID)
Exchange_ID 是主键中的唯一字段,也是分区键。没有额外的聚类列。
PRIMARY KEY (Exchange_ID, Timestamp, Stock_Symbol)
Exchange_ID、Timestamp 和 Stock_Symbol 一起构成一个复合主键。分区键为Exchange_ID,Timestamp 和Stock_Symbol 都是集群列。
PRIMARY KEY ((Exchange_ID, Timestamp), Stock_Symbol)
Exchange_ID、Timestamp 和 Stock_Symbol 共同构成一个复合主键。分区键由Exchange_ID 和Timestamp 组成。额外的括号将Exchange_ID 和Timestamp 分组为一个复合分区键,Stock_Symbol 是一个集群列。
PRIMARY KEY ((Exchange_ID, Timestamp))
Exchange_ID 和Timestamp 一起构成一个复合主键。分区键由Exchange_ID 和Timestamp 组成。没有聚类列。
但他们是做什么的?
在内部,分区键用于计算令牌,令牌确定记录存储在哪个节点上。聚类列不用于确定将记录存储在哪个节点上,但它们用于确定记录在节点内的布局顺序 - 这在查询一系列记录时很重要。聚类列值相似的记录将彼此靠近存储在同一节点上;它们“聚集”在一起。
Cassandra 中的过滤
由于 Cassandra 的分布式特性,字段只有在被索引时才能被过滤。这可以通过几种方式来实现,通常是作为主键的一部分或通过在字段上设置二级索引。二级索引可能会导致性能问题according to DataStax Documentation,因此通常建议尽可能使用主键来捕获您的用例。
主键中的任何字段都可以应用WHERE 子句(与一般情况下无法过滤的未索引字段不同),但有一些规定:
-
Order Matters -
WHERE 子句中的主键字段必须按照定义的顺序;如果您的主键为(field1, field2, field3),则不能使用WHERE field2 = 'value',而是必须同时包含前面的字段:WHERE field1 = 'value' AND field2 = 'value'。
-
必须存在整个分区键 - 如果将
WHERE 子句应用于主键,则必须给出整个分区键,以便集群可以确定请求的数据位于集群中的哪个节点在;如果您的主键为((field1, field2), field3),则不能使用WHERE field1 = 'value',而是必须包含完整的分区键:WHERE field1 = 'value' AND field2 = 'value'。
应用于您的用例
考虑到上述信息,您可以像您所做的那样分析用户将如何查询数据库,并将该信息用于design your data model,或者更具体地说,在这种情况下,您的主键表。
您提到您将拥有大约 30k 个 Stock_Symbol 的唯一值,并且它始终包含在 WHERE 子句中。这听起来像是分区键的合理候选者,只要查询将只包含他们在Stock_Symbol 中搜索的单个值(例如WHERE Stock_Symbol = 'value' 而不是WHERE Stock_Symbol < 'value')。如果查询旨在返回具有多个 Stock_Symbol 中的多个值的记录,则存在集群需要从多个节点检索数据的危险,这可能会导致性能下降。
此外,如果您的用户希望过滤 Timestamp,它也应该是主键的一部分,尽管想要过滤范围表明它可能不应该是分区键的一部分,因此它是一个很好的聚类列候选者。
这让我想到了我的建议:
PRIMARY KEY (Stock_Symbol, Timestamp)
如果基于Stock_Symbol 和Timestamp 分发数据很重要,您可以引入一个基于时间但基数较小的预先计算的分时字段,例如Day_Of_Week或Month 或类似的东西:
PRIMARY KEY ((Stock_Symbol, Day_Of_Week), Timestamp)
如果您想为过滤引入另一个字段,例如 Exchange_ID,它可以是分区键的一部分,这将要求它包含在过滤器中,或者它可以是集群列的一部分,这这意味着除非需要过滤主键中的后续字段,否则不需要它。正如您提到的,用户将始终按 Exchange_ID 过滤,然后按 Stock_Symbol 过滤,这样做可能是有意义的:
PRIMARY KEY ((Exchange_ID, Stock_Symbol), Timestamp)
或者让它成为非强制性的:
PRIMARY KEY (Stock_Symbol, Exchange_ID, Timestamp)