Many teams choose UUIDs as primary keys to support distributed systems and avoid ID collisions.
But not all UUIDs are equal.
Some teams have seen 10×–50× performance improvements simply by switching from UUIDv4 to UUIDv7.
Sounds surprising?
Let’s break it down - step by step - with no hand-waving.
🎯 What This Blog Will Help You Decide
By the end, you’ll understand:
- What UUIDv4 and UUIDv7 actually are
- How PostgreSQL indexes work internally
- Why random IDs hurt performance
- Why UUIDv7 scales dramatically better
- When you should (and shouldn’t) switch
- How to benchmark this yourself
1️⃣ Quick Refresher: What Is a UUID?
A UUID (Universally Unique Identifier) is a 128-bit value designed to be globally unique.
Why developers use UUIDs:
- Safe for distributed systems
- Can be generated client-side
- No central ID generator
- Avoids ID collisions across services
PostgreSQL stores UUIDs efficiently (16 bytes), so UUIDs themselves are not slow.
👉 The problem is the insertion pattern, not the type.
2️⃣ UUIDv4 - Random by Design
How UUIDv4 Works
UUIDv4 is purely random (122 random bits).
Example:
9f1c0d3a-4b6e-4b8f-8f91-17e0a0c9a721
e2a31f8b-91f2-4df7-98cb-6e9fcae721aa
0d2c5f99-5d8c-4b6d-8a0e-2dbbfa17d441
Key properties:
- No ordering
- No timestamp
- Completely scattered values
This randomness is great for uniqueness
…but terrible for database indexes.
3️⃣ UUIDv7 - Time-Ordered UUIDs
UUIDv7 is a newer standard designed specifically for modern databases.
How UUIDv7 Works
UUIDv7 combines:
- A timestamp (milliseconds) in the most significant bits
- Random bits for uniqueness
Example (simplified):
018f3b3a-9b5c-7d12-bc01-9a2cfe123456
018f3b3a-9b5d-7f44-a231-acde441298ab
018f3b3a-9b5e-82a1-9d22-acde112341aa
Key properties:
- Mostly increasing over time
- Still globally unique
- Still safe for distributed systems
👉 This ordering changes everything.
4️⃣ How PostgreSQL Stores and Indexes Data (Critical Part)
PostgreSQL Table Storage
- Rows are stored in a heap
- Order in heap is not guaranteed
PostgreSQL Indexes
Primary keys use B-tree indexes by default.
5️⃣ What Is a B-Tree (In Simple Terms)
A B-tree:
- Stores keys in sorted order
Is optimized for:
- Sequential inserts
- Range scans
- Cache-friendly access
Think of it like:
A well-organized book where new pages are best added at the end.
6️⃣ Why UUIDv4 Hurts PostgreSQL Performance
With UUIDv4:
- Each new ID belongs somewhere random in the index
PostgreSQL must:
- Find the correct position
- Split pages frequently
- Rebalance the tree
- Touch random memory pages
Consequences:
- Heavy page splits
- Index fragmentation
- Poor cache locality
- Increased disk I/O
- Slower inserts
- Slower reads
- Slower vacuum
This gets worse as the table grows.
7️⃣ Why UUIDv7 Is Fast
With UUIDv7:
- New rows are mostly appended
- Inserts hit the rightmost leaf page
- Minimal page splits
- Excellent cache locality
- Sequential disk writes
This is very similar to:
BIGSERIAL
IDENTITY
- Snowflake-style IDs
👉 PostgreSQL loves this pattern.
8️⃣ Why Teams See 10×–50× Improvements
At scale (millions of rows):
| Metric | UUIDv4 | UUIDv7 |
| Insert latency | High | Low |
| Index size | Large | Smaller |
| Cache efficiency | Poor | Excellent |
| Page splits | Frequent | Rare |
| Vacuum cost | High | Lower |
⚠️ Important note:
- 50× is workload-dependent
- Typical gains are 5×–20×
- Still very significant
9️⃣ Hands-On Benchmark (You Can Try This)
Table Setup
CREATE TABLE test_uuid_v4 (
id UUID PRIMARY KEY,
data TEXT
);
CREATE TABLE test_uuid_v7 (
id UUID PRIMARY KEY,
data TEXT
);
Insert UUIDv4
INSERT INTO test_uuid_v4
SELECT gen_random_uuid(), 'data'
FROM generate_series(1, 1000000);
Insert UUIDv7 (Postgres 16+ or extension)
INSERT INTO test_uuid_v7
SELECT uuidv7(), 'data'
FROM generate_series(1, 1000000);
UUIDv7 generation requires Postgres 18 (uuidv7()) or an extension such as pg_uuidv7 in earlier versions.
Observe:
- Insert time
- Index size
- CPU usage
- Disk writes
You’ll see:
- UUIDv7 inserts are dramatically smoother
- Index size is smaller
- Less I/O pressure
🔍 Why This Matters in Real Systems
Backend Impact
- Faster writes
- Better read performance
- Healthier indexes
- Lower infrastructure cost
Frontend Impact
- IDs sort naturally by creation time
- Easier pagination
- Better caching behavior
- Cleaner URLs
⚠️ When UUIDv7 Is NOT the Right Choice
Be honest and balanced:
- If you need fully random IDs
- If timestamp leakage is a concern
- If your dataset is small
- If sequential IDs are unacceptable
UUIDv7 is a performance trade-off, not magic.
🏁 Final Thoughts
This performance boost isn’t accidental — it’s how databases work.
The takeaway:
- PostgreSQL B-trees love ordered inserts
- UUIDv4 is random → bad at scale
- UUIDv7 is time-ordered → excellent at scale
- Schema design decisions matter more than hardware
Choosing the right ID strategy early can save years of performance tuning later.
💬 Have you used UUIDv4 in production and faced scaling issues?
Or already switched to UUIDv7?
Let’s discuss — real-world experiences help everyone.