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



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

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

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

性能要求:

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

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

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

好的,我在这个问题后面想了一些。这种情况下有几件事很棘手:

  • 序列号进入并分配(但不是立即分配)。随着每年100万的增长,每天大约有2800个新的序列号。将这些人排队(当他们进来时将其打乱,当他们被分配时将其删除)将产生大量的墓碑(基本上每天2800个)。

  • 50个UPC与1000万个序列号有关,每个UPC的序列号为20万个(假设分布均匀)。这意味着我们不能在集合中存储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进行密钥设置。但是timeuuid是唯一的(因此在serialNumbers上不会发生冲突),并且通过insertTime进行集群还有一个额外的好处,即允许您按日期/时间进行排序。当然,在为UPC分配序列号时,您需要确保向这两个表追加序列号。

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

当然,您可以决定不理会上述警告,并坚持使用Cassandra来实现该功能。如果是这样,那么就是我在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,但您可以使用任何大小的bucket(例如:"20150416"将把2015年4月16日输入的序列号分割在一起)。

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

  • 我在insertTime上集群的原因与在serialNumbersByUPC表中集群的原因相同。

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

  • CCD_ 12被设置为1天而不是10天。这将强制每天对逻辑删除的行进行垃圾收集。这个设置的缺点是,如果你有一个节点关闭,你需要在它关闭的1天内把它带回来,以进行删除。如果你不这样做,你将需要进行全面修复,或者冒着被删除的序列号"死而复生"的风险

您还需要了解DateTieredCompactionStrategy。它可能还有一些其他选项,对你来说可能是有意义的。

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

这个怎么样:

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

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

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

要获取一个序列号来尝试插入,您可以从相应的未分配UPC分区中选择一个限制为100的分区,然后随机选择一个返回的序列号(这样,如果您有多个进程,它们在尝试插入时不会都尝试相同的编号)。分区内的限制100将非常快。

现在,要获得未分配数字的计数,可以对未分配表中的每个UPC分区进行选择计数(*),但如果行太多,则可能会超时。因此,您可以有一个第三个表,其中包含计数器列,并在添加和使用序列号时递增和递减这些列。

最新更新