UUID v7 vs UUID v4: Which Should You Use for Database Primary Keys?

UUID v7 vs UUID v4: Which Should You Use for Database Primary Keys?
UUID v7 and UUID v4 both produce globally unique 128-bit identifiers, but they work very differently. UUID v4 is entirely random. UUID v7 embeds a millisecond-precision Unix timestamp in its first 48 bits, making it lexicographically sortable by creation time — a property that has a significant impact on database index performance.
This guide explains the technical differences, shows real benchmark data, and helps you decide which format is right for your next project.
Table of Contents
- What Changed: RFC 9562 and the New UUID Versions
- UUID v4: The Random Standard
- UUID v7: Time-Ordered and Index-Friendly
- Why Sort Order Matters for Database Indexes
- UUID v7 vs UUID v4: Performance Benchmarks
- How to Generate UUID v7
- When to Use UUID v4 vs UUID v7
- Frequently Asked Questions
What Changed: RFC 9562 and the New UUID Versions
In May 2024, the IETF published RFC 9562, which superseded the original UUID specification (RFC 4122) and formally defined three new UUID versions: v6, v7, and v8.
UUID v7 is the most practically significant of the new versions. It was designed specifically to solve the database index fragmentation problem caused by random UUIDs — a problem developers have been working around with custom solutions (ULIDs, CUIDs, Twitter Snowflakes) for over a decade.
RFC 9562 is now the standard. UUID v7 is no longer experimental.
UUID v4: The Random Standard
UUID v4 has been the default choice for application-generated identifiers since the early 2000s. Its structure is simple: 122 bits of cryptographically random data, with 6 bits reserved for the version and variant markers.
UUID v4 format:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
Example:
f47ac10b-58cc-4372-a567-0e02b2c3d479The 4 in the third group indicates version 4. The y is a variant marker (8, 9, a, or b). Everything else is random.
Strengths of UUID v4:
- Extremely simple to generate — every language has a built-in function
- No coordination required between services
- Maximum privacy: no time or machine information is embedded
- 2^122 possible values — collision probability is negligible
Weaknesses of UUID v4:
- Entirely random → new UUIDs insert at random positions in B-tree indexes
- Random insertion causes index page splits and fragmentation over time
- Cannot be sorted by creation time without a separate
created_atcolumn - No inherent ordering makes cursor-based pagination harder to implement
UUID v7: Time-Ordered and Index-Friendly
UUID v7 uses a 48-bit Unix timestamp (milliseconds since epoch) in its most-significant bits, followed by a version marker, an optional sub-millisecond sequence counter, and random bits for uniqueness.
UUID v7 format:
tttttttt-tttt-7xxx-yxxx-xxxxxxxxxxxx
│ │
└─ 48-bit Unix └─ version 7
timestamp (ms)
Example (generated at 2026-04-24T10:30:00.000Z):
01966b5f-7400-7a3c-b891-2f4d8e1c0a72The first 12 hex characters (01966b5f-7400) encode the timestamp. Any UUID v7 generated after this one will have a lexicographically larger value — meaning UUIDs sort in the same order they were created.
Strengths of UUID v7:
- Time-ordered: new rows always append near the end of the B-tree index
- Dramatically reduces index fragmentation and page splits
- Globally unique without coordination (timestamp + random bits)
- Sortable by creation time without a separate timestamp column
- Compatible with existing UUID storage (same 128-bit / 36-char format)
Weaknesses of UUID v7:
- Slightly more complex to generate (requires a reliable clock source)
- Embeds creation time — a minor privacy consideration for public-facing IDs
- Not yet natively available in all language standard libraries (though coverage is growing rapidly)
Why Sort Order Matters for Database Indexes
Most databases use a B-tree data structure for primary key indexes. B-trees keep rows sorted. When you insert a new row, the database finds the correct sorted position and inserts the key there.
With UUID v4, every new row inserts at a random position in the index. This means:
- The database must find a random leaf page in the B-tree
- If that page is full, it splits into two pages (a page split)
- Over time, many pages become half-empty — this is called index fragmentation
- Fragmented indexes consume more memory, produce more I/O, and slow down range scans
With UUID v7, every new row inserts at the end of the index (because the timestamp is always increasing). This is identical to the behaviour of AUTOINCREMENT integer keys — the best-case scenario for B-tree indexes.
The practical result: UUID v7 primary keys behave like integer primary keys for insert performance, while retaining all the distributed-system advantages of UUIDs.
UUID v7 vs UUID v4: Performance Benchmarks
The performance gap between UUID v4 and UUID v7 grows significantly as tables get larger. Here are representative results from PostgreSQL benchmarks at different table sizes:
| Rows | UUID v4 insert/sec | UUID v7 insert/sec | UUID v4 index size | UUID v7 index size |
|---|---|---|---|---|
| 1 million | ~45,000 | ~48,000 | 57 MB | 54 MB |
| 10 million | ~22,000 | ~44,000 | 587 MB | 548 MB |
| 100 million | ~8,000 | ~42,000 | 5.9 GB | 5.4 GB |
At 100 million rows, UUID v7 inserts more than 5x faster than UUID v4 in PostgreSQL. Index size is also reduced by approximately 9% due to lower fragmentation and more efficient page utilisation.
MySQL/InnoDB shows similar patterns, because InnoDB clusters the entire table on the primary key — meaning random UUID v4 inserts cause whole-row relocation, not just index page splits.
How to Generate UUID v7
UUID v7 support is being added to language runtimes rapidly following RFC 9562. Here is how to generate UUID v7 across common environments:
Node.js / TypeScript
// Using the 'uuidv7' package (npm install uuidv7)
import { uuidv7 } from 'uuidv7';
const id = uuidv7();
// → '01966b5f-7400-7a3c-b891-2f4d8e1c0a72'Python
# Using 'uuid-utils' (pip install uuid-utils) — Rust-backed, fast
import uuid_utils as uuid
id = uuid.uuid7()
print(str(id))
# → '01966b5f-7400-7a3c-b891-2f4d8e1c0a72'PostgreSQL (pgcrypto extension)
-- PostgreSQL 17+ has native uuid_generate_v7()
SELECT uuid_generate_v7();
-- For older versions, use the pg_uuidv7 extension
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
SELECT gen_random_uuid_v7();Go
// Using 'github.com/google/uuid'
import "github.com/google/uuid"
id, err := uuid.NewV7()
// → "01966b5f-7400-7a3c-b891-2f4d8e1c0a72"Java / Kotlin
// Using 'com.fasterxml.uuid:java-uuid-generator'
import com.fasterxml.uuid.Generators;
UUID id = Generators.timeBasedEpochGenerator().generate();
// → UUID v7You can also use our free UUID Generator to generate UUID v4 identifiers instantly in your browser.
When to Use UUID v4 vs UUID v7
Choose UUID v7 when:
- Building a new application with PostgreSQL, MySQL/InnoDB, or SQLite
- Your table will have more than ~1 million rows
- You want cursor-based pagination using the primary key (the timestamp ordering makes this trivial)
- You want insert performance comparable to integer primary keys
- You are designing a distributed system where multiple services generate IDs independently
Choose UUID v4 when:
- You need maximum privacy and do not want any time information embedded in public-facing identifiers
- You are adding IDs to an existing system already using UUID v4 and migration cost is not justified
- Your language or framework does not yet have a reliable UUID v7 library and you cannot add a dependency
- You are generating IDs for security-sensitive tokens (e.g., password reset tokens) where unpredictability is more important than sortability
Keep using integers when:
- You are building a simple single-server application with no distributed ID generation requirement
- Maximum insert speed and minimum index overhead are critical and you have no need for distributed uniqueness
Summary
UUID v7 is the right default for new applications in 2026. It delivers the distributed uniqueness of UUID v4 with index performance approaching that of auto-increment integers, and it is now standardised in RFC 9562. The only real trade-off is slightly reduced privacy (the timestamp is visible in the UUID) and the need to use a library for generation until standard runtimes add native support.
If you are starting a new project and choosing between UUID v4 and UUID v7 — start with v7.
Related tools: Use our free UUID Generator to generate UUID v4 identifiers instantly in your browser, or explore our other developer utilities for everyday coding tasks.
Frequently Asked Questions
Is UUID v7 backwards compatible with UUID v4?
Yes. UUID v7 uses the same 128-bit / 36-character string format as UUID v4. Any system that stores UUIDs as CHAR(36), VARCHAR(36), or a native UUID type will accept UUID v7 without schema changes. The version number (7 instead of 4) is embedded in the UUID itself and does not affect storage.
Does UUID v7 guarantee strict ordering across multiple servers?
Not strictly. UUID v7 guarantees monotonic ordering within a single generator — each new UUID is greater than the previous one. Across multiple servers, UUIDs generated within the same millisecond from different machines may interleave, but they will still cluster within that millisecond window. For strict global ordering, you need a centralised sequence generator, which defeats the purpose of distributed UUID generation.
What happened to UUID v5 and v6?
UUID v5 (name-based SHA-1 hash) was defined in RFC 4122 and remains valid for deterministic ID generation from a fixed namespace and name. UUID v6 is a time-ordered variant of the older UUID v1 (timestamp + MAC address) that reorders the timestamp bits for sortability — it was included in RFC 9562 for backwards compatibility with systems using v1. For new projects, UUID v7 is preferred over v6 because it does not embed MAC address information.
Can I use UUID v7 with an ORM like Prisma or TypeORM?
Yes. Both Prisma and TypeORM allow you to specify a custom default value for primary keys using @default(dbgenerated(...)) in Prisma or a custom generation strategy in TypeORM. Generate UUID v7 at the application layer and pass it in, or configure a database-level extension (like pg_uuidv7 for PostgreSQL) as the default. Application-layer generation is generally preferred as it gives you the ID before the database round-trip completes.
