【问题标题】:Best Data Model for Unique Data Selection and Assignment in CassandraCassandra 中唯一数据选择和分配的最佳数据模型
【发布时间】:2015-06-19 08:35:02
【问题描述】:

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

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

  • 我们有多种型号的烤面包机,每种型号都由不同的 UPC 唯一标识。
  • 序列号永远不会分配给一个以上的烤面包机型号(特定于 UPC)。
  • 按顺序分配序列号并不重要。
  • 至少每天一次,我们需要找出每个 UPC 仍有多少未分配的序列号。
  • 我们的系统不时由另一个系统的 UPC 大批量提供序列号。

性能要求:

  • 为 UPC 查找未分配的序列号必须很快
  • 插入新的序列号不需要很快。
  • 计数确实不需要很快。

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

我们目前正在使用 Cassandra 2.0.x,并将很快迁移到 2.1.x。

【问题讨论】:

  • 一些明确的问题:UPC 的格式是什么?您有多少个独特的 UPC?
  • 我们目前大约有 50 个,预计每年增长 10-20 个左右。格式是 UPC 实际上是 UUID - 我称它们为 UPC,因为我认为它更容易理解。
  • 我不得不说。这听起来像是非常少量的数据。您使用 Cassandra 的主要动机是什么?是正常运行时间还是多个数据中心?
  • 它实际上是一个更大系统的一部分,其中其他方面受益于 Cassandra 的功能。我们的目标是将这个系统的所有部分迁移到 Cassandra。

标签: cassandra data-modeling datastax denormalization nosql


【解决方案1】:

好的,我对此进行了一些思考。这种情况有几件棘手的事情:

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

  • 如果有 50 个 UPC 与 1000 万个序列号相关,那么每个 UPC 有 200k 个序列号(假设分布均匀)。这意味着我们无法将 UPC 与序列号的关系存储在一个集合中(最大大小为 65536 个项目)。

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

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));

请注意,您还可以使用serialNumber 作为集群键(而不是insertTime)来键入serialNumbersByUPC。但是timeuuids 是独一无二的(所以serialNumbers 上不会发生冲突),并且insertTime 的聚类具有允许您按日期/时间排序的额外好处。当然,在将序列号分配给 UPC 时,您需要确保对这两个表都进行了 upsert。

对于未分配的序列号,最好使用HornetQRabbitMQ 等排队系统。这样您就可以从队列中提取新的序列号,并根据需要分配它们。我建议这样做的原因是,使用 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 进行分区,那么我不会担心该表会变得足够大而影响查询性能。当然,您的删除操作会创建您查询必须处理的墓碑,但这应该有助于我的最后两点。

  • 我在insertTime 上进行聚类,原因与我在serialNumbersByUPC 表中所做的相同。

  • 我正在为这张桌子使用DateTieredCompactionStrategy。此策略将同时写入磁盘上相同的 SSTABLE 文件中的行。当您删除和写入新数据时,这对性能很重要。

  • gc_grace_seconds 被设置为 1 天而不是 10 天。这将强制每天对墓碑行进行垃圾收集。此设置的缺点是,如果您有一个节点宕机,您需要在节点宕机后 1 天内将其恢复以获取删除。如果您不这样做,您将需要进行全面修复,否则您删除的序列号可能会“起死回生”。

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

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

【讨论】:

  • 将 gc_grace_seconds 设置为 1 天是危险的,因为 OP 强调不得将序列号分配两次。此应用程序的流失率极低(每天 2800 个任务),因此不应该担心墓碑堆积。我会在 10 天时保持优雅秒数。如果流失率是每秒 2800,那么我会担心墓碑,但每天它很小。
  • @jimmeyer 好电话。 OP 应该在更改类似内容之前监控墓碑。但是如果有一个类似队列的桌子和墓碑挂了 10 天,你可能会有一个 9:1 的墓碑与活行的比例......这令人担忧。
  • 你是如何计算 9:1 的?如果我们有 1M 未分配和 2800 x 10 天的墓碑,那么只有不到 3% 的数据是墓碑。
  • @JimMeyer 如果我们马上就拥有全年的 1M,那是真的。 OP提到“不时由另一个系统大批量获取序列号”。当然,在不知道那个时间框架是什么或批次有多大的情况下,我在考虑最坏的情况。那将是每天获得 2800 个,并且可能在一天结束之前分配和删除它们。到第 10 天开始时,表中可能只有 2800 行,最多有 25200 个墓碑。
  • 哦,我明白你在说什么,但从绝对意义上讲,我仍然认为墓碑数量微不足道。
【解决方案2】:

这个怎么样:

有一张包含未分配序列号的表,称之为“未分配”。当您获得新的序列号时,它们将被插入“未分配”中。 UPC 号可以是分区键,序列号可以是集群列。

然后有另一个名为“已分配”的表。当您构建一个烤面包机时,您从未分配的表中随机获取一个序列号,并尝试使用插入时的“IF NOT EXISTS”子句将其插入到已分配的表中。这样可以确保序列号只分配一次,以防您有多个进程在运行。

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

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

现在要获取未分配数字的计数,您可以为未分配表中的每个 UPC 分区执行 select count(*),但如果行数过多,则可能会超时。因此,您可以拥有第三个带有计数器列的表,并在添加和使用序列号时对它们进行递增和递减。

【讨论】:

    猜你喜欢
    • 2019-11-20
    • 2021-06-19
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    • 2022-06-30
    • 2021-05-12
    • 2023-04-01
    • 1970-01-01
    相关资源
    最近更新 更多