New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v7 should have a counter option? #717
Comments
Hi @rogusdev 👋 I'd be keen to go catch up on some of the discussion around that. Those sections look like recommendations rather than requirements, so I think It seems a bit overkill to me to need a clock, a source of randomness, and a monotonic counter to generate V7 UUIDs, but we could consider adding |
The test vector for UUIDv7 appears to use a fully random value for I think we should consider supporting this, but don't think we need to block stabilizing the current APIs on it. |
From my perspective, while the main point of uuid v7 is to put the clock at the front, so that db indexes can btree on them properly, counters add additional safety mechanisms that are quite to my taste: if you are generating ids in a single thread, you are guaranteed that no duplicates can happen, if there are less than the counter limit per millisecond. While the degree of randomness involved otherwise is certainly extremely unlikely to have duplicates, I am a big fan of guarantees. Which is in fact why they put it into the RFC, as that is also a feature in many other popular id libraries. |
That said, "SHOULD" is indeed a recommendation, rather than requirement, and they list multiple alternatives. So I support having a separate set of ctor functions to call for the counter version(s). |
Hi Ashley, I'm Sergey Prokhorenko, a contributor to rfc4122bis and a counter enthusiast. Some developers implement the counter:
UUIDv7 with the counter for PostgreSQL is currently being developed in C by Andrey Borodin: But it seems to me that Rust is also a good tool for such development. |
We've currently actually got access to a counter in our
An alternative for 2. would be to add |
Like the vast majority, I am convinced that only the seventh version is worthy of attention. There is no need to waste time and effort implementing other versions.
I'm against. A counter initialized to zero increases the collision probability. Worse, the counter value may be the same as the random value used instead of the counter at the beginning of the millisecond. I prefer a counter that is initialized to a random value at the beginning of each millisecond. But this may reduce the actual capacity of the counter. Therefore, the leftmost bit of the counter should be initialized to zero and/or the timestamp should be incremented when the counter overflows.
The timestamp in the seventh version is shorter, and you missed the ver and var segments.
If we are talking about initializing the counter every millisecond with a random value and incrementing within a millisecond, then I am for it. |
This library already implements |
I highly recommend looking at the UUIDv7 implementation in PostgreSQL v17 by Andrey Borodin |
Thanks @sergeyprokhorenko 👍 Having a good reference implementation to point at will definitely be helpful |
Another good implementation: It is mentioned in this benchmark. Due to the excessively long counter (initialized with a random number every millisecond), less entropy is generated, which requires a lot of resources. But it's worth trying to replace rand::rngs::OsRng with openssl-rand for better performance. |
This patch has already been successfully tested: https://ardentperf.com/2024/02/03/uuid-benchmark-war/ |
Thought/Question: Why not add a builder pattern with defaults? That doesn't eliminate the backwards compatibility and can improve clarity around generating the UUID. |
The API in the Rust ULID crate provides monotonicity using an If you know that you are generating a batch then an Here's is an example: use ulid::Ulid;
use uuid::Uuid;
fn main() {
let mut uuids = Vec::with_capacity(10);
for _ in 0..10 {
let uuid = Uuid::now_v7();
uuids.push(uuid);
}
for uuid in uuids.iter() {
println!("{:<12} {uuid}", "uuidv7:");
}
println!("");
let mut ulids = Vec::with_capacity(10);
let mut ulid = Ulid::new();
for _ in 0..10 {
ulids.push(ulid);
ulid = ulid.increment().unwrap();
}
for ulid in ulids.iter() {
println!("{:<12} {ulid}", "ulid:");
}
} Is taking blatant inspiration from the excellent work in the ULID crate ok? Of course. ULID is the very first referred to time-based unique ID referenced in the new RFC. Note that this also gives more control over creation of batches. If one creates a batch of 1000 UUIDs, using an internal counter you have no control over whether they will be in the same millisecond. This means you will have part of the batch spread across different timestamps and the random component will jump around for each millisecond. Using an increment you can keep the millisecond and the random component stable. It's not required in the specification but seems useful to be able to trivially see if UUIDs are part of the same batch. |
Per the new draft "With this method rand_a section of UUIDv7 SHOULD be utilized as fixed-length dedicated counter bits that are incremented by one for every UUID generation."
That's in the "Fixed-Length Dedicated Counter Bits (Method 1)" section under https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#monotonicity_counters which is directly linked from https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2 under the rand_a explanation.
To my reading, that means it should have a counter, like v6 (and v1), but instead the current v7 builder implementation https://docs.rs/uuid/latest/src/uuid/v7.rs.html#47-53 is just random for rand_a. Am I missing something on this one? If not, and this is a desirable feature, I might look into putting up a PR to add the counter support.
The text was updated successfully, but these errors were encountered: