All articles
169 articles · updated weekly See our Tools
All articles
Comparisons

UUID v4 vs UUID v7: What's the Difference and Which Should You Use?

UUID v4 is pure random; UUID v7 embeds an ordered timestamp. The difference seems small, but it completely changes index performance on large tables.

UUID v4 vs UUID v7: What's the Difference and Which Should You Use?
COVER · Comparisons

I was modeling an event table — high INSERT frequency, date-based pagination, no separate timestamp index — and had to decide between UUID v4 and v7 for the primary key. I went and read RFC 9562 rather than guessing. What looked like an implementation detail turned into an architecture decision with measurable performance implications.

If you're not yet clear on what a UUID is and what it's for, the post What Is a UUID? covers the fundamentals before diving into this comparison.

Bit structure: where the two actually differ

A UUID is 128 bits. What changes between versions is how those bits are used.

UUID v4 dedicates 6 fixed bits to version and variant; the remaining 122 bits are purely random. The result is a globally unique, completely unpredictable identifier — no embedded information, no implicit ordering.

UUID v7, defined in RFC 9562 (2024), uses the first 48 bits to store the Unix timestamp in milliseconds. The remaining 74 bits are random (plus the 6 version/variant bits). The layout looks like this:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

In practice, a UUID v7 generated right now starts with 018fea... — the prefix changes as time passes, but always in ascending order. A UUID v4 generated at the same instant can start with anything: 7f3c9a, 2b0d41, e8fa12.

Why sortability matters in databases

Most relational databases use B-tree indexes for primary keys. A B-tree performs best when data is inserted in approximately ascending order: the active page stays hot in the buffer pool, fragmentation is minimal, and the number of page splits is low.

With UUID v4, each INSERT lands at a random position in the tree. On small tables, this is imperceptible. On tables with tens of millions of rows, the effects are concrete:

  • Page fragmentation: the database must split pages frequently to accommodate out-of-order keys, increasing disk usage and degrading sequential reads.
  • Buffer pool thrashing: since each INSERT may hit a different page, the database needs to load pages from disk more often. The effective working set grows continuously.
  • INSERT performance: in PostgreSQL benchmarks, degradation with random UUIDs v4 starts to appear around 10–50 million rows, depending on hardware and configuration.

With UUID v7, new IDs are always greater than previous ones (within the same millisecond there is still randomness, but the temporal prefix guarantees approximate order). The behavior is similar to a BIGSERIAL or IDENTITY, but with the advantage of being globally unique without central coordination.

What they look like in practice

Fictional for illustration, but the structure reflects reality:

-- UUID v7: chronologically sortable
018fea1c-27ab-7abc-8def-123456789abc
018fea1c-27ac-7def-9012-abcdef012345
018fea1c-27ad-71a2-b345-678901234567

-- UUID v4: completely random
550e8400-e29b-41d4-a716-446655440000
f47ac10b-58cc-4372-a567-0e02b2c3d479
6ba7b810-9dad-11d1-80b4-00c04fd430c8

Sorting the first list by value returns rows in creation order. Sorting the second returns a sequence with no temporal meaning.

You can generate both formats directly in the browser with the UUID Generator — useful for testing behavior before committing to a schema.

Collision: is the entropy still sufficient in v7?

This is the question everyone asks when they see that v7 "lost" 48 bits of randomness to the timestamp.

With v4, you have 122 bits of entropy. The probability of collision between two independently generated UUIDs is approximately 1 in 5.3 × 10³⁶ — a number so large it's useless to try to visualize.

With v7, within a single millisecond, you have 74 bits of random entropy. That gives approximately 1.9 × 10²² possible combinations per millisecond. For any practical system — even those generating millions of events per second — the collision risk within the same millisecond is negligible. RFC 9562 discusses additional strategies for extremely high-frequency systems (such as using a monotonic counter in the rand_a bits), but for the vast majority of use cases, 74 bits per millisecond is more than enough.

Database support

Native support varies significantly by database:

PostgreSQL: gen_random_uuid() generates v4 natively since version 13. For v7, the simplest option through PostgreSQL 17 is generating in the application — the pgcrypto extension doesn't offer v7 yet. PostgreSQL stores UUIDs in the native uuid type (16 bytes), which is more efficient than TEXT.

MySQL / MariaDB: no native support for any version. The historical recommendation was to use UUID_TO_BIN(UUID(), 1) (swap mode that reorders the temporal bytes of v1), but with v7 generated in the application you don't need that hack. For performance, use BINARY(16) instead of CHAR(36) — the difference in space and comparison speed is significant.

SQLite: stores as TEXT or BLOB. For SQLite, the choice between v4 and v7 has less impact because the database doesn't have a complex buffer pool or B-tree fragmentation in the same way as heavy relational databases.

On storage: if the database has a native uuid type, use it. If not, BINARY(16) is preferable to VARCHAR(36) — you save 20 bytes per row and eliminate string comparisons.

Language and library support

JavaScript/TypeScript: crypto.randomUUID() is available natively in the browser and Node.js 19+ — but generates only v4. For v7, the uuidv7 library (less than 2 KB minified) is the most straightforward option:

import { uuidv7 } from 'uuidv7';

const id = uuidv7(); // "018fea1c-27ab-7abc-8def-123456789abc"

Python: the standard library uuid supports v4 natively (uuid.uuid4()). For v7, use uuid-utils or python-uuid7.

Go: github.com/google/uuid supported v7 starting from version 1.6.

Rust: the uuid crate with the v7 feature generates UUID v7 since version 1.7.

Why v1 fell out of use

Before v7, anyone who wanted sortability used UUID v1 — which also embeds a timestamp. The problem is that v1 uses the machine's MAC address as part of the identifier. This raises privacy concerns (it's possible to trace which machine generated the UUID) and creates problems in containerized environments where the MAC can be generic or repeated.

v7 solves the same ordering problem without exposing any information about the infrastructure. It's a direct replacement for v1 in use cases where sortability was the only reason to choose it.

When to use each one

Use v4 when:

  • The identifier is a session token, password reset link, or anything where you actively don't want it to be predictable or sortable.
  • You're integrating with legacy systems that expect v4 UUIDs and ordering is irrelevant to the use case.
  • The table is small and will never grow to the point where index fragmentation is a real problem.

Use v7 when:

  • The UUID is a primary key for a table that grows continuously (logs, events, transactions, messages).
  • You'll paginate by date using the PK as a time proxy.
  • You want to migrate from BIGSERIAL/IDENTITY to UUIDs without losing the natural ordering that makes debugging and analysis easier.
  • You're in a multi-service environment where generating IDs in the application (rather than the database) simplifies the architecture.

The direct take

Use v7 for primary keys on tables that grow. The argument that "v4 is good enough" ignores the real cost of index fragmentation at scale — a cost that appears gradually and that nobody notices until the table has 50 million rows and INSERTs start taking twice as long.

v4 has its place: security tokens, IDs that won't be used as PKs, contexts where unpredictability is a desirable property. But as a PK for a high-write table, v7 has no practical disadvantage over v4 — only advantages.

RFC 9562 was published in 2024 and supersedes RFC 4122. The change wasn't motivated by trends; it was because the real-world usage patterns of UUIDs evolved and sortability proved important enough to warrant a slot in the official specification.

Frequently asked questions

Is UUID v7 slower to generate than v4?

Not perceptibly. Both depend on a cryptographically secure pseudo-random number generator (CSPRNG) for the random part. The difference is that v7 also reads the current timestamp — a nanosecond operation. In benchmarks, the generation difference is under 1% and irrelevant for any practical application.

Can I migrate from v4 to v7 on an existing table?

Not directly. Existing PKs remain v4 and new ones would be v7 — the index would have v4 data mixed with v7, without gaining the ordering advantage. The practical migration involves creating a new table with v7 as the PK and migrating data, or accepting a transition period where the index is partially out of order. For new tables, the decision is straightforward: start with v7 from the beginning.

Is UUID v7 a recognized standard or still experimental?

It's a standard. RFC 9562 was published by the IETF in May 2024 and is the official document defining UUID versions 1 through 8. v7 is fully specified and stable — not a proposal or draft.

What's the difference between UUID v7 and ULID?

ULID (Universally Unique Lexicographically Sortable Identifier) also uses timestamp + randomness and was popular before UUID v7 was standardized. The main difference is the format: ULID uses base32 (26 characters), while UUID v7 uses the standard UUID format with hyphens (36 characters). With v7 standardized in RFC 9562, ULID lost most of its raison d'être — UUID v7 solves the same problem within the ecosystem of tools that already support UUIDs.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile