对于以下情况,最好的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分区进行选择计数(*),但如果行太多,则可能会超时。因此,您可以有一个第三个表,其中包含计数器列,并在添加和使用序列号时递增和递减这些列。