UUID v4 vs UUID v7: Which One Should Developers Use in 2026?
Last year I spent an afternoon diagnosing why a PostgreSQL table with 12 million rows was taking 40ms per insert. The table used UUID v4 primary keys. Every insert landed in a random position in the B-tree index, causing page splits that cascaded through the entire index structure. The database wasn't CPU-bound or memory-bound -- it was fragmentation-bound.
I migrated the write path to UUID v7. Insert latency dropped to 4ms. Same hardware, same table structure, same application logic. The only change was the UUID version.
This is the UUID v4 vs v7 discussion in a nutshell. UUID v4 solved the uniqueness problem years ago. UUID v7 solves the operational problem that shows up once your system gets real traffic. Here's what you need to know to pick the right one.
How UUID v4 Works
UUID v4 generates 122 bits of cryptographically random data, reserves 6 bits for version and variant markers, and calls it done.
36b8f84d-df4e-4d49-b662-bcde71a8764f
↑
v4 marker
Every language supports it natively:
crypto.randomUUID() // JavaScript
import uuid; uuid.uuid4() # Python
UUID.randomUUID() // Java
The selling point is simplicity. You call one function, you get a globally unique identifier, and the probability of collision is small enough that you don't need to think about it. For a deeper dive into those probabilities, read Can UUIDs Really Collide?.
The problem with UUID v4 isn't correctness. It's mechanical sympathy -- or the lack of it.
The Hidden Cost of Randomness in Databases
B-tree indexes (used by PostgreSQL, MySQL/InnoDB, SQLite) work best when new keys arrive in sorted order. Each new key gets appended to the rightmost leaf page. When a page fills up, it splits once and you move on.
Auto-increment IDs do this naturally:
Insert order: 1 → 2 → 3 → 4 → 5
Index pages: [1|2|3] → [1|2|3|4] → [1|2|3] [4|5]
Smooth appends Clean split
UUID v4 throws randomness at the index:
Insert order: f7a3... → 1d9c... → 8b4f... → 3c11...
Index pages: [1d9c...|3c11...|...] → split → split → split
Every insert potentially splits a random page
Each random insert can trigger a page split anywhere in the index tree. A page split means:
- Allocate a new page.
- Redistribute keys between the old and new page.
- Update the parent page's pointers.
- Write all modified pages to the WAL (write-ahead log).
- Potentially cascade splits up the tree.
At 1,000 rows you won't notice. At 1 million rows with moderate write throughput, you'll see insert latency creep up. At 10 million rows with high write throughput, you'll have a problem.
The database can also cache hot index pages (the rightmost ones) effectively with sequential IDs. UUID v4's random pattern thrashes the buffer pool, forcing more disk reads.
How UUID v7 Fixes This
UUID v7 embeds a 48-bit Unix timestamp (millisecond precision) in the first 48 bits, followed by 74 random bits for uniqueness within the same millisecond.
01911d4b-4c3f-7c21-ae6b-d0a7f72e93f4
│──────│ │──│ │──│ │──│ │──────────│
48-bit 4 4 4 12 (remainder = 74 random bits)
timestamp
This means UUID v7 values sort chronologically. Newer IDs are numerically larger than older ones. The B-tree index sees a steady stream of append-only inserts -- same as auto-increment, same cache behavior, same minimal fragmentation.
The 74 random bits ensure uniqueness within the same millisecond. Two processes generating UUID v7 at the same wall-clock moment have 2^74 ≈ 1.8 × 10^22 possible distinct values. More than enough.
Unlike UUID v1, UUID v7 does not include a MAC address or machine identifier. It preserves privacy while fixing the database performance problem.
Real PostgreSQL Comparison
Let's run through what actually matters. Two tables:
-- UUID v4
CREATE TABLE events_v4 (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
payload JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
-- UUID v7 (using pg_uuidv7 extension or application-side generation)
CREATE TABLE events_v7 (
id UUID PRIMARY KEY,
payload JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
After inserting 10 million rows under similar conditions, typical observations:
| Metric | UUID v4 | UUID v7 |
|---|---|---|
| Insert latency (p99) | 15-40 ms | 2-8 ms |
| Index size | ~420 MB | ~350 MB |
| Index fragmentation | 35-50% | 5-10% |
| Buffer cache hit rate | 85-92% | 96-99% |
| VACUUM overhead | Higher | Lower |
The absolute numbers vary by hardware, PostgreSQL version, and fill factor settings. The direction is consistent: v7 inserts faster, uses less space, and puts less pressure on the buffer pool.
Generating UUID v7 in Your Stack
Support is catching up fast:
JavaScript (Node.js 20.11+, browsers coming):
import { v7 } from 'uuid';
const id = v7(); // => "01911d4b-4c3f-7c21-ae6b-d0a7f72e93f4"
Python:
pip install uuid6
from uuid6 import uuid7
print(uuid7())
Java:
Several libraries now support UUID v7 (including java-uuid-generator and uuid-creator). Native JDK support is expected in a future release.
PostgreSQL (via extension):
CREATE EXTENSION IF NOT EXISTS "pg_uuidv7";
SELECT uuid_generate_v7();
Go:
import "github.com/google/uuid"
id, _ := uuid.NewV7()
For quick testing and bulk generation during development, the UUID generator supports v1, v4, and v7 output with configurable formatting (with/without dashes, uppercase, JSON array, SQL IN format). It's useful when you're prototyping a schema and want to see what actual UUID values look like before committing to a generation strategy.
When UUID v4 Is Still the Right Choice
UUID v4 isn't obsolete. It's the right tool for situations where ordering doesn't matter:
Session tokens and temporary identifiers. A session ID lives for hours, not years. Index performance on the session table is rarely a bottleneck.
IDs that are never indexed. If your UUID is a correlation ID written to a log, not a database primary key, v4 is fine.
Low-write tables. A user preferences table that gets one INSERT per user per lifetime doesn't care about insert ordering.
Security-sensitive randomness. UUID v4 maximizes randomness. If you're generating identifiers that must be unpredictable (not the same as secure -- for auth tokens use dedicated crypto), v4 has an edge over v7's partially-timestamped structure.
Existing systems you don't want to migrate. If you already have UUID v4 primary keys on a table with millions of rows, migrating is almost certainly not worth the effort. New tables in the same database can use v7; existing tables keep v4.
UUID v7 vs ULID
ULID (Universally Unique Lexicographically Sortable Identifier) solves the same problem with a different encoding:
ULID: 01ARZ3NDEKTSV4RRFFQ69G5FAV (26 chars, base32)
UUID v7: 01911d4b-4c3f-7c21-ae6b-d0a7f72e93f4 (36 chars, hex)
ULID's base32 encoding makes it 10 characters shorter and case-insensitive. It looks cleaner in URLs and logs.
UUID v7's advantage is infrastructure compatibility. Existing UUID columns, UUID libraries, UUID validation patterns -- everything that already works with UUID v4 also works with UUID v7. If your database has UUID type columns, your ORM has UUID support, and your API contracts expect UUIDs, switching to v7 is one library version bump away. Switching to ULID means changing column types, serialization logic, and every system that reads those IDs.
For teams already on UUID infrastructure, v7 is the path of least resistance. For greenfield projects where you control the full stack, ULID is worth evaluating alongside v7.
When Not to Bother With Either
Some use cases genuinely don't need UUIDs. If you're building:
- A single-database internal tool that will never be distributed.
- A small application where integer IDs suffice.
- A system where sequential IDs are actually a feature (like invoice numbers).
Then stick with auto-increment. Read the UUID vs auto-increment comparison for a detailed breakdown of when each makes sense.
FAQ
Is UUID v7 officially standardized?
Yes. UUID v7 is part of RFC 9562 (published 2024), which supersedes the older RFC 4122. It's a proper IETF standard, not a draft or proposal.
Does UUID v7 leak creation time?
Yes, the timestamp is recoverable from a UUID v7 value. If knowing when a record was created is a privacy concern (e.g., a user ID that appears in URLs), UUID v4 is a better choice because it reveals nothing.
Is UUID v7 more likely to collide than v4?
In theory, marginally. UUID v7 has 74 random bits vs UUID v4's 122 random bits. In practice, 74 bits still provides ~1.8 × 10^22 possible values per millisecond -- far beyond any realistic collision threshold. The practical collision risk difference between v4 and v7 is zero.
Does MySQL benefit from UUID v7?
Yes, arguably more than PostgreSQL. MySQL/InnoDB uses a clustered index for the primary key, meaning the table data is physically organized by primary key value. UUID v4's random order causes massive page splits in the clustered index. UUID v7's time ordering prevents this. If you use UUID primary keys in MySQL, switch to v7.
Can I mix UUID v4 and v7 in the same column?
Yes. Both are valid UUIDs that occupy the same 128-bit space and use the same string format. A UUID column in PostgreSQL or a BINARY(16) column in MySQL can store both versions without issue. The version bit (position 13) tells you which is which.
How do I tell which UUID version a value is?
Look at the 13th character (first character of the third group). If it's 4, it's UUID v4. If it's 7, UUID v7. Use our UUID generator's validator to inspect version, variant, and structure of any UUID value.
Still on the fence? The UUID generator lets you generate bulk batches of both v4 and v7 UUIDs, so you can test how each format looks in your database, compare insertion speeds on your hardware, and validate output formats before writing a line of integration code.