MeP*_*uck 3 database postgresql uuid cassandra
我需要将一个表从 Cassandra 迁移到 PostgreSQL。
我需要迁移的内容:该表有一个 TimeUUID 列,用于将时间存储为 UUID。此列还用作聚类键。时间存储为 UUID,以避免在同一毫秒内插入行时发生冲突。此外,此列通常包含在 where 子句中,timeUUID between 'foo' and 'bar'
并且会产生正确的结果。
我需要将它迁移到哪里:我要迁移到 Postgres,因此需要找到合适的替代方案。PostgreSQL 具有 UUID 数据类型,但从我到目前为止所阅读和尝试的内容来看,它将其存储为 4 字节 int,但是当在具有关系运算符的 where 子句中使用时,它将 UUID 处理为类似于 String。
select * from table where timeUUID > 'foo'
xyz
结果中会有。
根据我的理解,UUID 甚至 TimeUUID 都没有必要一直增加。因此,与具有相同数据集的 Cassandra 相比,Postgres 会产生错误的结果。
到目前为止我所考虑的:我考虑将它存储为 BIGINT,但它会受到以毫秒为单位的时间分辨率的冲突。我可以达到 mirco/nano 秒的分辨率,但我担心 BIGINT 会耗尽它。
将 UUID 存储为 CHAR 将防止冲突,但随后我将失去在列上应用关系运算符的能力。
TIMESTAMP 最合适,但我担心时区和冲突。
我到底需要什么(tl;dr):
某种具有更高时间分辨率或避免冲突的方法(生成唯一值)。
该列应支持关系运算符,即
uuid_col < 'uuid_for_some_timestamp'
.
PS:这是一个Java应用程序。
停止用 Cassandra 的术语思考。设计师在他们的设计中做出了一些有缺陷的决定。
? 不要将两者混合。
将两者混合是 Cassandra 的缺陷。
不幸的是,Cassandra 滥用了 UUID。你的困境表明他们的做法是不幸的愚蠢。
UUID 的目的是严格地生成标识符,而无需像序列号等其他方法那样需要与中央机构协调。
Cassandra 使用Version 1 UUIDs,它取当前时刻,加上一个任意的小数字,并与发布计算机的MAC 地址结合。所有这些数据构成了UUID 中128 位的大部分。
Cassandra 做出了一个糟糕的设计决定,即提取该时刻用于时间跟踪,这违反了 UUID 设计的意图。UUID 从未打算用于时间跟踪。
UUID 标准中有几个替代版本。这些替代方案不一定包含某个时刻。例如,第 4 版 UUID改为使用从加密强生成器生成的随机数。
如果要生成版本 1 UUID,请安装通常与 Postgres 捆绑在一起的uuid-ossp插件(“扩展”)(包装OSSP uuid库)。该插件提供了几个函数,您可以调用它来生成 UUID 值。
[Postgres] 将其存储为 4 字节 int
Postgres 将 UUID 定义为原生数据类型。因此,如何存储这些值实际上与我们无关,并且可能会在 Postgres 的未来版本(或其新的可插拔存储方法)中发生变化。你传入一个 UUID,你会得到一个 UUID,这就是我们作为 Postgres 用户所知道的一切。作为奖励,了解 Postgres(在其当前的“堆”存储方法中)将 UUID 值有效地存储为 128 位是很好的,而不是低效的,例如,存储规范地用于显示 UUID 的十六进制字符串的文本对人类。
请注意,Postgres 内置支持存储UUID 值,而不是生成UUID 值。生成值:
要了解更多信息,请参阅:在 Postgres 中为 Insert 语句生成 UUID?
至于你的迁移,我建议“说实话”作为一种普遍的好方法。日期时间值应存储在具有适当标记名称的日期类型列中。标识符应存储在具有适当标记名称的适当类型(通常是整数类型或 UUID)的主键列中。
所以不要再玩 Cassandra 玩的那些愚蠢而聪明的游戏了。
提取日期时间值,将其存储在日期时间列中。Postgres 具有出色的日期时间支持。具体来说,您需要将值存储在 SQL 标准类型的列中TIMESTAMP WITH TIME ZONE
。这种数据类型代表一个时刻,时间线上的一个特定点。
Java 中用于表示时刻的等效类型是Instant
orOffsetDateTime
或ZonedDateTime
。JDBC 4.2 规范要求仅支持第二个,莫名其妙,而不是第一个或第三个。搜索 Stack Overflow 以获取更多此类 Java 和 JDBC 信息,因为它已被多次介绍。
继续使用 UUID,但仅用作 Postgres 中新表的指定主键列。您可以告诉 Postgres 自动生成这些值。
将 UUID 存储为 CHAR
不,不要将 UUID 存储为文本。
TIMESTAMP 最合适,但我担心时区和冲突。
TIMESTAMP WITH TIME ZONE
和之间有天壤之别TIMESTAMP WITHOUT TIME ZONE
。所以永远不要只说时间戳。
Postgres 总是TIMESTAMP WITH TIME ZONE
以 UTC格式存储 a 。包含在提交值中的任何时区或偏移量信息都用于调整为 UTC,然后被丢弃。Java 检索这种类型的值作为 UTC。所以没问题。
当使用其他工具时会出现问题,这些工具具有善意但有严重缺陷的功能,即在生成文本以显示字段值的同时动态应用默认时区。从 Postgres 检索的值总是在 UCT 中,但它的表示可能已调整为另一个偏移量或区域。要么避免使用此类工具,要么确保将默认区域设置为 UTC 本身。所有程序员、DBA 和系统管理员都应该学习在工作中使用 UTC 工作和思考。
TIMESTAMP WITHOUT TIME ZONE
是完全不同的。此类型缺少时区或 UTC 偏移量的上下文。所以这种类型不能代表片刻。它包含一个日期和一个时间,但仅此而已。这当然是模棱两可的。如果该值是今年 1 月 23 日的中午,我们不知道您指的是东京的中午、德黑兰的中午还是托莱多的中午——所有的时间都非常不同,相隔几个小时。Java 中的等效类型是LocalDateTime
. 搜索 Stack Overflow 以了解更多信息。
时间存储为 UUID,以避免在同一毫秒内插入行时发生冲突。
如果主机硬件时钟可以这样做,则版本 1 UUID 轨道和时间的分辨率可达 100 纳秒(1/10 微秒)。该java.time类捕获时间和微秒的分辨率(如Java 9及更高版本)。Postgres 以微秒的分辨率存储时刻。因此,使用 Java 和 Postgres,您将在这方面接近 Cassandra。
存储当前时刻。
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
Run Code Online (Sandbox Code Playgroud)
恢复。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Run Code Online (Sandbox Code Playgroud)
我可以达到 mirco/nano 秒的分辨率
你不能。今天的传统计算机时钟无法以纳秒为单位精确跟踪时间。
仅使用时间跟踪作为标识符值是一个有缺陷的想法。
UUID 甚至 TimeUUID 没有必要总是增加
你永远不能指望时钟总是在增加。时钟得到调整和重置。计算机硬件时钟不是那么准确。不了解计算机时钟的局限性是 Cassandra 设计的天真和不合理的方面之一。
这就是版本 1 UUIDclock sequence
与当前时刻一起使用任意小数字(称为)的原因,因为当时钟重置/调整时,当前时刻可能会重复。一个负责任的 UUID 实现预计会注意到时钟回落,然后增加这个小数字以补偿和避免重复。根据 RFC 4122 第 4.1.5 节:
对于 UUID 版本 1,时钟序列用于帮助避免在时钟向后设置或节点 ID 更改时可能出现的重复。
如果时钟向后设置,或者可能已经向后设置(例如,当系统断电时),并且 UUID 生成器无法确定没有生成时间戳大于时钟设置值的 UUID,那么时钟序列必须改变。如果已知时钟序列的先前值,则可以将其递增;否则应设置为随机或高质量的伪随机值。
有没有在UUID规范,承诺要“始终不断增加”。回到我的开场白,Cassandra 滥用 UUID。