UUID v4 vs v7 for Databases: The Benchmark You Need
UUID v4 fragments your primary key index. UUID v7 fixes it with millisecond-ordered timestamps. Here's the benchmark data, why it matters, and when to switch.
On this page ⌄
UUID v4 generates 122 bits of randomness. That's its entire feature set. It's collision-resistant, globally unique, and completely unordered. For decades "use UUID v4 as your primary key" was treated as obvious good practice.
It causes a measurable, documented performance problem in databases at scale, and UUID v7, finalized in RFC 9562 (May 2024), fixes it without giving up uniqueness or collision resistance.
The UUID Generator on this site supports both v4 and v7. Here's which one to reach for and why.
The B-tree problem with random UUIDs
Almost every production database uses B-tree indexes for primary keys: PostgreSQL, MySQL, SQLite, SQL Server. B-trees maintain keys in sorted order in fixed-size pages (typically 8-16 KB).
When you insert a row with a sequential or monotonically increasing key, the new key goes to the rightmost page of the index, the "hot page." Writes cluster at the end. This is cache-friendly: the hot page stays in memory, and the rest of the tree doesn't need to be touched.
When you insert a row with a random UUID v4 key, the new key can land anywhere in the index. With 10 million rows and 8 KB pages, there are roughly 50,000+ leaf pages. A new UUID v4 insertion hits a random one. If that page has been evicted from the buffer pool (which it has, you haven't touched it recently), the database must read it from disk, insert the row, mark the page dirty, and potentially split it if it's full.
This causes three cascading problems:
- Page splits: random inserts fill pages out-of-order, causing frequent splits that fragment the tree
- Buffer pool thrash: writes scatter across thousands of pages, none of which stay hot
- Write amplification: each insert triggers random reads before the write can happen
At small scale (under ~1 million rows), none of this is noticeable. At 10-100 million rows with moderate write throughput, the effect is real.
Benchmark numbers
Markus Winand (Use The Index, Luke) and several PostgreSQL / MySQL community benchmarks document roughly a 2-3x throughput penalty for random UUID v4 vs sequential integer primary keys on INSERT-heavy workloads. The exact penalty depends on:
- Table size vs buffer pool size (once the index fits in memory, penalty shrinks)
- Write/read ratio
- Storage type (SSDs reduce the random-read penalty)
UUID v7, because it embeds a millisecond timestamp in the high bits, produces monotonically increasing values within a millisecond window. New rows cluster at the end of the index. The fragmentation problem disappears.
What UUID v7 looks like
UUID v7 (RFC 9562) format:
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 (48 bits) │
├─────────────────────────────────────────────────────────────────┤
│ ver(4) │ rand_a (12 bits) │
├─────────────────────────────────────────────────────────────────┤
│ var(2) │ rand_b (62 bits) │
└─────────────────────────────────────────────────────────────────┘
The first 48 bits are the Unix timestamp in milliseconds. The remaining 74 bits are random. This means:
- Values generated within the same millisecond differ only in the random portion: still globally unique
- Values generated across milliseconds are strictly monotonically ordered
- The UUID is still globally unique: the 74-bit random portion provides more than enough collision resistance
A v7 UUID looks like 019602e4-8d32-7f4b-8c0a-3b4e9a1f2d56. The timestamp prefix means it sorts in creation order in any lexicographic comparison, including string-typed columns.
Generating UUID v7
UUID v7 is new enough that not all libraries support it natively yet.
// Node, uuid package (v10+)
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// Or use the @uuid-rock/uuid7 package
import { uuidv7 } from '@uuid-rock/uuid7';
# Python 3.12+
import uuid
id = uuid.uuid7()
# Python < 3.12: uuid7 package
from uuid7 import uuid7
id = uuid7()
// Go, google/uuid
import "github.com/google/uuid"
id := uuid.Must(uuid.NewV7())
PostgreSQL doesn't have a native gen_uuid_v7() function yet, but you can use an extension or generate them in application code.
When to use v4 vs v7
Use v7 when:
- The UUID is a primary key on a table that gets significant write volume
- You want lexicographic ordering to reflect creation order (useful for pagination, debugging)
- You're on a new project with no legacy constraints
Use v4 when:
- The UUID is not a primary key (foreign keys, session tokens, correlation IDs)
- You need opaque, unguessable identifiers where embedding a timestamp is a privacy concern (the timestamp in v7 reveals approximate creation time)
- You're maintaining an existing v4 system and the migration cost isn't worth it
The privacy consideration: UUID v7 embeds the millisecond creation timestamp in the first 48 bits. If you're using UUIDs as public-facing identifiers (e.g., in URLs), this leaks when the record was created. For user-facing IDs where you want to obscure creation time, v4 is the right choice.
UUID v7 vs ULID vs NanoID
| Property | UUID v7 | ULID | NanoID |
|---|---|---|---|
| Standard | RFC 9562 | Informal spec | No RFC |
| Ordered | Yes (ms) | Yes (ms) | No |
| Collision-resistant | 74-bit random | 80-bit random | Configurable |
| String form | 36 chars (hyphenated) | 26 chars (Crockford base32) | 21 chars (default) |
| DB type support | Native UUID type in PG/MySQL | Store as varchar | Store as varchar |
| Sortable in UUID type column | Yes | No (needs varchar) | No |
For most database use cases, UUID v7 is the best choice: it has a formal standard, native UUID column type support, and the same ordered-insertion property as ULID without requiring a custom string format.
Migrating from v4 to v7
You cannot change existing primary keys without a full-table migration. The practical approach:
- New tables: use v7 from the start
- Existing high-write tables: generate a migration plan: add a new v7 column, backfill, swap references, drop old column. Painful but worth it for tables > 10M rows with write-heavy workloads
- Low-write tables: v4 is fine to leave; the performance difference is negligible
The UUID Generator generates both v4 and v7, use it to inspect the format and understand the timestamp prefix.
Further reading
- RFC 9562: Universally Unique IDentifiers (UUIDs): the authoritative spec for v4 through v8
- Use The Index, Luke: UUID performance
- Postgres ULID discussion: community analysis of ordered UUID approaches
Keep reading
CUID2: Collision-Resistant IDs Better Than UUID v4
CUID2 generates secure, URL-safe, database-friendly IDs with better collision resistance than UUID v4. Learn how CUID2 differs from UUID, nanoid, and ULID, with JavaScript and...
NanoID vs UUID: Which Unique ID Generator Should You Use?
NanoID generates shorter, URL-safe unique IDs using a custom alphabet. UUID v4 is the standard 128-bit identifier. Here's when to use each and how they compare on size, speed,...
ULID Generator: Universally Unique Lexicographically Sortable Identifiers
ULID is a 26-character sortable unique identifier that embeds a millisecond timestamp prefix. Unlike UUID v4, ULIDs sort chronologically. Here's how ULIDs work and when to use...
UUID Collision Probability: How Likely Are Duplicate UUIDs?
UUID v4 has 122 bits of randomness. Collisions are theoretically possible but astronomically unlikely. Here's the math behind UUID uniqueness and when collision risk actually...
UUID Format: Understanding the 128-Bit Unique Identifier Structure
UUIDs follow a specific 8-4-4-4-12 hexadecimal format defined by RFC 4122. Here's what each section of a UUID means, the version and variant bits, and how to parse UUID fields.
UUIDs in Databases: Performance, Storage, and Best Practices
UUIDs as primary keys have real performance implications in PostgreSQL and MySQL. Learn UUID storage size, index fragmentation with random UUIDs, why UUID v7 is better than v4...
UUID Versions Explained: v1, v3, v4, v5, and v7
UUID has multiple versions with different generation strategies. Learn when to use UUID v4 (random), v5 (name-based SHA-1), v7 (time-ordered), and why v1 and v3 are largely...
UUID v4 Generator: Random UUIDs Explained
UUID v4 uses random bits to generate universally unique identifiers. Here's how v4 differs from other UUID versions, what makes them unique, and when to use UUID v4 vs...
Related tools
UUID Generator
Generate UUID v4 and v7 identifiers in bulk.
QR Code Generator
Generate QR codes for URLs, text, Wi-Fi, contact cards. Custom size, colors, error correction. Download as PNG or SVG. 100% client-side.
Barcode Generator
Generate Code 128, Code 39, EAN-13, UPC-A, EAN-8 and ITF barcodes as SVG with automatic check digit calculation.
Morse Code Translator
Translate text to Morse code and Morse back to English, live. Play it as audio at any speed, with a full alphabet chart.
Written by Mian Ali Khalid. More guides in Encoding & Crypto.