我需要将一个表从cassandra迁移到postgresql。
我需要迁移的是:该表有一个timeuuid列,用于将时间存储为uuid。此列还充当集群键。时间被存储为uuid,以避免在同一毫秒内插入行时发生冲突。此外,此列通常包含在where子句中 timeUUID between 'foo' and 'bar'
它产生了正确的结果。
我需要把它迁移到哪里:我要去postgres,所以需要找到一个合适的替代方案。postgresql有uuid数据类型,但从我读到的和尝试到目前为止,它存储为4字节int,但它对待uuid类似于使用关系运算符的where子句时的字符串。 select * from table where timeUUID > 'foo'
会有 xyz
在结果中。
根据我的理解,uuid甚至timeuuid没有必要总是增加。因此,postgres产生错误的结果时,与Cassandra与相同的数据集。
到目前为止,我考虑的是:我考虑将它存储为bigint,但对于毫秒级的时间分辨率,它很容易发生冲突。我可以选择分辨率为毫微秒/纳秒,但我担心bigint会耗尽它。
将uuid存储为char可以防止冲突,但是这样我就失去了对列应用关系运算符的能力。
时间戳最合适,但我担心时区和碰撞。
我到底需要什么(tl;医生):
以某种方式获得更高的时间分辨率或避免冲突的方式(唯一值生成)。
列应该支持关系运算符,即 uuid_col < 'uuid_for_some_timestamp'
.
ps:这是一个java应用程序。
2条答案
按热度按时间j9per5c41#
听起来cassandra timeuuid是版本1的uuid,而postgres生成版本4的uuid。你也可以在postgres中生成v1:
https://www.postgresql.org/docs/11/uuid-ossp.html
我对uuid使用pg\u crypto,但它只生成v4。
其他人可以更权威地说,但我记得在postgres中,uuid是由128位/16字节类型组成的,不容易转换成数字。您可以将它们转换为文本,甚至是二进制字符串:
从foo中选择decode(replace(id::text,'-','','','','','hex');
我无法想象这是一个超快速或好主意。。。
从你所说的,你的问题是围绕排序的时间戳元素。安科龙萤光虫一直在研究这个问题,我相信。你可以在这里找到他的一些测试结果:
https://github.com/ancoron/pg-uuid-test
在postgres中,序列“类型”是用于唯一序列号的标准特性。所以,你刚才说的是bigserial而不是bigint。timestamp列很好(也是8字节),但不太适合唯一的id。在我们的设置中,我们使用v4uuids作为合成键,timestampz字段作为时间戳。所以,我们有两列而不是一列(postgres是一个集中的收藏家,收集了很多不同的信息
o4tp2gmn2#
热释光;博士
不要用Cassandra的话来思考。设计师在设计中做了一些有缺陷的决定。
使用uuid作为标识符。
使用日期时间类型跟踪时间。
➥ 不要两者混用。
混合两者是Cassandra的缺陷。
Cassandra·乌伊德
不幸的是,Cassandra滥用uuids。你的困境表明他们的做法是不幸的愚蠢。
uuid的目的是严格地生成一个标识符,而不需要像序列号这样的其他方法所需要的那样与中央机构协调。
cassandra使用版本1的uuids,它取当前时刻加上一个任意小的数字,并与发行计算机的mac地址相结合。所有这些数据将构成uuid中128位的大部分。
cassandra做出了一个可怕的设计决定,即及时提取该时刻以用于时间跟踪,这违反了uuid设计的意图。uuid从未打算用于时间跟踪。
uuid标准中有几个替代版本。这些备选方案不一定包含某个时刻。例如,版本4的uuid使用从加密强大的生成器生成的随机数。
如果要生成版本1的uuid,请安装通常与postgres捆绑在一起的uuid ossp plugin(“extension”)( Package ossp uuid库)。该插件提供了几个函数,您可以调用这些函数来生成uuid值。
[postgres]将其存储为4字节int
postgres将uuid定义为本机数据类型。因此,如何存储这些值实际上与我们无关,可能会在postgres的未来版本(或其新的可插入存储方法)中发生变化。你传入一个uuid,你就会得到一个uuid,这就是我们作为postgres用户所知道的。另一个好处是,postgres(在其当前的“堆”存储方法中)将uuid值有效地存储为128位,而不是低效地存储,例如,将十六进制字符串的文本规范地用于向人类显示uuid。
请注意,postgres内置了对存储uuid值的支持,而不是生成uuid值。要生成值:
有些人使用pgcrypto扩展,如果已经安装在他们的数据库中的话。该插件只能生成版本4几乎所有的随机uuid。
我建议您改用uuid ossp扩展。这使您可以选择各种版本的uuid。
要了解更多信息,请参阅:在postgres中为insert语句生成uuid?
至于你的迁移,我建议“实话实说”是一个很好的方法。日期时间值应存储在具有适当标记名称的日期类型列中。标识符应存储在具有适当标记名称的适当类型(通常是整数类型或uuid)的主键列中。
所以别再玩Cassandra玩的那些愚蠢聪明的游戏了。
提取日期时间值,将其存储在日期时间列中。postgres有很好的日期时间支持。具体来说,您需要将值存储在sql标准类型的列中
TIMESTAMP WITH TIME ZONE
. 此数据类型表示一个时刻,即时间线上的一个特定点。java中表示某个时刻的等效类型是
Instant
或者OffsetDateTime
或者ZonedDateTime
. jdbc4.2规范只要求支持第二个规范,这让人费解,而不是第一个或第三个规范。在堆栈溢出中搜索更多的java和jdbc信息,因为已经讨论过很多次了。继续使用uuid,但只能作为postgres中新表的指定主键列。你可以告诉postgres自动生成这些值。
将uuid存储为char
不,不要将uuid存储为文本。
时间戳最合适,但我担心时区和碰撞。
两者之间有着天壤之别
TIMESTAMP WITH TIME ZONE
以及TIMESTAMP WITHOUT TIME ZONE
. 所以不要只说时间戳。postgres总是存储
TIMESTAMP WITH TIME ZONE
以utc为单位。提交的值中包含的任何时区或偏移量信息都将用于调整为utc,然后丢弃。java检索这种类型的值作为utc。所以没问题。当使用其他工具时,问题就来了,这些工具具有良好的意图,但不幸的是存在缺陷,即在生成文本以显示字段值的同时动态应用默认时区。从postgres检索到的值总是在uct中,但是它的表示形式可能已经调整到另一个偏移量或区域。要么避免使用此类工具,要么确保将默认区域设置为utc本身。所有程序员、DBA和系统管理员在工作时都应该学会用utc工作和思考。
TIMESTAMP WITHOUT TIME ZONE
完全不同。此类型缺少时区或utc偏移量的上下文。所以这个类型不能代表一个时刻。它有日期和时间,但仅此而已。这当然是模棱两可的。如果数值是今年1月23日中午,我们不知道你指的是东京中午、德黑兰中午还是托莱多中午- 完全不同的时刻,相隔几个小时。java中的等效类型是LocalDateTime
. 搜索堆栈溢出以了解更多信息。时间被存储为uuid,以避免在同一毫秒内插入行时发生冲突。
版本1 uuid跟踪和时间,分辨率高达100纳秒(1/10微秒),如果主机硬件时钟可以这样做的话。time类以微秒的分辨率捕获时间(从Java9和更高版本开始)。postgres以微秒的分辨率存储瞬间。因此,在java&postgres中,您将接近于cassandra。
存储当前时刻。
检索。
我可以用米尔科/纳秒的分辨率
不,你不能。如今,传统的计算机时钟无法精确地跟踪纳秒级的时间。
仅仅将时间跟踪作为一个标识符值是一个有缺陷的想法。
uuid甚至timeuuid没有必要总是增加
你永远不能指望时钟总是在增加。时钟得到调整和重置。计算机硬件时钟不是那么精确。不了解计算机时钟的局限性是一个令人困惑的问题ïCassandra的设计有很多不合理的地方。
这就是为什么版本1 uuid使用任意小的数字(称为
clock sequence
)与当前时刻一起,因为当前时刻可能在时钟重置/调整时重复。一个负责任的uuid实现应该注意到时钟的下降,然后增加这个小数字以补偿和避免重复。根据rfc 4122第4.1.5节:对于uuidversion1,时钟序列用于帮助避免在时钟设置向后时或节点id更改时可能出现的重复。
如果时钟被向后设置,或者可能被向后设置(例如,当系统关闭时),并且uuid生成器不能确定没有生成时间戳大于时钟设置值的uuid,那么必须更改时钟序列。如果时钟序列的前一个值是已知的,它可以只是递增;否则,应将其设置为随机或高质量伪随机值。
uuid规范中没有承诺“总是在增加”。回到我的开场白,Cassandra。