UUID v4 vs UUID v7: The Ultimate Guide for Database Primary Keys & Performance
Universally Unique Identifiers (UUIDs) have been the backbone of distributed systems for decades. Whether you are generating transaction IDs across distributed microservices, assigning keys in offline-first mobile apps, or avoiding auto-increment enumeration vulnerabilities in REST APIs, UUIDs provide unique identity without requiring a centralized coordinator.
However, standard UUID version 4 (random) has long had a major architectural flaw: it causes severe B-Tree index page splitting and fragmentation in relational databases like PostgreSQL, MySQL, and SQLite.
Enter UUID version 7 (Unix Epoch time-based), standardized in RFC 9562. In this guide, we dive deep into how UUID v4 and v7 work, benchmark their database performance, and explain when to switch.
1. Anatomy of a UUID: How They Are Built
A UUID is a 128-bit integer, conventionally formatted as a 36-character hexadecimal string divided into five groups separated by hyphens (8-4-4-4-12):
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Where:
- The
Mdigit indicates the UUID Version (e.g.,4for random,7for Unix-epoch time-based). - The
Ndigit (the top bits of the 4th block) denotes the UUID Variant (typically8,9,a, orbrepresenting RFC 4122 / RFC 9562).
UUID v4: Pure Pseudorandomness
UUID v4 contains 122 bits of cryptographic entropy. Only 6 bits are reserved for version and variant metadata.
- Pros: Unpredictable, impossible to reverse-engineer timestamp or origin node. Zero coordination needed.
- Cons: Completely unordered. Consecutive IDs generated milliseconds apart land in random positions across the 128-bit keyspace.
UUID v7: Time-Ordered Entropy
UUID v7 dedicates its first 48 bits to an accurate Unix timestamp in milliseconds, followed by 4 bits for version (7), 12 bits of optional sub-millisecond precision or monotonic counter, 2 bits for variant, and 62 bits of cryptographic randomness.
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Because the leading 48 bits increment monotonically over time, consecutive UUID v7 values sort naturally in chronological order.
2. The Database Problem: Why UUID v4 Destroys B-Tree Indexes
Relational databases store primary keys in B-Trees. In a B-Tree, keys are maintained in sorted order within fixed-size 8KB or 16KB disk pages.
The Problem with Random v4 Inserts
When you insert rows with random UUID v4 keys:
- Every new row belongs at a completely random location in the tree.
- If the target 8KB page is full, the database must execute an expensive page split—allocating a new page and copying half the rows over.
- As the table grows beyond RAM (buffer cache), every insert requires fetching a random disk block from SSD/HDD, destroying cache locality.
- Index pages become half-empty, wasting 30% to 50% of your disk and memory bandwidth.
Random UUID v4 Inserts:
Page 1 [ 01a... | 14f... | 29b... ] -> SPLIT! (Random write)
Page 2 [ 67c... | 89d... | a1e... ] -> SPLIT! (Random write)
Page 3 [ b3f... | e4a... | ffe... ] -> SPLIT! (Random write)
The Solution with UUID v7
Because UUID v7 contains a millisecond timestamp prefix, new records always append to the right-most leaf page of the B-Tree index:
- Zero unnecessary page splits.
- 99%+ buffer pool cache hit ratio for recent inserts.
- Up to 4x faster write throughput on high-volume tables.
3. Comparison Matrix: UUID v4 vs v7 vs ULID vs BigInt
| Feature | Auto-Increment BigInt | UUID v4 | ULID | UUID v7 |
|---|---|---|---|---|
| Size | 64 bits (8 bytes) | 128 bits (16 bytes) | 128 bits (16 bytes) | 128 bits (16 bytes) |
| Sortable | Yes (Sequential) | No (Random) | Yes (Lexicographical) | Yes (Chrono/Binary) |
| Standardized | SQL Native | RFC 4122 | Community Spec | RFC 9562 |
| Database Native | Yes | Yes (uuid) | Custom string | Native (uuid) |
| Enumeration Attack Proof | No (Trivial to guess) | Yes | Yes | Yes |
| Distributed Safe | No (Central lock needed) | Yes | Yes | Yes |
| B-Tree Friendly | Excellent | Very Poor | Excellent | Excellent |
4. How to Generate UUIDs in Code
JavaScript / TypeScript (Node.js & Modern Browsers)
For standard UUID v4, modern JavaScript has native support:
// Native UUID v4 in all modern runtimes
const v4 = crypto.randomUUID();
console.log(v4); // "3b241101-e2bb-4255-8caf-4136c566a964"
To generate UUID v7 in modern Node.js or browsers:
function generateUuidV7() {
const now = Date.now();
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
// Set 48-bit timestamp
bytes[0] = (now / 0x10000000000) & 0xff;
bytes[1] = (now / 0x100000000) & 0xff;
bytes[2] = (now / 0x1000000) & 0xff;
bytes[3] = (now / 0x10000) & 0xff;
bytes[4] = (now / 0x100) & 0xff;
bytes[5] = now & 0xff;
// Set version 7 (0b0111)
bytes[6] = (bytes[6] & 0x0f) | 0x70;
// Set variant RFC 9562 (0b10)
bytes[8] = (bytes[8] & 0x3f) | 0x80;
return [...bytes]
.map((b, i) => ([4, 6, 8, 10].includes(i) ? '-' : '') + b.toString(16).padStart(2, '0'))
.join('');
}
console.log(generateUuidV7()); // "01925b6a-93f4-7e81-9b12-984e4f9b8821"
Python
Using Python 3.12+:
import uuid
# UUID v4
id_v4 = uuid.uuid4()
print(f"UUID v4: {id_v4}")
# In Python 3.13+, UUID v7 is natively supported via uuid.uuid7()
# For older versions, use the 'uuid6' or 'uuid-utils' package:
# pip install uuid6
import uuid6
id_v7 = uuid6.uuid7()
print(f"UUID v7: {id_v7}")
PostgreSQL 17+ Native Support
In upcoming PostgreSQL distributions, uuid_generate_v7() or gen_random_uuid_v7() will be available natively without external extensions, storing IDs in standard 16-byte UUID columns.
5. Security & Privacy Considerations
While UUID v7 solves the performance hurdle, there is one security factor to remember:
Information Leakage: Because the first 48 bits encode the exact millisecond timestamp of generation, an attacker can extract when an object was created.
- When to use UUID v7: Primary keys, internal user IDs, order records, logs, audit trails, and distributed tracing.
- When to stick with UUID v4: Password reset tokens, API session bearer tokens, unguessable share links, and security secrets where generation timestamps must remain confidential.
6. Summary: The Verdict for 2026
If you are starting a new database schema or scaling a high-throughput backend, UUID v7 is the superior choice for primary keys. It combines the distributed safety and unguessability of UUIDs with the clustering efficiency of traditional sequential IDs.
Need to generate or validate UUIDs right now? Try our free, client-side UUID Generator for instant cryptographic identifier generation with zero tracking.