close

DEV Community

Cover image for πŸš€ UUIDv4 vs UUIDv7 in PostgreSQL
Fazal Mansuri
Fazal Mansuri

Posted on

πŸš€ UUIDv4 vs UUIDv7 in PostgreSQL

Why Switching UUID Versions Can Boost Performance at Scale

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

Insert UUIDv4

INSERT INTO test_uuid_v4
SELECT gen_random_uuid(), 'data'
FROM generate_series(1, 1000000);
Enter fullscreen mode Exit fullscreen mode

Insert UUIDv7 (Postgres 16+ or extension)

INSERT INTO test_uuid_v7
SELECT uuidv7(), 'data'
FROM generate_series(1, 1000000);
Enter fullscreen mode Exit fullscreen mode

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.


Top comments (0)