UUID v7 vs v4: when sortable IDs matter, and when they don't
UUID v7 sorts by time and plays nicer with database indexes than v4. Here's when the difference matters in production, and when v4 is still the right call.
For two decades, UUID v4 was the default. Random, opaque, globally unique without coordination — it solved a real problem (distributed ID generation) so well that most teams never thought about it again. Then in May 2024, RFC 9562 standardised UUID v7, and now most modern stacks ship both and ask you to pick.
So which one. The short answer: v7 for database primary keys, v4 for everything else that needs an unguessable identifier. The longer answer follows.
What changed
UUID v4 is 122 bits of randomness with 6 bits set aside for version and variant flags. That's it. The whole identifier is uncorrelated noise.
UUID v7 takes the first 48 bits of those 128 and replaces the randomness with a Unix millisecond timestamp. The remaining 74 bits stay random. Result: two v7 IDs generated a millisecond apart sort next to each other; two v4 IDs generated a millisecond apart land randomly across the index.
That sounds like a small change. It isn't, for one specific use case.
The B-tree problem
Most relational databases — Postgres, MySQL, SQL Server — store table data sorted by primary key in a B-tree. When you insert a new row, the database has to:
- Find the right index page to put it in.
- Read that page from disk if it's not in cache.
- Write the row.
- Rebalance the tree if the page is full.
When primary keys are random (v4 UUIDs), inserts scatter across every index page in the table. Every insert is a cold page read. Cache hit rates collapse at scale. Write throughput halves, then halves again.
When primary keys are sequential (v7 UUIDs, auto-incrementing integers, ULIDs), inserts cluster at the right edge of the index. The hot page stays hot. Cache hits dominate. Throughput holds.
For a small app this is invisible. At 1k inserts per second on a 100M-row table, it's a 3-10× performance gap.
Where v7 is the answer
- Database primary keys for any table you expect to grow past a few million rows.
- Sortable identifiers in event streams, logs, or queues where ordering matters and you don't want a separate timestamp column.
- Distributed systems where multiple writers each generate IDs locally and you want monotonic-ish ordering without coordination.
Where v4 is still the right answer
- Security tokens. Session IDs, password reset tokens, API keys, magic links. A v7 ID leaks the time the token was issued through its first 48 bits. That's a side channel you usually don't want. Use v4 (or better: a 256-bit random token) for anything that must be unguessable.
- Public-facing identifiers where you don't want enumeration. A timestamp prefix lets an attacker correlate IDs across the system.
- Pre-9562 codebases where existing tooling assumes random distribution. The migration cost is rarely worth it for tables that aren't insert-heavy.
Library support in 2026
UUID v7 generation is available in every major language:
- Python: standard library
uuidmodule ships v7 in 3.14+;uuid7oruuid-utilson earlier versions - JavaScript / Node:
uuidpackage v9.0.0+ exportsv7 - Go:
github.com/google/uuidv1.6.0+ providesNewV7() - Rust:
uuidcrate v1.10+ with thev7feature flag - Java: JDK 25 (October 2025) added
UUID.timeOrderedEpochUUID();java-uuid-generatoron earlier versions - C#: .NET 9 added
Guid.CreateVersion7() - PHP:
ramsey/uuidv4.7+ providesUuid::uuid7() - Postgres: Postgres 18 (Q4 2025) shipped native
uuidv7(); SQL functions work on earlier versions
We have language-specific code examples for each of these. Each page shows the v4 and v7 calls with the exact package or import.
How to actually migrate
If you have an existing system on v4 and you're feeling the pain at insert-time, the migration is straightforward but not free:
- Add a new column
id_v7to the affected tables, populated by your application on new rows. - Backfill old rows with reconstructed v7 IDs that preserve creation order (
gen_random_uuid()won't work — you need v7 generation againstcreated_at). - Update foreign keys to reference the new column.
- Drop the old primary key, promote
id_v7.
For most teams, the right time to do this is during a routine schema change anyway. For greenfield projects in 2026, just use v7 from day one. There's no remaining reason to default to v4 for primary keys.
Try the generator
If you just need to produce a few UUIDs right now, our bulk UUID generator produces up to 1000 v4 or v7 IDs at once, with one-click copy and JSON/CSV export. Runs in your browser, no signup.
More posts
Why your AI agent costs 10× what you expected
Agents look cheap in the demo and expensive in production. The gap is almost always one of four things — context bloat, retries, tool-call cascades, or the wrong model. Here's the math.
Prompt injection in production: the defenses that work
Most prompt injection mitigations advertised online don't survive contact with a determined adversary. Here are the four that do — used together, not in isolation.
MCP vs function calling: when each one wins
Function calling and MCP solve overlapping problems with different tradeoffs. Here's the decision tree we use — and the costs that bite when you pick wrong.