Apache Cassandra 股票数据模型设计

Apache Cassandra stock data model design

我有很多关于股票价格的数据,我想为此试用 Apache Cassandra。但是我对主/分区/集群键不是很熟悉。

我的数据库列是:

Stock_Symbol
Price
Timestamp

我的用户将始终针对 Stock_Symbol(其中 stock_symbol=XX)进行过滤,然后他们可能会针对某个时间范围(大于/小于(等于))进行过滤。将有大约 30.000 个股票代码。

此外,使用另一个“过滤器”时的最大区别是什么,例如exchange_id(只有两家证券交易所可用)

Exchange_ID
Stock_Symbol
Price
Timestamp

所以我的用户会首先过滤证券交易所(或多或少是外键),然后是股票代码(或多或少也是外键)。数据也将按此顺序插入/写入。

我该如何选择按键?

快速回答

根据您的用例和预测的查询模式,我会为您推荐以下内容之一 table:

PRIMARY KEY (Stock_Symbol, Timestamp)

分区键由Stock_Symbol组成,Timestamp是唯一的聚簇列。这将允许 WHERE 与这两个字段一起使用。如果要过滤其中任何一个,则查询中将需要过滤 Stock_Symbol,并且必须作为 WHERE.

的第一个条件

或者,对于您列出的第二种情况:

PRIMARY KEY ((Exchange_ID, Stock_Symbol), Timestamp)

分区键由Exchange_IDStock_Symbol组成,Timestamp是唯一的聚簇列。这将允许 WHERE 与这三个字段一起使用。如果要过滤这三个中的任何一个,查询中将需要过滤 Exchange_IDStock_Symbol,并且必须按照 WHERE 的前两个条件的顺序出现。

请参阅此答案的最后一部分,了解也可以根据您的需要应用的其他一些变体。

长答案和解释

主键、分区键和集群列

Cassandra 中的主键,类似于它们在关系数据库中的作用,用于识别记录并为它们建立索引以便快速访问它们。但是,由于 Cassandra 中记录的分布式特性,它们的次要目的是 确定给定记录应存储在哪个节点上。

Cassandra table 中的主键进一步分为两部分 - Partition Key, which is mandatory and by default is the first column in the primary key, and optional clustering column(s),它们是主键中不属于分区键的所有字段.

这里有一些例子:

PRIMARY KEY (Exchange_ID)

Exchange_ID是主键中唯一的字段,也是分区键。没有额外的聚类列。

PRIMARY KEY (Exchange_ID, Timestamp, Stock_Symbol)

Exchange_IDTimestampStock_Symbol共同构成复合主键。分区键是 Exchange_ID 并且 TimestampStock_Symbol 都是聚类列。

PRIMARY KEY ((Exchange_ID, Timestamp), Stock_Symbol)

Exchange_IDTimestampStock_Symbol共同构成复合主键。分区键由Exchange_IDTimestamp组成。额外的括号分组 Exchange_IDTimestamp 将它们分组为单个复合分区键,并且 Stock_Symbol 是一个集群列。

PRIMARY KEY ((Exchange_ID, Timestamp))

Exchange_IDTimestamp 一起构成复合主键。分区键由Exchange_IDTimestamp组成。没有聚簇列。

但他们是做什么的?

在内部,分区键用于计算令牌,该令牌确定记录存储在哪个节点上。集群列不用于确定将记录存储在哪个节点上,但它们用于确定记录在节点内的布局顺序 - 这在查询一系列记录时很重要。聚类列值相似的记录将在同一节点上彼此靠近存储;他们“聚集”在一起。

在 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,或者更具体地说,在本例中, table.

的主键

您提到 Stock_Symbol 将拥有大约 30k 个唯一值,而且它始终包含在 WHERE 子句中。这听起来最初像是一个合理的分区键候选者,只要查询只包含他们在 Stock_Symbol 中搜索的单个值(例如 WHERE Stock_Symbol = 'value' 而不是 WHERE Stock_Symbol < 'value')。如果查询的目的是return Stock_Symbol 中具有多个值的多条记录,则存在集群需要从多个节点检索数据的危险,这可能会导致性能下降。

此外,如果您的用户希望在 Timestamp 上进行过滤,它也应该是主键的一部分,尽管我想在范围内进行过滤表明它可能不应该是主键的一部分的分区键,因此它很适合作为聚簇列。

这引出了我的建议:

PRIMARY KEY (Stock_Symbol, Timestamp)

如果基于 Stock_SymbolTimestamp 分发数据很重要,您可以引入一个基于时间但基数较小的预计算时间段字段,例如 Day_Of_WeekMonth 或类似的东西:

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)