Cassandra 唯一数据选择和分配的最佳数据模型
Best Data Model for Unique Data Selection and Assignment in Cassandra
对于以下情况,最好的 Cassandra 数据模型和查询是什么?
当每台烤面包机在我们的工厂生产时,我们的系统负责为我们的烤面包机分配唯一的序列号。
- 我们有多种型号的烤面包机,每种型号都由不同的 UPC 唯一标识。
- 一个序列号永远不会分配给一个以上的烤面包机型号(特定于 UPC)。
- 按顺序分配序列号并不重要。
- 每天至少一次,我们需要查明每个 UPC 还有多少未分配的序列号。
- 我们的系统不时由另一个系统通过其 UPC 大批量提供序列号。
性能要求:
- 为 UPC 查找未分配的序列号必须很快。
- 插入新序列号不需要快。
- 计数 不需要 很快。
我们的数据集现在大约有 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));
请注意,您还可以将 serialNumbersByUPC
与 serialNumber
作为聚类键(而不是 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。可能还有一些其他选项可能对您设置有意义。
如果您有任何问题,或者我遗漏了什么,请告诉我。
对于以下情况,最好的 Cassandra 数据模型和查询是什么?
当每台烤面包机在我们的工厂生产时,我们的系统负责为我们的烤面包机分配唯一的序列号。
- 我们有多种型号的烤面包机,每种型号都由不同的 UPC 唯一标识。
- 一个序列号永远不会分配给一个以上的烤面包机型号(特定于 UPC)。
- 按顺序分配序列号并不重要。
- 每天至少一次,我们需要查明每个 UPC 还有多少未分配的序列号。
- 我们的系统不时由另一个系统通过其 UPC 大批量提供序列号。
性能要求:
- 为 UPC 查找未分配的序列号必须很快。
- 插入新序列号不需要快。
- 计数 不需要 很快。
我们的数据集现在大约有 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));
请注意,您还可以将 serialNumbersByUPC
与 serialNumber
作为聚类键(而不是 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. 相同的原因,我在 我为此 table 使用
DateTieredCompactionStrategy
。此策略会将同时写入的行保存在磁盘上的相同 SSTABLE 文件中。当您删除和写入新数据时,这对性能很重要。gc_grace_seconds
被设置为 1 天而不是 10 天。这将强制每天对逻辑删除行进行垃圾收集。此设置的缺点是,如果您有一个节点出现故障,您需要在它出现故障后的 1 天内将其恢复以恢复删除。如果不这样做,您将需要 运行 进行全面维修,否则将面临删除序列号的风险 "coming back from the dead."
insertTime
上进行了聚类
您还需要阅读 DateTieredCompactionStrategy。可能还有一些其他选项可能对您设置有意义。
如果您有任何问题,或者我遗漏了什么,请告诉我。