You've been generating UUID v4 for your primary keys. It works. Nothing is on fire. But your database is slowly getting slower, and you might not have noticed why yet.
Here's the short version: UUID v4 is completely random. That sounds like a good thing until you realise what it does to your B-tree index.
What's actually happening inside your database
Every time you insert a row with a random UUID v4 as the primary key, PostgreSQL (or MySQL, or any B-tree-indexed store) has to find a random spot in the index to place it. Not at the end — somewhere in the middle. Randomly. Every single time.
This causes index fragmentation. Over thousands of inserts, the index pages split constantly, fill unevenly, and require more I/O to read. On a small app you'll never notice. At 10M rows, you will.
Here's what a v4 primary key sequence looks like:
f47ac10b-58cc-4372-a567-0e02b2c3d479 ← random
3d4a2f1c-9b3e-4c7a-8d2e-1f5a9b3c7d2e ← random
a1b2c3d4-e5f6-4789-abcd-ef0123456789 ← random
Every one of those lands in a different page of your index. PostgreSQL has to do a page lookup every single insert.
UUID v7 fixes this
UUID v7 (standardised in RFC 9562) encodes a Unix millisecond timestamp in the first 48 bits. The rest is still random — you still get global uniqueness — but because the timestamp comes first, rows generated close in time sort close together in the index.
Here's what a v7 sequence looks like:
018f4b2c-1a00-7e3d-9b4f-2a1c3d5e7f9b ← 2026-05-01 09:00:00.000
018f4b2c-1a01-7d2c-8a3e-1b2c4d6e8f0a ← 2026-05-01 09:00:00.001
018f4b2c-1a02-7c1b-7b2d-0a1b3c5d7e9f ← 2026-05-01 09:00:00.002
Sequential. New rows go at the end of the index, not scattered through it. Index pages fill cleanly. Fragmentation drops dramatically.
The real-world impact: teams migrating from v4 to v7 primary keys have reported 30–60% reduction in index bloat on high-write tables. The write amplification on SSDs drops too.
When to use v4 vs v7
| Situation | Use |
|---|---|
| General-purpose unique ID (session tokens, correlation IDs, API responses) | v4 |
| Database primary key on a high-write table | v7 |
| You need IDs to be sortable by creation time | v7 |
| You want to obscure creation order from external users | v4 |
| Distributed system, multiple writers, no coordination | v7 (timestamp still prevents fragmentation) |
If you're starting a new project in 2026, default to v7 for primary keys. The only reason to pick v4 is when you explicitly want non-sortable IDs (rate-limiting tokens, password reset links, etc.).
Generating them in code
JavaScript / Node.js (v7):
// Node 22+ has experimental v7 support
// Until then, use the 'uuid' package
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// → '018f4b2c-dead-7bee-beef-123456789abc'
PostgreSQL native (requires pg_uuidv7 extension):
SELECT uuid_generate_v7();
Python:
import uuid_utils # pip install uuid-utils
id = uuid_utils.uuid7()
print(str(id))
Quick sanity check — extract the timestamp from a v7:
function extractTimestamp(uuidV7) {
const hex = uuidV7.replace(/-/g, '').slice(0, 12);
return new Date(parseInt(hex, 16));
}
Need to generate a batch right now?
If you're seeding a database, writing a migration script, or just need a stack of test UUIDs without installing anything — I built a free browser-based tool that handles both v4 and v7 bulk generation.
👉 Bulk UUID v4 & v7 Generator — WebToolkit Pro
Everything runs in your browser via crypto.getRandomValues(). Nothing is sent to a server. Generate up to 100 at a time, copy them all in one click.
TL;DR
- UUID v4 is random → random index positions → index fragmentation at scale
- UUID v7 is time-prefixed → sequential inserts → cleaner indexes, faster writes
- Use v7 for database primary keys, v4 for tokens and IDs that shouldn't be guessable
- Native browser generation with
crypto.getRandomValues()is already cryptographically secure — no library needed for v4; use theuuidpackage for v7 until runtimes catch up
Abu Sufyan is a full-stack developer and builder of WebToolkit Pro — a free, privacy-first collection of 150+ client-side developer utilities.
Top comments (0)