UUIDv7: Timestamps aren’t time-ordered and other oddities
https://alexsci.com/blog/uuidv7-is-weird/They are also not supposed to be used as public IDs, just like you wouldn't use auto-incrementing IDs that way.
(I wrote a library for this 13y ago [1], around the time Twitter, Instagram et al started reaching enough scale to think about this kind of optimization)
I thought the main reason UUIDv7 was created with a time component is to keep temporally related rows together in the index pages. Randomly distributed row ids seem OK until the entire index no longer fits in RAM.
Yes you can shrink it down when things get bad, but if your index is large enough that you care then it is large enough that this reorganisation is going to be costly in time and may slow your system while it happens (or cause a complete lock if your DB doesn't support, or optionally supports and you don't select the option, this as an online operation.
https://pypi.org/project/uuid-utils/ is what you want :)
I hope to god no one is actually using the timestamp portion to replace an “updated at” timestamp but I suspect I’ll be disappointed.
Older designs like ULIDs (https://github.com/ulid/spec) and Snowflake IDs (https://en.wikipedia.org/wiki/Snowflake_ID) have similar properties to UUIDv7, and potentially some advantages w.r.t. length/encoding/runtime cost/etc.
But UUIDv7 has one key advantage none of those do: you can store them in a UUID-typed column or struct field without any lossy conversion. All of the UUID versions share a common size and serialization format, so it doesn't actually matter if you generate v1/4/7/etc.
Many language standard libraries can generate them interchangeably, and (for example) a Postgres table can store any of the versions in the same column type.
So yes, to the author's point(s): they are not magic, monotonic, un-guessable IDs and you shouldn't treat them as either "secure" or strictly ordered. That doesn't mean they aren't useful.
I know the conventional wisdom says that the collisions are extremely rare <<insert some sort of cosmis analogy>>. Then, some reddit users reported encountering a collision: https://www.reddit.com/r/webdev/comments/1kvkxte/comment/mud...
Ultimately, we'd have to design the system to handle collision anyway. Might as well design a system to avoid collision in the first place. Then, if we have a system that avoids collision, then whether UUID is used is irrelevant.
the UUID timestamp is not for de-duping purposes. it's avoid expensive re-indexes that happen with truly random ids
If you are using your primary key for anything other than a unique identifier for rows, you are almost certainly doing something wrong. If you need an index on creation time, or just creation order, have a creation time column and an index on that.
Sure, we could use faster alternatives specifically for indexes - but UUIDs are used throughout all business processes. Setting up a separate "conversion to UUID" step seems like a waste.
In fairness, he does mention this, but squeezes it into a single sentence near the end. But eg the fact that you can't reliably extract the precise timestamp from the UUIDv7 is a feature, not a bug. It's either that or collisions and you don't want the collisions.
if timestamp_ms < _last_timestamp_v7:
timestamp_ms = _last_timestamp_v7 + 1
https://github.com/python/cpython/blob/8d119208074fef0f652f1...But if you did want "monotonically increasing IDs", as the article suggests, that is what you want.
I assume I'm oblivious and naive, but it seems theoretically possible these issues could -- theoretically -- result in TOCTOU or similar issues.
Any thoughts about relying on:
- central time service, with message-queue of inbound requests, resulting in correctly timed + sequenced events [ albeit with hopefully small lag tradeoffs ]?
- know of any databases that implement this type of logic around their own minting of time-stamped UUIDs?
Prior efforts implementing event-driven backend service with UUID7s minted in client-code seemed perfectly fine at low usage/scale. However, as clients + usage + event-volume scaled, team became more curious about time/timestamp/related issues, and any minting client-side quickly fell out of favor.
Any insights greatly appreciated -- thanks
[ edited b/c i don't know how to format... ]
There are fun conditions under which a quasicrystalline clock could coordinate this in a distributed way. https://en.wikipedia.org/wiki/Time_crystal
Just like NOW() won't have the current time.
I believe it was Poincare who has proven that it's even theoretically impossible to synchronize clocks exactly.
It has never crossed my mind to decode the timestamp from them so I save myself a 12-byte column, neither has it crossed my mind to have them monotonic in a distributed system...
Realities are arrows. Time marks flight. UUIDv7 is Time.
(Yes. Yes. No, hell no, not after what Robert just told ya.)
https://www.evalapply.org/posts/poor-mans-time-oriented-data....
The small saving grace for me is that, despite my broken mental model of UUIDv7's time and monotonicity properties, I get to also use SQLite's automatic rowid for ordering and disambiguation. That too, only because I can (still think I can) get away with a "DB per tenant" architecture. Nevertheless, the same breakage potential will return when (not if, when) I have to mix / remix records from two or more DB files.
brb, investigating whether I can reliably construct UUIDv7s using db-time...
This glares for me - I'm not "listing" by creation time per se; I'm taking advantage of locality - the page of data that a given key's data is in should also contain data of other keys created around the same time.
That locality means that I have less disk calls if I want to get the data for a series of rows that were created around the same time.
The blog post seems to be saying that (worst case) I cannot rely on that aspect.
I think this is good. A proper UUID that has specific significant bits dedicated to (approximately) monotonic values is useful. I don't want it over-specified beyond that, and I don't want to just use UUIDv8.
I'm leaning increasingly heavily on v5 for its determinism which is extremely important for IDs. I would sacrifice every other property to get determinism.
You can't have idempotency without determinism and you can't get reliability without idempotency.
Once you factor in failure and retry scenarios under concurrency, you often need ID determinism. In the rare cases I don't want determinism, I prefer to go all-in on randomness. I prefer my IDs to be well spread out; it scales better, more predictable, less of a security liability (prefer not to have all data clustered in the same place)...
Both UUIDv4 and UUIDv5 outputs are uniformly distributed. IMO this is a desirable property which is well worth a bit of additional compute. v7 feels like an optimization hack, and it comes at a cost.
If you want to get the performance benefits of v7 with the determinism of v5, then you need to have essentially two IDs; the id (v7) and an idempotency key (which is essentially like a second ID you have to maintain; e.g. UUIDv5)... In the end, maintaining a UUIDv7 ID + idempotency key is less performant than just having a single UUIDv5 id which does both jobs for no extra effort... Not to mention, less maintainable and more complicated to use.