UUID v4 vs v7: Which Should You Actually Use?
If you've spent any time building databases or distributed systems, you've probably just defaulted to UUID v4 without thinking too hard about it. Random, universally unique, no coordination needed — what's not to love? Quite a bit, it turns out, once you hit scale. UUID v7 landed in the RFC 9562 standard in 2024, and it addresses the single biggest complaint developers have had about v4 for years: the index performance problem.
This isn't a theoretical debate. The choice between v4 and v7 has real consequences for query speed, database bloat, and even how easy it is to debug production issues at 2am. Let's get into the specifics.
What Actually Distinguishes These Two Formats
UUID v4 is pure randomness, almost. 122 bits of it, with 6 bits reserved for version and variant identifiers. You get something like f47ac10b-58cc-4372-a567-0e02b2c3d479 — completely unpredictable, no hidden structure, no embedded timestamp. That's by design.
UUID v7 keeps the 128-bit format but reorganizes it. The first 48 bits hold a Unix timestamp in milliseconds. The next 12 bits are a sequence counter (useful for UUIDs generated within the same millisecond). The remaining 62 bits are random. So a v7 UUID looks structurally similar but carries time information baked into its most significant bits: 018f2d8a-1c40-7e3a-8b5c-2d4f9a1e6c08. The leading segment encodes when it was created.
That structural difference is everything.
The Index Performance Problem with v4
Here's the situation that breaks UUID v4: you have a table with millions of rows, a primary key column of type UUID, and a B-tree index on it. Every time you insert a new row, the database has to figure out where in the B-tree this new random UUID belongs. Because v4 UUIDs are random, new entries scatter across the entire index — there's no pattern, no locality, no clustering. The database is constantly jumping to non-sequential pages, loading them into memory (or from disk), making space, and writing back.
This phenomenon has a name: index fragmentation. And it compounds over time. On PostgreSQL, heavy UUID v4 primary key tables are notorious for bloat. The index ends up with half-empty pages everywhere because random insertions don't fill pages sequentially. You can VACUUM and REINDEX, but you're fighting the fundamental nature of the data.
Benchmarks vary by workload and database engine, but it's common to see UUID v4 tables require 2–3x more index space than an equivalent auto-increment integer table, with insert throughput dropping significantly under sustained load — sometimes 40–60% slower than sequential keys at high volumes. MySQL's InnoDB, which uses clustered primary key indexes, suffers particularly badly here because every insert potentially triggers page splits.
How v7 Solves This
Because the timestamp occupies the most significant bits, UUID v7 values generated over time are monotonically increasing. Not strictly monotonically — two UUIDs created in the same millisecond could order either way depending on the random portion — but close enough that insertions follow a generally append-only pattern into the index.
The B-tree loves this. New entries cluster at the "right end" of the index, the same way auto-increment integers do. Pages fill sequentially and then new pages are allocated. Fragmentation stays low. Cache hit rates improve because recently-inserted records are grouped together and likely to be in the buffer pool when queried.
In practice, UUID v7 tables tend to perform comparably to integer primary key tables for insert-heavy workloads, while still giving you the distribution and coordination-free generation benefits of UUIDs. That's a meaningful improvement — you're not sacrificing much to get it.
Sortability and What You Can Do With It
Lexicographic ordering of UUID v7 values maps to chronological order. This opens up patterns that v4 simply can't support:
- Cursor-based pagination: Instead of storing a separate
created_atcolumn and paginating on that, you can page directly on the UUID primary key.WHERE id > :last_seen_id ORDER BY id LIMIT 50gives you chronological pagination with a single indexed column. - Range scans by time: If you know the approximate timestamp range you care about, you can construct UUID v7 values for the boundaries and do a range scan on the primary key rather than a secondary index lookup.
- Visual debugging: When you're grepping logs and see a UUID, with v7 you can extract the timestamp from the first 48 bits and immediately know when that record was created. With v4, you know nothing.
That last point is underrated. During an incident, being able to look at a UUID in a log line and know it was created at 14:32:07.441 UTC can save meaningful time.
Collision Probability: Is There a Real Difference?
Both formats are effectively collision-proof at any reasonable scale, but let's be precise about it.
UUID v4 has 122 bits of randomness. To reach a 50% collision probability, you'd need to generate approximately 2.71 × 1018 UUIDs. That's 2.71 quintillion. If you generated a billion UUIDs per second, it would take over 85 years to hit that threshold. In practice, no system comes remotely close.
UUID v7 has 62 bits of randomness per millisecond (plus the 12-bit counter for same-millisecond uniqueness). Within a single millisecond on a single node, you have 4,096 sequence counter slots before you'd need the random portion to differentiate. The collision math is more nuanced because the timestamp partitions the problem, but the net effect is comparable safety.
The only scenario where v7 becomes slightly riskier is if you're generating enormous volumes of UUIDs within the same millisecond across multiple nodes with no coordination. Each node has its own random suffix, so cross-node collisions are still astronomically unlikely — but it's worth knowing the structure if you're doing something unusual at extreme scale.
For virtually every application, both formats are equally safe from collisions.
When v4 Still Makes Sense
UUID v4 isn't obsolete. There are specific situations where you should still reach for it:
When opacity is a security requirement. UUID v7 leaks information: anyone holding a v7 UUID knows approximately when the entity was created. For some systems — security tokens, session identifiers, public-facing IDs for sensitive resources — that's a problem. A user shouldn't be able to infer from their account UUID when they signed up, or use timestamp-based patterns to enumerate other accounts. Use v4 when the ID must reveal nothing.
When you're not using a B-tree index. If you're storing IDs in a hash index, a NoSQL document store that doesn't care about ordering, or a system where keys are looked up only by exact match, the sequential ordering benefit of v7 doesn't apply. v4 is fine.
When backwards compatibility matters. If your system already has millions of v4 UUIDs in production and you're considering migration, the operational cost might outweigh the performance gain. Adding a separate created_at column and a good secondary index is often less disruptive than migrating primary keys.
Library Support Right Now
UUID v7 is newer, so support varies. In JavaScript, the uuid package (v9+) has uuidv7(). Python's standard library uuid module added v7 in Python 3.13; before that, uuid7 on PyPI fills the gap. Go has third-party packages like github.com/google/uuid which added v7 support. Java's java.util.UUID still only covers v1-v4 natively — you'll need a library like uuid-creator.
Database-side generation is patchier. PostgreSQL 17 introduced uuidv7() as a built-in function. Earlier versions need an extension or application-layer generation. MySQL doesn't have native v7 support yet. If you're letting the database generate UUIDs, check what your version actually supports before committing to a design.
The Recommendation
If you're starting a new system today, default to UUID v7 for primary keys and any ID that will be indexed and queried at scale. The performance characteristics are strictly better for database workloads, the sortability is a genuine bonus, and the collision safety is identical for all practical purposes. The only reasons to deviate are the specific cases above — opacity requirements, non-B-tree storage, or backwards compatibility constraints.
If you're on an existing v4 system, don't panic-migrate. Evaluate whether you're actually hitting the performance ceiling. Add monitoring, look at your index bloat numbers, and migrate new tables to v7 while leaving existing tables alone unless there's a clear problem.
UUID v4 was the right default for a long time because nothing better was standardized. That's changed. v7 is the better general-purpose choice for most database-backed applications in 2024 and beyond.