UUID v4 vs v7: Why Your Database Primary Keys Might Be Slowing You Down
Fully random UUIDs are the safe, boring default — until they become a real, measurable database performance problem at scale. Here's the mechanism, and why UUID v7 exists to fix it.
If you've picked UUIDs as primary keys for a database table, there's a good chance you reached for version 4 — fully random, 122 bits of entropy, effectively zero collision risk. It's the right default for a lot of things. It's also, at real scale, a quiet performance tax that a newer version was specifically designed to remove.
The problem: random keys and B-tree indexes
Most relational databases store their primary-key index as a B-tree, and B-trees like sequential-ish insertion. When you insert rows with a steadily increasing key — an auto-increment integer, say — new rows land at the "end" of the index, in the same region of the tree, in the same disk pages, over and over. That's cheap: mostly appending, minimal rebalancing, good cache locality.
A fully random UUID v4 key does the opposite on purpose. Every insert lands at a random position in the keyspace, which means a random position in the B-tree — scattered across pages that are unlikely to already be in memory, forcing more disk reads, more page splits, and worse cache behavior than a sequential key would. At small scale this is invisible. At large scale — millions of rows, high insert throughput — it becomes a real, measurable source of write amplification and degraded index performance, a well-documented pattern across most B-tree-based engines (MySQL/InnoDB and PostgreSQL both).
The fix: put a timestamp in the front
UUID version 7 (formalized in RFC 9562, which supersedes the original UUID spec, RFC 4122) solves this by putting a 48-bit Unix millisecond timestamp in the first part of the identifier, with the remaining bits still random. The result: UUIDs generated close together in time sort close together in value — inserts land in roughly the same region of the index, the same way sequential auto-increment IDs always have, while the rest of the value stays random enough to keep the collision resistance and unguessability that made UUIDs attractive over integers in the first place.
You get both properties at once: globally unique, unguessable to an outside observer looking at any single ID (they can't derive the previous or next value the way they could from an auto-increment key), and index-friendly on insert.
Where v4 is still the right call
v7's timestamp prefix is a real, deliberate tradeoff, not a strictly-better replacement in every situation. Because early bits are predictable (an approximate creation time), v7 leaks slightly more information than v4 about when a record was created purely from its ID — usually harmless, but worth knowing if that timing information itself needs to stay private. For request IDs, trace IDs, and other identifiers that are never used as a sorted database key, v4's simplicity is still perfectly fine — there's no index-locality benefit to capture if nothing is indexing on insertion order.
The other versions, briefly
The UUID spec defines more versions than most people ever encounter directly:
- v1 embeds a timestamp and the generating machine's MAC address. It technically gives you sortability, but leaks machine identity — which is why it fell out of favor for anything public-facing.
- v3 / v5 are deterministic: they hash a namespace plus a name (MD5 for v3, SHA-1 for v5), so the same input always produces the same UUID. Useful when you want a stable, repeatable ID derived from something else — a URL, an email address — rather than a random one.
- v4 is fully random, and until v7 arrived was the sensible default for almost everything.
- v7 adds the timestamp prefix described above, and is increasingly the recommended choice specifically for database primary keys and other insert-ordered identifiers.
The practical takeaway
If you're choosing a primary key today and expect meaningful write volume, v7 is worth defaulting to over v4 — you lose very little and gain real index performance. If you're already on v4 at scale and have noticed write throughput or index bloat creeping up, this mechanism — not necessarily your schema or your hardware — is a genuine, well-documented place to look.
Generate v4 or v7 UUIDs — including a built-in inspector that decodes any UUID's version, variant, and (for v7) its embedded timestamp.