UUIDv7 doesn't have the properties you think it does
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
Built on Shards of Silicon This post is part of a collection on UUIDs. I’m going to call it. UUIDv7 is weird. Most blog posts about UUIDv7 tout these benefits: monotonically increasing IDs a helpful timestamp of when the object was created; and unguessable IDs But none of these properties are strictly held. Certain RFC compliant implementations of UUIDv7 violate these properties to extreme degrees. The only useful property UUIDv7 appears to strictly provide is uniqueness and therefore collision resistance. I’ve already discussed how UUIDv7 IDs can be guessable, so this post explores the other properties.
“Built on Shards of Silicon” 是关于 UUID 系列文章的一部分。我要直说了:UUIDv7 很奇怪。大多数关于 UUIDv7 的博文都吹捧其具有以下优点:单调递增的 ID、对象创建时间的有用时间戳,以及不可预测的 ID。但这些属性没有一个是严格保持的。某些符合 RFC 标准的 UUIDv7 实现会在很大程度上违反这些属性。UUIDv7 似乎唯一能严格提供的有用属性是唯一性,从而实现抗碰撞性。我已经讨论过 UUIDv7 ID 如何变得可预测,因此本文将探讨其他属性。
Is the timestamp useful? Superficially, it seems that embedding a timestamp in the ID can remove the need to track the creation time in a separate database column, providing marginally more efficient database operations. If you need to list things ordered by creation time very often, it’s useful if the primary index gives that behavior, removing the need for another index. Unfortunately, the timestamp embedded in a UUIDv7 ID may not relate to the object creation time. The Postgres docs for uuid_extract_timestamp helpfully warn that “the extracted timestamp is not necessarily exactly equal to the time the UUID was generated”. RFC 9562 allows implementations to alter the timestamp field, using values that aren’t strictly derived from the system clock. This relates to the tension between UUID monotonicity and timestamp accuracy.
时间戳有用吗?从表面上看,在 ID 中嵌入时间戳似乎可以免去在单独数据库列中跟踪创建时间的需要,从而提供稍微高效的数据库操作。如果你经常需要按创建时间列出事物,那么如果主索引能提供这种行为,就会很有用,从而无需建立另一个索引。不幸的是,嵌入在 UUIDv7 ID 中的时间戳可能与对象创建时间无关。Postgres 关于 uuid_extract_timestamp 的文档很有帮助地警告说,“提取的时间戳不一定完全等于生成 UUID 的时间”。RFC 9562 允许实现更改时间戳字段,使用并非严格源自系统时钟的值。这涉及到 UUID 单调性与时间戳准确性之间的矛盾。
Implementations MAY alter the actual timestamp. — RFC 9562 §6.1 This specification makes no requirement or guarantee about how close the clock value needs to be to the actual time. — RFC 9562 §6.1 … implementations MAY increment the timestamp ahead of the actual time … — RFC 9562 §6.2 Incrementing the timestamp ahead of the system clock is an interesting problem. If the generator is seeing so much load that a counter field is regularly overflowing then the timestamp could accelerate into the future. Using a sufficiently large counter would be wise and the RFC says the minimum size SHOULD be 12 bits. A counter overflow would require a pretty aggressive creation rate, over 4,096 IDs per millisecond per node, which is beyond the limits of many systems. This overflow becomes relevant, however, if the timestamp freezes; which I’ll discuss in a later section.
实现可以更改实际时间戳。—— RFC 9562 §6.1 本规范对时钟值需要多接近实际时间没有要求或保证。—— RFC 9562 §6.1 ……实现可以将时间戳增加到实际时间之前……—— RFC 9562 §6.2 将时间戳增加到系统时钟之前是一个有趣的问题。如果生成器负载过大,导致计数器字段经常溢出,那么时间戳可能会加速进入未来。使用足够大的计数器是明智的,RFC 建议最小大小应为 12 位。计数器溢出需要非常激进的创建速率,即每个节点每毫秒超过 4,096 个 ID,这超出了许多系统的限制。然而,如果时间戳冻结,这种溢出就会变得相关;我将在后面的章节中讨论这一点。
Example: Python uuid.uuid7() One prominent implementation that increments the timestamp is the Python standard library’s UUIDv7. The increment happens only in certain circumstances, like when the counter overflows. Unfortunately, the generator can get stuck in an increment cycle if the clock ever jumps backwards and the timestamp can race ahead of the system time by an arbitrarily large amount. Here’s a simple example: import uuid from datetime import datetime # Prime the generator with the current time t = uuid.uuid7().time now = datetime.now() delta = (datetime.fromtimestamp(t/1000) - now).total_seconds() print(“Initial delta: ”, delta) # Ask the user to change the time input(“Set clock back one hour”) while True: for _ in range(100000): uuid.uuid7() t = uuid.uuid7().time now = datetime.now() delta = (datetime.fromtimestamp(t/1000) - now).total_seconds() print(“New delta: ”, delta)
示例:Python uuid.uuid7() 一个增加时间戳的著名实现是 Python 标准库的 UUIDv7。这种增加仅在特定情况下发生,例如当计数器溢出时。不幸的是,如果时钟向后跳动,生成器可能会陷入增加循环,并且时间戳可能会比系统时间超前任意大的量。这是一个简单的示例:[代码略]
Which gives: $ uv run —python 3.14 main.py Initial delta: -0.000748 Set clock back one hour New delta: 3695.277581 New delta: 3795.042411 New delta: 3894.80756 New delta: 3994.573106 ^C We expect the difference to be 3600 seconds (one hour) as the implementation refuses to allow the embedded timestamp to jump backwards and we expect the difference to decrease as the system clock catches up to the frozen timestamp. But when the load is above one ID per millisecond the difference will increase without bound. At lower loads it will take longer for the system clock to catch the timestamp, but we don’t see the runaway effect. The related issue shows Python developers reasoning that this is consistent with the RFC, is consistent with other implementations, and that there isn’t “much we can do” on a “flawed system”. I think these points are correct as I see this same behavior in other implementations. Nevertheless, it would be helpful if existing UUIDv7 implementations could handle system clock jumps without runaway behavior. There are many real-world examples of clocks jumping backwards, even decades out of sync in time-sensitive environments.
结果如下:[代码输出略]。我们预期差异为 3600 秒(一小时),因为实现拒绝允许嵌入的时间戳向后跳动,并且我们预期随着系统时钟赶上冻结的时间戳,差异会减小。但当负载高于每毫秒一个 ID 时,差异将无限增加。在较低负载下,系统时钟需要更长时间才能赶上时间戳,但我们看不到这种失控效应。相关问题显示,Python 开发人员认为这与 RFC 一致,与其他实现一致,并且在“有缺陷的系统”上“无能为力”。我认为这些观点是正确的,因为我在其他实现中也看到了同样的行为。尽管如此,如果现有的 UUIDv7 实现能够处理系统时钟跳动而不会出现失控行为,那将很有帮助。现实世界中有许多时钟向后跳动的例子,在对时间敏感的环境中甚至会出现几十年的不同步。
Is leaking a timestamp harmful? Embedding the timestamp in the ID means that you cannot share the ID without also sharing the timestamp. It’s pretty difficult to think of an example where this really matters. One example could be a user profile page that doesn’t disclose when the account was created, but the UUIDv7 ID in the URL leaks the high-precision timestamp. This issue would typically be very low on my list of security concerns because I find much more severe problems when I do security assessments. Nevertheless, there are contexts where this matters and engineering resources would be focused on addressing it. If you’re in the latter camp, be wary of UUIDv7 leaking metadata.
泄露时间戳有害吗?在 ID 中嵌入时间戳意味着你在共享 ID 时必然会共享时间戳。很难想到一个这真的很重要的例子。一个例子可能是用户个人资料页面没有披露账户创建时间,但 URL 中的 UUIDv7 ID 泄露了高精度时间戳。这个问题通常在我的安全关注列表中排名很低,因为我在进行安全评估时会发现更严重的问题。尽管如此,在某些情况下这很重要,工程资源会集中在解决它上。如果你属于后者,请警惕 UUIDv7 泄露元数据。
Are UUIDv7s monotonic? This section is even more pedantic, so skip ahead if you’ve had enough. Briefly: UUIDv7 IDs provide some sort of rough order, which may be suitable for your requirements. When I was reading the UUIDv7 RFC I was surprised to see it describing a method for creating IDs across different nodes that are monotonically ordered. This was proven impossible back in the 1970s, and lead to the development of vector clocks and similar technology. At a high level, the level, the problem is that system clocks cannot be synchronized to a suitable precision. Ordering things by timestamp only works if you know precisely what time it is. Any uncertainty in the time results in uncertainty of event ordering. Succinctly: Timestamps are only time-ordered if you know what time it is and you don’t know what time it is. For this reason,
UUIDv7 是单调的吗?本节更加迂腐,如果你已经看够了,请跳过。简而言之:UUIDv7 ID 提供了一种粗略的顺序,可能适合你的需求。当我阅读 UUIDv7 RFC 时,我很惊讶地看到它描述了一种在不同节点间创建单调有序 ID 的方法。这在 20 世纪 70 年代就被证明是不可能的,并导致了向量时钟和类似技术的发展。从宏观层面来看,问题在于系统时钟无法同步到合适的精度。只有当你确切知道时间时,按时间戳排序才有效。时间的任何不确定性都会导致事件排序的不确定性。简而言之:只有当你既知道时间又不知道时间时,时间戳才是按时间排序的。因此,