CalcLake
Blog
DeveloperAugust 10, 2026 · 9 min read

UUID v4 vs v7: Why Your Database Primary Keys Might Be Fighting Your Index

The same randomness that makes UUID v4 collision-proof is exactly what makes it a quietly bad choice for a database primary key. UUID v7 fixes it without giving up a single benefit of using a UUID in the first place.

The CalcLake Team

Built alongside the calculators themselves

If you've used UUIDs as primary keys — and most of us have, at some point, usually to avoid the coordination problems of auto-incrementing integers across distributed systems — you've probably also, at some point, watched a table's insert performance quietly degrade as it grew. That's not a coincidence, and it's not your database's fault. It's the UUID.

What makes UUID v4 good is exactly what makes it bad here

Version 4 UUIDs are 122 bits of cryptographically random data plus a handful of fixed version/variant bits. That randomness is the entire point — it's what makes collisions between two independently-generated UUIDs astronomically unlikely, even generated on completely different machines with zero coordination.

But a database index — specifically the B-tree structure most relational databases use for primary keys — is built on an assumption that new rows tend to have keys that sort near the end of the existing range. Auto-incrementing integers satisfy this perfectly: row 10,001 always inserts right after row 10,000, appending to the same region of the tree.

A random UUID doesn't respect that assumption at all. Every single insert lands at a random position across the entire keyspace, because the key itself carries no information about insertion order. The database can't append — it has to find the correct random slot in an already-large tree, which means page splits, fragmented storage, and index pages that don't fit neatly into memory or disk cache the way sequential keys do.

The randomness that makes v4 safe for distributed generation is the exact same property that makes it hostile to how a B-tree index actually wants new rows to arrive.

What UUID v7 changes

UUID v7 — standardized in RFC 9562 — keeps the part of UUIDs that's actually useful (128 bits, effectively globally unique, no central coordination needed) and fixes the part that was hurting you. Instead of being fully random, a v7 UUID leads with a 48-bit big-endian Unix millisecond timestamp, followed by random bits for the rest.

That timestamp prefix means UUIDs generated later always sort after UUIDs generated earlier — the same append-friendly property auto-incrementing integers give you, but without needing a central counter or a database round-trip to get the next value. New rows land at the end of the index, not scattered randomly across it. You get the collision resistance and decentralized generation of a UUID with the index locality of a sequential integer.

What it actually looks like

A batch of v7 UUIDs generated in sequence will visibly sort in the order they were created, since the leading bytes are literally a timestamp. A batch of v4s generated in the exact same sequence will look completely unordered — because they are.

Try the tool

Generate v4 or v7 UUIDs and compare them

When you'd still reach for v4

v7 isn't strictly better in every situation — it trades away one thing: a v7 UUID leaks approximately when it was created, since the timestamp is right there in the value. For most application data that's a non-issue, but if you're generating identifiers for something where creation time genuinely needs to stay hidden from anyone who can see the ID — some security tokens, certain anonymized identifiers — plain randomness (v4) is still the right call specifically because it reveals nothing.

  • Use v7 for anything that's primarily a database primary key or a naturally time-ordered record (orders, events, log entries, most application rows).
  • Use v4 when you specifically don't want the identifier to reveal creation order or timing — security-sensitive tokens, anything where that leak matters.
  • Either way, stop reaching for auto-incrementing integers purely out of habit in a distributed system — that's the problem UUIDs were solving in the first place, and v7 removes the one real tradeoff that made people reluctant to use them for exactly this purpose.

The short version: if your only reason for still using v4 is "that's what UUID means," it's worth checking whether v7 is available in your database and libraries yet — most modern ones now support it — and switching. You keep every real benefit of a UUID and stop quietly fighting your own index on every insert.