UUID Benchmark War
ardentperf.com
ardentperf.com
DigitalOcean and AWS RDS both support the https://www.postgresql.org/docs/16/uuid-ossp.html extension but it mentions v5 is the highest supported UUID version.
https://github.com/kiwicopple/pg-extensions/tree/main/pg_idk...
Also since RDS supports trusted language extensions you can install it via dbdev:
Why would UUIDv7 be more compact than v4? Doesn’t it take the same number of bits?
UUIDv4 contains no information, so you cannot accidentally leak information through them.
UUIDs have nothing to do with cryptography. "Cryptographically safe" has no meaning when it comes to identifiers. Encrypting information-carrying identifiers like UUIDv7 is not a good way to hide the information, because you cannot rotate the key if/when it leaks. The moment your key leaks, all information in the identifier is public again.
There's only three ways to mitigate the risk of information-carrying identifiers:
* Don't have them. Use fully random identifiers instead. * Don't share them. Use a different identifier in public (including in URLs). * Carefully consider the information carried in the identifier to decide if the information can be made public.
For one similar example with sequentially generated IDs - https://www.albert.io/blog/german-tank-problem-explained-ap-....
That being said, unless you have a specific reason to use a UUID over the standard incrementing integer, I wouldn't if for no other reason than dealing with UUIDs is visually complicated.
Clustering indexes make a lot of sense for many workloads, you just have to design your data model with them in mind.
The effect is just worsened by clustering, as done in SQL Server for significant (or at least measurable) benefits for many data patterns, because your base data is bigger due to wasted space due to excess page splits, as well as your supplementary indexes, so that needs reorganising more often too.
A common answer is to use an int (or maybe bigint) for your internal keys and a UUID for anything external, so you have the benefits of a UUID for external use (practically zero chance of collision in distributed systems, if appropriately random, and not potentially leaking information in some security contexts) but the efficiently of the integer otherwise. Or to use a partially ordered UUID format, which balances the compromises slightly differently (benefit: single value, dropping some of the UUID issues, keeping some of the benefits; detriment: letting some of the disadvantages of UUIDs, potentially reducing some of the benefits).
Why would you think that? You think it's equally likely to need to look up a salesorder from ten years ago vs one from two weeks ago? Are Github issues from ten years ago read equally often as the ones created yesterday? Most systems have a long tail of historic data that's mainly kept as a reference.
SELECT * FROM forum_post WHERE created_at > $DATE AND created_at < $DATE
If you were using UUIDv7, you could even use that to get the created_at [0], although you'd likely lose the indexing unless you made a functional index on it, like this: CREATE INDEX foo_ctime_idx ON foo(extract_timestamp_from_uuid_v7(uuid_v7_col))
[0]: https://stackoverflow.com/a/75869619As to alternatives:
First, if you can model your data using a natural key (singular or composite), and it doesn't unduly bloat the key, do so. This isn't always possible, of course (you wouldn't want to use an email address as a PK, as tempting as it may seem, since they're subject to change by users), but in many cases it is. Even better, for pre-existing data that's unlikely to change, like SKUs, you could pre-sort the data so you avoid any random page costs.
Barring that, design your API in such a way that you can use an incrementing integer without fear of leaking anything. At the very least, you can create an association table that maps an external ID (preferably UUIDv7 or ULID, as they're k-sortable) to the internal integer ID.
If you can't do either, use UUIDv7 or ULID. I prefer UUIDv7, especially since Postgres will be natively supporting their generation in an upcoming release (and because you can store them in RDBMS' native binary format), but either will do.
What's the risk (in terms of "pwning") when the UUID is generated on a server?
Step 2: Use the UUID as a security key - like saving a private file at files.example.com/12345678-1234-5678-1234-123456781234/private-file.pdf and assuming nobody will be able to download it without knowing the UUID
Step 3: Attacker predicts the UUID and downloads the private file.
Obviously the real solution here is to not use UUIDs as security keys - but dumbasses shoot towards their feet all the time, and UUIDv4 makes them hit less often.
I was kind of expecting some claim that actually exploits the inclusion of a hardware identifier, rather than "don't use predictable numbers as secrets".
Yes, they are. Still, it's not good security advice to point towards an other thing and say "this is even worse".
It's like complaining that a garden hose can be used to fill a car with carbon dioxide so we should recommend against using hoses in gardens.
> On a recent Code Assisted Penetration Test (CAPT) our team identified a vulnerability in a client's web application which allowed the consultant to bypass authentication and take over legitimate user accounts. The issue stemmed from the use of UUID version 1 in password reset tokens instead of the secure version 4 counterpart.
https://realizesec.com/blog/sandwich-attacks-exploiting-uuid...
You can probably sub in "suitably experienced" for "reasonable" if it reads better.
Look up the v1 spec. Essentially the entire ID space is timestamp + UUID, both of which are easily guessable.
The amount of entropy matters, and v1 has little of it. The danger here is that a UUID seems random when it really isn't.
Entering URLs wrong can also cause phishing breaches, that doesn't mean we stop using URLs does it?
Especially in a larger team you can't expect everyone to know fine details of every other part of the system. If you look up UUIDs you will (correctly) get the impression that they're mostly random, except some types aren't.
I would not expect every engineer to know to inspect the UUID and figure out its type and to know that this could have real consequences for guessability by external actors.
If your team is small enough that everyone can hold the entire system in their head at once, that's great, but that excludes many real world projects.
Either we have very different ideas of what "mostly" means or you don't know what the different versions of UUID include as well as you think you do.
If we're considering the 5 published versions: 1 is built from random data; 2 are built from time & host data; and 2 are built from a namespace and a "name".
If we consider the 3 proposed additional versions: 1 is built from time & host; 1 is built from time & random data; and 1 is built from custom data.
So I personally wouldn't consider "most" to mean either 1 of 5 (20%) nor 2 of 7 (28.6%).
> I would not expect every engineer to know to inspect the UUID and figure out its type and to know that this could have real consequences for guessability by external actors.
I would expect someone who is writing software and encounters UUIDs to take the few minutes needed to understand the basics of what goes into the different versions, ensure they're being used correctly.
> If your team is small enough that everyone can hold the entire system in their head at once, that's great, but that excludes many real world projects.
They don't need to "hold the entire system in their head". They just need to (a) have the most basic understanding of what a UUID contains, and then (b) use it appropriately.
Sorry mate but not everyone gets to always work with the best of the best. I went through this exact exercise in the past and avoided v1 IDs because I suspected there is a risk they become externally exposed down the line, perhaps even years later when I'm gone.
I suppose you might decide otherwise and then blame others for incompetence instead if down the line someone failed to comprehend UUIDs like you so effortlessly do.
Please try re-reading what I wrote because you clearly didn't comprehend it the first time.
I never called anyone bottom of the barrel.
I said the reason not to use them is "bottom of the barrel", as in, it's a poor excuse for not using something, based on blatant misuse. Like saying "people mis-type URLs all the time, we should get rid of them" or "people get electrical shocks when they stick utensils in power outlets, we should stop using electricity"
https://www.postgresql.org/docs/current/uuid-ossp.html#UUID-...
Obviously clients should use this variant as well if you're using v1.
RFC 4122 does allow the MAC address in a version-1 (or 2) UUID to be replaced by a random 48-bit node ID, either because the node does not have a MAC address, or because it is not desirable to expose it.
The big problem - how can you tell for sure that is what is happening, and how would you catch a reversion if the underlying library changes behavior? Assuming you’re making a call into someone else’s stuff anyway.
A big challenge I’ve seen with UUID implementations is dependence on some kind of hidden system state that causes issues. like a MAC or nodeid file somewhere in a VM that gets cloned, resulting in duplicates where ‘duplicates should be impossible’.
https://www.postgresql.org/docs/current/uuid-ossp.html#UUID-...
There are lots of uses for thousands per second. Usually in some sort of logging tracking application where you have have lots of processes and users.
You’re also causing huge amounts of WAL bloat (unless you’re running ZFS and can thus safely disable full-page writes) [1].
And on a system with a clustered index (InnoDB, SQL Server), the performance and space bloat is even worse.
[0]: https://www.cybertec-postgresql.com/en/unexpected-downsides-...
[1]: https://www.2ndquadrant.com/en/blog/on-the-impact-of-full-pa...
Want to tag all traffic on your website? 1 billion visits = ~375 visits a second. Some of the accounting systems I have worked with were raking in datasets ~300GB at a time that were almost purely compressed transactions. Some of the companies I have worked for in retail have thousands of stores and a pretty constant volume, getting to that rowcount for some of their customer tracking stuff would easily blow that out.
Whether you should do anything else before the data has been persisted is a totally different discussion.
If you are doing async writes, as you alluded to, why bother with RDBMS in the first place? ACID is out the window.
- insert/then retrieve ID can easily result in duplicate records in some edge cases, and won’t necessarily be able to be easily fixed either. the inserted record doesn’t have a global ID until it’s inserted.
Can this generally be fixed using good transactions semantics? Yes usually. But it’s expensive. And in many cases you’ll have to default to failing writes instead of eventually consistent behavior.
- CRDT type behavior works better when things have a known valid unique ID from the get go. insert/update/ignore can happen quickly and easily without two way communication and in bulk, and edge cases have more easily modelable ‘eventual consistency’.
- generating unique IDs in the database forces serialization of certain processes in the database, which can cause scaling issues and high latency.
For instance, using the DB to create unique request IDs for web or API requests? Asking for problems.
Generating UUIDs for them at request time, and then putting those IDs where needed when later correlation/tracking is desirable? Much better.
Same can apply for other object ID creation, when there aren’t other natural keys that need to be checked first.
1) exposing in URLs (you can't scrape all data by iterating over IDs)
2) passing them around between microservices/systems (less confusion where an ID comes from when debugging/doing tech support, because you can check the ID in a few tables and be 100% sure that's exactly what you are looking for, because IDs are globally unique, unlike bigints)
3) useful in situations when data from several servers or DB shards is eventually aggregaged in one place (for example, for analytics) - with bigints you'd have collisions
(yes please benchmark on bare metal)
I can't see any proof of that, sources?
They talk about vCPU here:
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/compute-...
Do you mean for example c6g.metal? For c7a there are also "metal" instances available, but they don't mention that:
https://docs.aws.amazon.com/ec2/latest/instancetypes/co.html...
> One of the major differences between C7a instances and the previous generations of instances, such as C6a instances, is their vCPU to physical processor core mapping. Every vCPU on a C7a instance is a physical CPU core. This means there is no Simultaneous Multi-Threading (SMT). By contrast, every vCPU on prior generations such as C6a instances is a thread of a CPU core.
So your "vCPU" is not an SMT thread sharing a core with some other "vCPU". Of course you're still sharing the rest of the CPU, so things like memory bandwidth and presumably upper level cache, and you might be affected by thermal load from other cores? idk whether that kind of thing applies in server contexts.
AWS will happily sell you bargain bin performance at enterprise prices if you don't understand what you're buying. There's a reason they're almost a $2T company.
It's a single core, so no parallelism in the db itself. There's a fraction of the RAM my phone has, so that slow IO is more pronounced.
The basic implications of different keys and detailed look at the cache internals are valid and interesting, but the hardware is nothing like a server you'd want to run a database on, so the benchmark isn't very interesting. An iPhone is probably beefier in every way.
1) the percentages would be different but the basic implications should hold true even with NVMe and 96 cores, as long as we scaled up the data size and workload
2) in addition making it a bit easier to demonstrate what we'd expect to see (there's not really anything surprising here), chose this setup because it's cheap so anyone else could replicate the exact results or play around with the scripts and try variations without having to spend much money. for example, someone on twitter was curious about uuidv7 in a text field - would be easy & cheap to try it out and see what happens - also could easily go to bigger hardware and local NVMe, changing client and row counts
20 years ago when i wanted to benchmark oracle RAC, i had to go out and buy dual-attach firewire drives and that was a hack because who wants to spend their personal vacation money on an old EMC clarion storage array from eBay [i might have bought personally an old sun server or two though!]
size results should be independent of hardware setup, but the perf results are specific to this setup, which is why the post includes detailed specs and scripts for transparency
also, FWIW, most production use cases for databases these days include some kind of high availability which means network involvement in the persistence path - so even when the database is on local NVMe, it's not uncommon to have a hot standby or patroni or something with sync replication
If you're something like a bank, you need synchronous replication, but a lot of use-cases would probably be fine with async with a couple ms RPO. Then again most people probably don't need more than a few thousand writes/second anyway. For banks, I worked on storage arrays at IBM ~10 years ago, and I think our synchronous replication was sub 100 us, but can't remember anymore.
Results here [1].
Note that this used \COPY instead of INSERT so it bypasses a good bit of logic normally present, but the difference remains stark. Random page hits on a B+tree are always going to suck.
[0]: https://gist.github.com/stephanGarland/fe0788cf2332d6e241ff3...
[1]: https://gist.github.com/stephanGarland/ee38c699a9bb999894d76...
And in any case, the primary driver of the poor performance is from k-sortability due to page spread. The only alternative to UUIDv? that would do well is ULID, as it’s lexicographically sortable.