在这篇文章中,我们探索了在Amazon RDS for PostgreSQL中实现UUIDv7的方法。UUIDv7改进了UUIDv4的随机性,通过使用Unix时间戳来生成序列化的UUID。使用pgtleTrusted Language Extensions for PostgreSQL,可以轻松为PostgreSQL添加UUIDv7支持。我们将在后面详细介绍如何用PL/Rust创建和安装UUIDv7扩展,并对其实现进行了深入分析。
通用唯一标识符 (UUIDs) 是128位的值,旨在无需中央管理便能保证唯一性。这使得它们非常适合用作数据库中的主键,尤其是在分布式系统中。历史上,生成UUID的热门方法之一是使用UUID版本4 (UUIDv4),这种UUID是非顺序的随机生成。虽然UUIDv4在添加应用程序时相对简单,但它的随机性可能会影响数据库的性能。
主键的选择可能会根据工作负载模式影响应用程序的性能。尽管UUIDv4方便用作数据库主键,但其随机性可能导致在访问或插入数据到数据库时表现不佳。理想情况下,我们希望在数据库索引中聚集经常访问的数据,例如B树及其变种。这样可以增加数据正常存在于数据库内存中时的可能性,从而减少从磁盘获取数据的可能性,后者是耗时较长的操作。这个问题被称为“时间局部性”。如果索引中有大量随机分布的数据,可能会对缓存命中率产生负面影响,从而影响整体性能。
解决UUIDv4随机性导致的问题的一种方法是使用一种主键,将常被访问的数据聚集在一起。一种常用的方法是利用时间戳来辅助数据的定位,因为许多应用程序会更频繁地查询在相同时间生成的数据。
UUID版本7UUIDv7的引入旨在改善UUIDv4的随机性。UUIDv7在UUID的前48位编码了带有毫秒级精度的Unix时间戳,这意味着UUIDv7是基于时间和顺序生成的。这消除了使用UUIDv4时时间局部性差的缺点。将数据插入数据库索引是基于它们的创建顺序进行聚合的。类似地,检索与特定时间相关的数据也使得能够轻松访问在该时间附近创建的数据。
今天,PostgreSQL内建支持生成UUID,包括UUIDv1、UUIDv3、UUIDv4和UUIDv5的实现。然而,截止目前,PostgreSQL原生并不支持UUIDv7,但您可以通过使用Trusted Language Extensions for PostgreSQL (pgtle) 来添加UUIDv7支持。此外,假如在未来PostgreSQL支持UUIDv7,这种实现也将兼容旧版和新版PostgreSQL。
pgtle为PostgreSQL提供了一套新的开源开发工具包,帮助用户构建高性能的扩展,并且安全地在PostgreSQL上运行。它允许开发者使用流行的受信语言如PL/Rust、JavaScript、Perl和PL/pgSQL创建高性能的数据库扩展。本文中我们将演示如何使用PL/Rust创建和安装一个UUIDv7的可信语言扩展TLE,并深入探讨其底层实现。
Amazon关系数据库服务Amazon RDS为PostgreSQL支持可信语言扩展和PL/Rust扩展。PL/Rust使开发者能够在Rust编程语言中构建安全和高效的数据库函数。在这个例子中,我们安装一个包含三个PL/Rust函数的UUIDv7 TLE,以涵盖以下用例:
功能描述生成UUIDv7生成一个UUIDv7从用户提供的时间戳生成UUIDv7基于特定时间戳生成UUIDv7从UUIDv7中提取时间戳以PostgreSQL timestamp with time zone (timestamptz) 类型返回时间戳这三个数据库函数被打包在一个TLE中,您可以在安装该TLE后开始使用UUIDv7。
Amazon RDS for PostgreSQL还支持其他几种受信编程语言,包括PL/pgSQL、PL/Perl、PL/v8JavaScript和PL/Tcl。
要运行本文中的示例,您需要先配置一个运行PostgreSQL 161或更高版本、152或更高版本、149或更高版本、或1312或更高版本的RDS for PostgreSQL实例或多可用区数据库集群。另外,您需要将pgtle和plrust添加到数据库参数组中的sharedpreloadlibraries参数,并将该参数组分配给您的PostgreSQL数据库实例。您也可以直接创建带有参数组的PostgreSQL数据库实例。
您可以通过AWS命令行接口AWS CLI创建数据库参数组,并将pgtle和plrust添加到sharedpreloadlibraries参数:
bashREGION=useast1
aws rds createdbparametergroup dbparametergroupname pg16plrust dbparametergroupfamily postgres16 description 包含PostgreSQL 16的PL/Rust设置的参数组 region {REGION}
aws rds modifydbparametergroup dbparametergroupname pg16plrust parameters ParameterName=sharedpreloadlibrariesParameterValue=pgtleplrustApplyMethod=pendingreboot region {REGION}
如果您修改了现有数据库实例的sharedpreloadlibraries参数,改动将在实例重启后生效。您也可以直接通过AWS管理控制台修改参数组。如果您直接创建带有自定义参数组的实例,当实例可用时,您可以开始使用pgtle和plrust。更多信息请参见管理数据库参数组。
在Trusted Language Extensions开源Github库中,我们提供了安装扩展的方法。在将UUIDv7扩展注册到pgtle后,您可以通过以下命令检查所需数据库中是否可用该扩展:
香港加速器下载sqlSELECT FROM pgtleavailableextensions()
您将看到如下输出:
sql name defaultversion comment uuidv7 10 uuid v7的扩展 (1 row)
接下来,在所需数据库中安装plrust扩展,使用以下命令:
sqlCREATE EXTENSION plrust
您将看到如下输出:
sqlCREATE EXTENSION
最后,在所需数据库中安装UUIDv7扩展,使用以下命令:
sqlCREATE EXTENSION uuidv7
您将看到如下输出:
sqlCREATE EXTENSION
现在您可以开始使用uuidv7扩展。
您可以通过以下命令生成UUIDv7:
sqlSELECT generateuuidv7()
您将得到类似如下输出:

sql generateuuidv7 018d803ada8477f3839d24347c51137e (1 row)
在您的环境中可能会看到不同的UUIDv7,因为UUIDv7本质上是随机、唯一和基于时间的标识符。
如果您希望为与过去或未来的时间戳关联的数据生成UUIDv7,您可以使用以下命令生成UUIDv7:
sqlSELECT timestamptztouuidv7(20240207 20292677600)
您将得到如下输出:
sql timestamptztouuidv7 018d8542d7787c51bdb90ba5e494f3ef (1 row)
在您的环境中也可能会看到不同的UUIDv7。
您可以通过以下命令从给定的UUIDv7中提取时间戳:
sqlSELECT uuidv7totimestamptz(018d8542d77872fe9e668c8878dc53b5)
您将得到如下输出:
sql uuidv7totimestamptz 20240207 20292677600 (1 row)
我们来更深入地了解扩展的实现。如前所述,该扩展提供了三个函数来生成UUIDv7并从给定UUIDv7中提取时间戳。我们选择Rust编程语言是因为它强调内存安全,而其性能与C语言相当。有关PL/Rust的更多信息,请参考PL/Rust指南。
所有UUID都是128位长的。UUIDv7通过在前48位编码Unix时间戳以毫秒为单位来生成。剩余的位用于存储UUID的版本和变体,并随机生成的位以确保唯一性。完整实现可在我们的Github库中找到。
让我们将generateuuidv7函数分解成几个部分。首先,您需要在PL/Rust函数中声明任何您要使用的依赖项。此函数使用Rust随机库作为依赖项。该库用于生成随机位并将其编码到UUIDv7中:
toml[dependencies]rand = 085
接下来,让我们看看函数的代码部分。首先,您定义一个Rust类型别名UuidBytes以表示一个大小为16的u8向量:
rusttype UuidBytes = [u8 16]
接下来,您需要知道当前时间戳以生成UUIDv7。可以使用pgrx框架提供的clocktimestamp API来获取时间戳,该API返回一个Datum类型的时间戳。此API等同于PostgreSQL提供的clocktimestamp。
rustlet now = pgrxclocktimestamp()
您需要将Datum转换为具有毫秒精度的时间戳。为此,请使用pgrx提供的extractpart API来确定纪元自1970年1月1日000000 UTC以来的秒数,并乘以1000以获得以毫秒为单位的时间戳,类型为u64。pgrx提供的extractpart API等同于PostgreSQL提供的extract函数。
rustlet epochinmillisnumeric AnyNumeric = now extractpart(DateTimePartsEpoch) expect(Unable to extract epoch from clock timestamp) 1000
let epochinmillisnormalized = epochinmillisnumericfloor()normalize()toowned()let millis = epochinmillisnormalized parse() expect(Unable to convert from timestamp from type AnyNumeric to u64)
为生成随机字节,定义一个辅助方法,返回一个包含16个随机u8的向量,使用rust crate rand。并非所有的16个u8都是必需的,实际上,我们只需要其中的10个。
rustfn rngbytes() gt [u8 16] { randrandom()}
接下来,您将时间戳分解成一个u32和一个u16,以便可以稍后将其编码到UUID的前48位中。您还需要在UUID的相应字段中设置版本和变体。为此,我们的实现使用了uuid Rust crate作为参考。[1]
rustfn encodeunixtimestampmillis(millis u64 randombytes amp[u8 10]) gt (u32 u16 u16 [u8 8]) { let millishigh = ((millis gtgt 16) amp 0xFFFFFFFF) as u32
let millislow = (millis amp 0xFFFF) as u16let randomandversion = (randombytes[1] as u16 ((randombytes[0] as u16) ltlt 8) amp 0x0FFF) (0x7 ltlt 12)let mut d4 = [0 8]d4[0] = (randombytes[2] amp 0x3F) 0x80d4[1] = randombytes[3]d4[2] = randombytes[4]d4[3] = randombytes[5]d4[4] = randombytes[6]d4[5] = randombytes[7]d4[6] = randombytes[8]d4[7] = randombytes[9](millishigh millislow randomandversion d4)
}
现在,您可以通过组合之前所有的组件来构建一个UuidBytes一个包含16个u8的向量。同样,实施中还是参考了uuid Rust crate作为参考。[1]
rustfn generateuuidbytesfromfields(d1 u32 d2 u16 d3 u16 d4 amp[u8 8]) gt UuidBytes { [ (d1 gtgt 24) as u8 (d1 gtgt 16) as u8 (d1 gtgt 8) as u8 d1 as u8 (d2 gtgt 8) as u8 d2 as u8 (d3 gtgt 8) as u8 d3 as u8 d4[0] d4[1] d4[2] d4[3] d4[4] d4[5] d4[6] d4[7] ]}
最后,您可以使用pgrx提供的UUIDfrombytes API使用UuidBytes构造一个UUID。
接下来,让我们看看uuidv7totimestamptz函数。您可以在GitHub库中找到此函数的完整实现。此函数接收UUID作为参数,并返回相应的时间戳,类型为PostgreSQL的timestamptz。
回想一下,UUID表示为一个包含16个u8的向量,合计128位。由于时间戳被编码在UUIDv7的前48位中,因此您将提取前六个u8。通过将这六个以大端格式表示的u8构造成一个u64,该值表示自19700101 000000 UTC以来的毫秒数。
此时,您可以使用PostgreSQL函数totimestamp将其转换为PostgreSQL的timestamptz类型。pgrx框架提供了一个等效的Rust函数,叫做[totimestamp](https//docsrs/pgrx/latest/pgrx/datum/datetimesupport/fntotimestamphtml)。在此之前,您应将该值除以1000,作为Rust f64类型。这样做是因为Rust中的totimestamp函数接受f64作为参数,并且
电话:13594780397
联系人:周经理
邮箱:proportionate@qq.com
网址:https://www.qiyeseoer.com
地址:福安市艳摘谷187号