Cassandra 唯一数据选择和分配的最佳数据模型

Best Data Model for Unique Data Selection and Assignment in Cassandra

对于以下情况,最好的 Cassandra 数据模型和查询是什么?

当每台烤面包机在我们的工厂生产时,我们的系统负责为我们的烤面包机分配唯一的序列号。

性能要求:

我们的数据集现在大约有 1000 万个序列号,每年增长约 100 万个。

我们目前使用的是 Cassandra 2.0.x,很快就会迁移到 2.1.x。

这个怎么样:

有一个 table 包含未分配的序列号,称之为 "unassigned"。当您获得新的序列号时,它们将被插入 "unassigned"。 UPC 编号将是分区键,序列号可以是聚簇列。

然后有另一个 table 叫作 "assigned"。当你构建一个烤面包机时,你从未分配的 table 中随机获取一个序列号,并尝试使用插入上的 "IF NOT EXISTS" 子句将其插入已分配的 table 中。如果您有多个进程,这可确保仅分配一次序列号 运行.

如果插入成功,您的进程将返回并从未分配的 table 中删除序列号。如果由于号码已经存在而导致插入失败,您可以随机选择一个不同的号码并尝试那个号码,直到您获得一个成功的号码。

要获取一个序列号来尝试插入,您可以从适当的未分配 UPC 分区中执行类似 select 的操作,限制为 100,然后随机选择您返回的序列号之一(这样一来,如果您有多个进程,它们将不会在插入尝试时都尝试相同的数字)。而且一个分区内限制100个会很快

现在要计算未分配的数字,您可以对未分配的 table 中的每个 UPC 分区执行 select 计数 (*),但是如果行太多,则可能暂停。所以你可以有第三个 table 带有计数器列,并且随着序列号的添加和使用而增加和减少它们。

好吧,我想了想这件事。这个场景有一些棘手的地方:

  • 序列号进来,并被分配(但不是马上)。随着每年 100 万个的增长,每天大约有 2800 个新序列号。将它们排队(当它们进来时更新它们并在分配它们时删除它们)将创建大量墓碑(基本上每天 2800 个)。

  • 50 个 UPC 对应 1000 万个序列号,每个 UPC 有 20 万个序列号(假设均匀分布)。这意味着我们无法在集合中存储 UPC 到序列号的关系(最大大小为 65536 项)。

我假设您希望能够弄清楚哪些序列号与哪些型号相关联,以及哪些型号具有哪些序列号。为此,我会进行两次查找 tables:

CREATE TABLE serialNumbersByUPC (
  modelUPC uuid,
  insertTime timeuuid,
  serialNumber text,
  PRIMARY KEY (modelUPC,insertTime))
WITH CLUSTERING ORDER BY (insertTime DESC);

CREATE TABLE UPCsBySerialNumbers (
  modelUPC uuid
  insertTime timeuuid,
  serialNumber text,
  PRIMARY KEY (serialNumber));

请注意,您还可以将 serialNumbersByUPCserialNumber 作为聚类键(而不是 insertTime)。但是 timeuuid 是唯一的(因此 serialNumbers 上不会发生冲突),并且 insertTime 的聚类具有允许您按 date/time 排序的额外好处。当然,在将序列号分配给 UPC 时,您需要确保对这两个 table 进行更新。

对于未分配的序列号,最好使用像HornetQ or RabbitMQ这样的排队系统。这样您就可以从队列中取出新的序列号,并根据需要分配它们。我提出这个建议的原因是,使用 Cassandra 排队瞬态数据已被确定为反模式。

当然,您可以决定不理会上述警告,并坚持使用 Cassandra 来实现该功能。如果是这样,那么 this 就是我在 Cassandra 中存储未分配序列号的方式:

CREATE TABLE unassignedSerialNumbers (
  dateBucket bigint,
  serialNumber text,
  insertTime timeuuid,
  PRIMARY KEY ((dateBucket),insertTime))
WITH compaction = {'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy'}
AND gc_grace_seconds = 86400;

关于此解决方案的几点说明:

  • 我在 datebucket 上进行分区,因为我不确定您分配每天收到的 2800 个序列号的速度有多快。您可能只想查询今天或昨天收到的数字。我将其创建为 bigint,但您可以使用任意大小的存储桶(例如:“20150416”会将 2015 年 4 月 16 日进入的序列号一起划分)。

  • 如果您发现分配序列号的速度足够快,不需要按 datebucket 进行分区,那么我就不会担心 table 变大了足以阻碍查询性能。当然,您的删除会创建您查询时必须处理的逻辑删除,但这应该对我的最后两点有所帮助。

  • 出于与 serialNumbersByUPC table.

  • 相同的原因,我在 insertTime 上进行了聚类
  • 我为此 table 使用 DateTieredCompactionStrategy。此策略会将同时写入的行保存在磁盘上的相同 SSTABLE 文件中。当您删除和写入新数据时,这对性能很重要。

  • gc_grace_seconds 被设置为 1 天而不是 10 天。这将强制每天对逻辑删除行进行垃圾收集。此设置的缺点是,如果您有一个节点出现故障,您需要在它出现故障后的 1 天内将其恢复以恢复删除。如果不这样做,您将需要 运行 进行全面维修,否则将面临删除序列号的风险 "coming back from the dead."

您还需要阅读 DateTieredCompactionStrategy。可能还有一些其他选项可能对您设置有意义。

如果您有任何问题,或者我遗漏了什么,请告诉我。