支持关系操作的PostgreSQL中Cassandra的TimeUUID的替代
Alternative to Cassandra's TimeUUID in PostgreSQL that supports relational operations
我需要将 table 从 Cassandra 迁移到 PostgreSQL。
我需要迁移的内容: table 有一个 TimeUUID 列,用于将时间存储为 UUID。此列还用作聚类键。时间存储为 UUID,以避免在同一毫秒内插入行时发生冲突。此外,此列涉及 where 子句,通常是 timeUUID between 'foo' and 'bar'
,它产生了正确的结果。
我需要将其迁移到的地方: 我要迁移到 Postgres,因此需要找到一个 suitable 替代方案。 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 TimeUUID 是版本 1 UUID,而 Postgres 生成版本 4 UUID。您也可以在 Postgres 中生成 V1:
https://www.postgresql.org/docs/11/uuid-ossp.html
我使用 pg_crypto 作为 UUID,但它只生成 V4。
其他人可以说得更权威,但我记得 Postgres 中 128-bit/16-byte 类型的 UUID 不容易转换为数字。您可以将它们转换为文本甚至二进制字符串:
SELECT DECODE(REPLACE(id::text, '-',''), 'hex') 来自 foo;
我无法想象这是一个超级快或好主意...
根据您的说法,您的问题与按时间戳元素排序有关。我相信 Ancoron Luciferis 一直在研究这个问题。你可以在这里找到他的一些测试结果:
https://github.com/ancoron/pg-uuid-test
在 Postgres 中,序列号 "types" 是用于唯一序列号的标准特征。因此,在您所说的中,BIGSERIAL 而不是 BIGINT。时间戳列很棒(也是 8 个字节),但不太适合唯一 ID。在我们的设置中,我们使用 V4 UUID 作为合成键,使用 timestamptz 字段作为时间戳。所以,我们有两列而不是一列。 (Postgres 是这里许多不同数据源的集中式收集器,这就是我们使用 UUID 而不是串行计数器的原因,顺便说一句。)就个人而言,我喜欢 是 时间戳的时间戳,因为它们在不同的粒度级别上更容易使用、推理和搜索。加!您可能会利用 Postgres amazing BRIN 索引类型:
https://www.postgresql.fastware.com/blog/brin-indexes-what-are-they-and-how-do-you-use-them
tl;博士
停止用 Cassandra 术语思考。设计师在他们的设计中做出了一些有缺陷的决定。
- 使用UUID as an identifier.
- 使用日期时间类型来跟踪时间。
➥ 不要将两者混用。
将两者混为一谈是 Cassandra 的缺陷。
Cassandra 滥用 UUID
不幸的是,Cassandra 滥用 UUID。你的困境表明他们的做法是不幸的愚蠢。
UUID 的目的严格来说是为了生成一个标识符,而不需要像序列号等其他方法那样需要与中央机构协调。
Cassandra 在 UUID 中使用 Version 1 UUIDs, which take the current moment, plus an arbitrary small number, and combine with the MAC address of the issuing computer. All this data goes to make up most of the 128 bits。
Cassandra 做出了糟糕的设计决定,即及时提取该时刻用于时间跟踪,这违反了 UUID 设计的意图。 UUID 从未打算用于时间跟踪。
UUID 标准中有多个可供选择的版本。这些备选方案不一定包含时间片刻。例如,Version 4 UUIDs 改为使用从加密强度高的生成器生成的随机数。
如果您想生成版本 1 UUID,请安装通常与 Postgres 捆绑在一起的 uuid-ossp plugin (“extension”) (wrapping the OSSP uuid 库。该插件提供了几个函数,您可以调用这些函数来生成 UUID 值。
[Postgres] stores it as 4-byte int
Postgres 将 UUID 定义为本机数据类型。因此,如何存储这些值实际上是 none 我们的业务,并且可能会在 Postgres 的未来版本(或其新的可插入存储方法)中发生变化。你传入一个 UUID,你会得到一个 UUID,这就是我们作为 Postgres 用户所知道的全部。作为奖励,很高兴了解到 Postgres(在其当前的“堆”存储方法中)将 UUID 值有效地存储为 128 位,而不是低效的,例如,存储用于显示 UUID 的十六进制字符串的文本对人类。
请注意,Postgres 内置支持存储 UUID 值,而不是生成 UUID 值。生成值:
- 一些人使用 pgcrypto 扩展,如果已经安装在他们的数据库中的话。该插件只能生成版本 4 几乎所有随机的 UUID。
- 我建议您改为使用 uuid-ossp 扩展名。这为您提供了多种 UUID 版本供您选择。
要了解更多信息,请参阅:Generating a UUID in Postgres for Insert statement?
至于你的迁移,我建议“说实话”作为一个普遍的好方法。日期时间值应存储在具有适当标签名称的日期类型列中。标识符应存储在具有适当标签名称的适当类型(通常是整数类型或 UUID)的主键列中。
所以停止玩 Cassandra 玩的愚蠢聪明的游戏。
提取日期时间值,将其存储在日期时间列中。 Postgres 具有出色的日期时间支持。具体来说,您需要将值存储在 SQL 标准类型 TIMESTAMP WITH TIME ZONE
的列中。这种数据类型代表一个时刻,时间轴上的一个特定点。
Java 中表示时刻的等效类型为 Instant
或 OffsetDateTime
或 ZonedDateTime
。 JDBC 4.2 规范只需要支持第二个,莫名其妙,而不是第一个或第三个。搜索 Stack Overflow 以获取更多 Java 和 JDBC 信息,因为它已经被覆盖了很多次。
继续使用 UUID,但 仅 作为 Postgres 中新 table 的指定主键列。您可以告诉 Postgres 自动生成这些值。
Storing UUID as CHAR
不,不要将 UUID 存储为文本。
TIMESTAMP fits the best but I'm worried about timezone and collisions.
TIMESTAMP WITH TIME ZONE
和 TIMESTAMP WITHOUT TIME ZONE
之间有天壤之别。所以永远不要只说时间戳。
Postgres 始终以 UTC 格式存储 TIMESTAMP WITH TIME ZONE
。提交值中包含的任何时区或偏移量信息都用于调整为 UTC,然后被丢弃。 Java 将此类型的值检索为 UTC。所以没问题。
当使用其他工具时会出现问题,这些工具具有在生成文本以显示字段值时动态应用默认时区的善意但不幸的缺陷特征。从 Postgres 检索的值在 UCT 中是 always,但它的 presentation 可能已被调整到另一个偏移量或区域。要么避免使用此类工具,要么确保将默认时区设置为 UTC 本身。所有程序员、DBA 和系统管理员都应该在工作中学习使用 UTC 工作和思考。
TIMESTAMP WITHOUT TIME ZONE
完全不同。此类型缺少时区或与 UTC 的偏移量的上下文。所以这个类型不能表示一个时刻。它包含一个日期和一天中的时间,仅此而已。这当然是模棱两可的。如果值是今年 1 月 23 日的中午,我们不知道你指的是东京的中午、德黑兰的中午还是托莱多的中午——所有这些时间都非常不同,相隔几个小时。相当于
输入 Java 是 LocalDateTime
。搜索 Stack Overflow 以了解更多信息。
Time was stored as UUID to avoid collisions when rows are inserted in the same millisecond.
版本 1 UUID 轨道和时间分辨率可达 100 纳秒(1/10 微秒),如果主机硬件时钟可以的话。 java.time 类 捕获时间,分辨率为微秒(自 Java 9 起及更高版本)。 Postgres 以微秒的分辨率存储时刻。因此,使用 Java 和 Postgres,您将在这方面接近 Cassandra。
存储当前时刻。
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
I can go for resolution of mirco/nano seconds
不,你不能。今天的传统计算机时钟无法精确地跟踪以纳秒为单位的时间。
并且仅将时间跟踪用作标识符值是一个有缺陷的想法。
it is not necessary for UUID or even TimeUUID to be always increasing
你可以永远不会指望一个总是在增加的时钟。时钟得到调整和重置。计算机硬件时钟不是那么准确。不理解计算机时钟的局限性是 Cassandra 设计的幼稚和不合理的方面之一。
这就是版本 1 UUID 使用任意小数字(称为 clock sequence
)和当前时刻的原因,因为当时钟变为 reset/adjusted 时,当前时刻可能会重复。一个负责任的 UUID 实现应该注意到时钟回落,然后增加那个小数字来补偿和避免重复。根据 RFC 4122 第 4.1.5 节:
For UUID version 1, the clock sequence is used to help avoid duplicates that could arise when the clock is set backwards in time or if the node ID changes.
If the clock is set backwards, or might have been set backwards
(e.g., while the system was powered off), and the UUID generator can
not be sure that no UUIDs were generated with timestamps larger than
the value to which the clock was set, then the clock sequence has to
be changed. If the previous value of the clock sequence is known, it
can just be incremented; otherwise it should be set to a random or
high-quality pseudo-random value.
UUID specifications 中没有任何东西承诺“永远增加”。回到我的开场白,Cassandra 滥用 UUID。
我需要将 table 从 Cassandra 迁移到 PostgreSQL。
我需要迁移的内容: table 有一个 TimeUUID 列,用于将时间存储为 UUID。此列还用作聚类键。时间存储为 UUID,以避免在同一毫秒内插入行时发生冲突。此外,此列涉及 where 子句,通常是 timeUUID between 'foo' and 'bar'
,它产生了正确的结果。
我需要将其迁移到的地方: 我要迁移到 Postgres,因此需要找到一个 suitable 替代方案。 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 TimeUUID 是版本 1 UUID,而 Postgres 生成版本 4 UUID。您也可以在 Postgres 中生成 V1:
https://www.postgresql.org/docs/11/uuid-ossp.html
我使用 pg_crypto 作为 UUID,但它只生成 V4。
其他人可以说得更权威,但我记得 Postgres 中 128-bit/16-byte 类型的 UUID 不容易转换为数字。您可以将它们转换为文本甚至二进制字符串:
SELECT DECODE(REPLACE(id::text, '-',''), 'hex') 来自 foo;
我无法想象这是一个超级快或好主意...
根据您的说法,您的问题与按时间戳元素排序有关。我相信 Ancoron Luciferis 一直在研究这个问题。你可以在这里找到他的一些测试结果:
https://github.com/ancoron/pg-uuid-test
在 Postgres 中,序列号 "types" 是用于唯一序列号的标准特征。因此,在您所说的中,BIGSERIAL 而不是 BIGINT。时间戳列很棒(也是 8 个字节),但不太适合唯一 ID。在我们的设置中,我们使用 V4 UUID 作为合成键,使用 timestamptz 字段作为时间戳。所以,我们有两列而不是一列。 (Postgres 是这里许多不同数据源的集中式收集器,这就是我们使用 UUID 而不是串行计数器的原因,顺便说一句。)就个人而言,我喜欢 是 时间戳的时间戳,因为它们在不同的粒度级别上更容易使用、推理和搜索。加!您可能会利用 Postgres amazing BRIN 索引类型:
https://www.postgresql.fastware.com/blog/brin-indexes-what-are-they-and-how-do-you-use-them
tl;博士
停止用 Cassandra 术语思考。设计师在他们的设计中做出了一些有缺陷的决定。
- 使用UUID as an identifier.
- 使用日期时间类型来跟踪时间。
➥ 不要将两者混用。
将两者混为一谈是 Cassandra 的缺陷。
Cassandra 滥用 UUID
不幸的是,Cassandra 滥用 UUID。你的困境表明他们的做法是不幸的愚蠢。
UUID 的目的严格来说是为了生成一个标识符,而不需要像序列号等其他方法那样需要与中央机构协调。
Cassandra 在 UUID 中使用 Version 1 UUIDs, which take the current moment, plus an arbitrary small number, and combine with the MAC address of the issuing computer. All this data goes to make up most of the 128 bits。
Cassandra 做出了糟糕的设计决定,即及时提取该时刻用于时间跟踪,这违反了 UUID 设计的意图。 UUID 从未打算用于时间跟踪。
UUID 标准中有多个可供选择的版本。这些备选方案不一定包含时间片刻。例如,Version 4 UUIDs 改为使用从加密强度高的生成器生成的随机数。
如果您想生成版本 1 UUID,请安装通常与 Postgres 捆绑在一起的 uuid-ossp plugin (“extension”) (wrapping the OSSP uuid 库。该插件提供了几个函数,您可以调用这些函数来生成 UUID 值。
[Postgres] stores it as 4-byte int
Postgres 将 UUID 定义为本机数据类型。因此,如何存储这些值实际上是 none 我们的业务,并且可能会在 Postgres 的未来版本(或其新的可插入存储方法)中发生变化。你传入一个 UUID,你会得到一个 UUID,这就是我们作为 Postgres 用户所知道的全部。作为奖励,很高兴了解到 Postgres(在其当前的“堆”存储方法中)将 UUID 值有效地存储为 128 位,而不是低效的,例如,存储用于显示 UUID 的十六进制字符串的文本对人类。
请注意,Postgres 内置支持存储 UUID 值,而不是生成 UUID 值。生成值:
- 一些人使用 pgcrypto 扩展,如果已经安装在他们的数据库中的话。该插件只能生成版本 4 几乎所有随机的 UUID。
- 我建议您改为使用 uuid-ossp 扩展名。这为您提供了多种 UUID 版本供您选择。
要了解更多信息,请参阅:Generating a UUID in Postgres for Insert statement?
至于你的迁移,我建议“说实话”作为一个普遍的好方法。日期时间值应存储在具有适当标签名称的日期类型列中。标识符应存储在具有适当标签名称的适当类型(通常是整数类型或 UUID)的主键列中。
所以停止玩 Cassandra 玩的愚蠢聪明的游戏。
提取日期时间值,将其存储在日期时间列中。 Postgres 具有出色的日期时间支持。具体来说,您需要将值存储在 SQL 标准类型 TIMESTAMP WITH TIME ZONE
的列中。这种数据类型代表一个时刻,时间轴上的一个特定点。
Java 中表示时刻的等效类型为 Instant
或 OffsetDateTime
或 ZonedDateTime
。 JDBC 4.2 规范只需要支持第二个,莫名其妙,而不是第一个或第三个。搜索 Stack Overflow 以获取更多 Java 和 JDBC 信息,因为它已经被覆盖了很多次。
继续使用 UUID,但 仅 作为 Postgres 中新 table 的指定主键列。您可以告诉 Postgres 自动生成这些值。
Storing UUID as CHAR
不,不要将 UUID 存储为文本。
TIMESTAMP fits the best but I'm worried about timezone and collisions.
TIMESTAMP WITH TIME ZONE
和 TIMESTAMP WITHOUT TIME ZONE
之间有天壤之别。所以永远不要只说时间戳。
Postgres 始终以 UTC 格式存储 TIMESTAMP WITH TIME ZONE
。提交值中包含的任何时区或偏移量信息都用于调整为 UTC,然后被丢弃。 Java 将此类型的值检索为 UTC。所以没问题。
当使用其他工具时会出现问题,这些工具具有在生成文本以显示字段值时动态应用默认时区的善意但不幸的缺陷特征。从 Postgres 检索的值在 UCT 中是 always,但它的 presentation 可能已被调整到另一个偏移量或区域。要么避免使用此类工具,要么确保将默认时区设置为 UTC 本身。所有程序员、DBA 和系统管理员都应该在工作中学习使用 UTC 工作和思考。
TIMESTAMP WITHOUT TIME ZONE
完全不同。此类型缺少时区或与 UTC 的偏移量的上下文。所以这个类型不能表示一个时刻。它包含一个日期和一天中的时间,仅此而已。这当然是模棱两可的。如果值是今年 1 月 23 日的中午,我们不知道你指的是东京的中午、德黑兰的中午还是托莱多的中午——所有这些时间都非常不同,相隔几个小时。相当于
输入 Java 是 LocalDateTime
。搜索 Stack Overflow 以了解更多信息。
Time was stored as UUID to avoid collisions when rows are inserted in the same millisecond.
版本 1 UUID 轨道和时间分辨率可达 100 纳秒(1/10 微秒),如果主机硬件时钟可以的话。 java.time 类 捕获时间,分辨率为微秒(自 Java 9 起及更高版本)。 Postgres 以微秒的分辨率存储时刻。因此,使用 Java 和 Postgres,您将在这方面接近 Cassandra。
存储当前时刻。
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
I can go for resolution of mirco/nano seconds
不,你不能。今天的传统计算机时钟无法精确地跟踪以纳秒为单位的时间。
并且仅将时间跟踪用作标识符值是一个有缺陷的想法。
it is not necessary for UUID or even TimeUUID to be always increasing
你可以永远不会指望一个总是在增加的时钟。时钟得到调整和重置。计算机硬件时钟不是那么准确。不理解计算机时钟的局限性是 Cassandra 设计的幼稚和不合理的方面之一。
这就是版本 1 UUID 使用任意小数字(称为 clock sequence
)和当前时刻的原因,因为当时钟变为 reset/adjusted 时,当前时刻可能会重复。一个负责任的 UUID 实现应该注意到时钟回落,然后增加那个小数字来补偿和避免重复。根据 RFC 4122 第 4.1.5 节:
For UUID version 1, the clock sequence is used to help avoid duplicates that could arise when the clock is set backwards in time or if the node ID changes.
If the clock is set backwards, or might have been set backwards (e.g., while the system was powered off), and the UUID generator can not be sure that no UUIDs were generated with timestamps larger than the value to which the clock was set, then the clock sequence has to be changed. If the previous value of the clock sequence is known, it can just be incremented; otherwise it should be set to a random or high-quality pseudo-random value.
UUID specifications 中没有任何东西承诺“永远增加”。回到我的开场白,Cassandra 滥用 UUID。