UUIDv7 doesn't have the properties you think it does
30 points by robalex
30 points by robalex
The main reason to use UUIDv7 is because ordered keys improve cache locality in b-tree indexes, which doesn't require these properties to be held strictly.
right, article doesn’t mention this at all yet it’s the main reason I use them. If Postgres could use hash indexes for pk I wouldn’t bother with v7.
I don't think its entirely fair to say the article does not mention this at all. After the section about why the ordering is not strictly monotonically ordered he does mention it is still roughly ordered which is still beneficial. He just doesn't exactly explain that this is one of those benefits. I don't think the article is arguing against uuid v7, I think it is just arguing to be aware of some foot guns if you make assumptions about its properties.
Author here, this comment sums my position up well, thanks!
There was a longer version of this post that talked about Postgres in more detail, but I trimmed it out. Using UUIDv7 for Postgres B-tree write locality does not require strictly monotonic IDs, so I think it is a good fit.
Postgres is interesting to talk about because the UUID could be generated by Postgres itself or it could be generated by a (trusted) client using any UUIDv7 implementation. That sort of distributed generation makes it easier to see how offset clocks could impact monotonicity.
I also wanted to be careful as there are many Postgres-compatible databases which use distributed storage. Those may see worse write performance if UUIDv7 causes all the writes end up on a single shard, but it's implementation specific and I'd want to benchmark it. Vendors encourage developers to think of those as Postgres, but they are only Postgres-compatible and they have different low-level trade offs.
I don't think I've ever met anyone in the wild who chose UUIDv7 because they need monotonicity but I guess there are also a lot of people I haven't met
As an example, TigerBeetle recommends using something similar to UUIDv7 [1] for client-side generated idempotency IDs. To prevent double spending arising from retries at various network layers, the database must ensure that every financial transfer is executed at most once. So, a website or a mobile app generates a transfer ID locally, and the database needs to check if it has ever seen the same ID before. If you have 10 years of transfer history, that's very slow!
However, if IDs are roughly monotonic [2] (e.g., based on client's local time), then it's enough to keep, say, the last day's worth of ID in memory for the checks, which is one way to make idempotency checks fast.
[1]: UUIDv7 is specced to be big endian, which is useful when you compare a pair of ids as strings/byte slices, but we want to compare them as u128 numbers, and so use little-endian.
[2]: Because ID is client side, it is not actually monotonic, and the clock value can be completely bogus, so the time can't be trusted. For internal IDs, we use server-side strictly monotonic u64 timestamps. If you assign ID server-side (and you DB is strictly serializable), you don't need randomness component at all, and server clocks can be made reliable.
You could use a hybrid logical clock to ensure monotonicity even when the client clock stalls or goes backwards. Well, with the caveat of knowing your stall tolerance, and adjusting the number of bits in the clock dedicated to the logical counter appropriately.
Yes, you need two clocks!
locally-monotonic but not globally monotonic purely client-side clock for end-to-end idempotency checks. It's important that idempotency ID is generated on the client, it's too late to do that when the data gets to the server, as it might have been duplicated by that time already. And you can't guarantee strict monotonicity in this case, because clients don't communicate with each other.
And once the data gets on the server, you want to stamp it with hybrid logical physical time. It's not strictly required, but it's so very useful: 64 bits instead of 128 bits, strict creation order, temporal locality, deterministic time for business logic, observability.
A spex of course can guarantee nothing. But a good implementation can have many of these properties