UUID v4 vs UUID v7: Which Should You Use?
For the last decade, if you needed a unique identifier, the answer was "just use UUID v4." Random, collision-free, trivial to generate — what's not to love? Then you put a few million of them into a Postgres table with a B-tree index and watched INSERT latency crawl.
UUID v7, standardized in RFC 9562 (2024), fixes that problem. It's a drop-in replacement for v4 that sorts by time, which changes database performance in measurable ways. This guide walks through how each version works, why v7 exists, and when the old default is still the right choice.
UUID Basics
A UUID (Universally Unique Identifier, also called a GUID in Microsoft-speak) is a 128-bit value usually written as 32 hex digits split into five groups:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
└── 8 ──┘└─4─┘└─4─┘└─4─┘└───── 12 ─────┘
Two fields have fixed positions regardless of version:
M— the version (1, 3, 4, 5, 6, 7, or 8). It's the first hex digit of the third group.N— the variant (usually8,9,a, orbfor the RFC 4122 / 9562 variant). First hex digit of the fourth group.
Every UUID you see in production has M = 4 (pure random, introduced in 2005) or M = 7 (time-ordered, introduced in 2024). Versions 1–3 and 5–6 exist but are rare outside legacy systems.
UUID v4: Pure Randomness
UUID v4 fills 122 bits with cryptographically random data (the other 6 bits are taken by the version and variant markers):
fa2f0e31-d6f8-4b7c-9f8c-e47bd22b3b9e
^^^^ ^
│ └── variant ('9' = RFC 4122 variant)
└────── version (4)
Collision probability for v4 is famously small: you'd need to generate ~2^61 UUIDs before a 50% chance of a collision. That's about 2 quintillion. You will never generate that many.
What v4 is good at:
- Unpredictability. You cannot guess the next UUID given the previous one.
- Statelessness. No clock, no counter, no coordination — any machine can mint one.
- Simplicity. Every language's standard library has
uuid.uuid4().
What v4 is bad at:
- Database insert performance. Random inserts fragment B-tree indexes.
- Natural sort order. UUIDs in a log or list are shuffled by creation order.
- Cache locality. Rows created together are scattered across pages.
UUID v7: Time-Ordered Randomness
UUID v7 takes the same 128 bits but lays them out differently:
├─────── 48 bits ───────┤├ 12 ┤├──── 62 bits ────┤
Unix milliseconds (BE) rand more random bits
The first 48 bits are a big-endian Unix timestamp in milliseconds. The remaining bits are random (minus the 6 version/variant bits). A sample v7 UUID:
01858def-3c8c-7b7c-9f8c-e47bd22b3b9e
└─── ms ───┘ ^
└── version (7)
Because the timestamp leads the UUID, v7 values naturally sort in creation order when compared as strings or bytes. Two UUIDs generated in the same millisecond will still compare correctly among themselves due to the random tail, but their relative order within that millisecond is undefined.
What v7 is good at:
- B-tree friendly. Inserts append to the rightmost leaf instead of fragmenting the index.
- Sortable.
ORDER BY idapproximatesORDER BY created_at. - Still globally unique. 74 bits of entropy after the timestamp is more than enough.
What v7 gives up:
- Unpredictability. Anyone with one v7 UUID can guess the approximate creation time of another.
- Information disclosure. If you publish v7 IDs, you're publishing timestamps. That matters for rate limits, growth-metric leaks, or anti-enumeration defenses.
Side-by-Side Comparison
| UUID v4 | UUID v7 | |
|---|---|---|
| Standard | RFC 4122 (2005) | RFC 9562 (2024) |
| Total bits | 128 | 128 |
| Random bits | 122 | 74 |
| Time component | None | 48-bit ms timestamp |
| Sortable | No | Yes (by time) |
| Unpredictable | Yes | No (timestamp is leaked) |
| DB index friendly | No (random inserts) | Yes (sequential) |
| Library support | Everywhere | Growing — mainstream in modern languages |
| Collision resistance | Extremely high | Extremely high |
The B-Tree Problem (and Why v7 Solves It)
Postgres, MySQL (InnoDB), SQL Server — nearly every production database — stores rows in a B-tree ordered by the primary key. When you insert a new row with a random primary key like a v4 UUID, you're inserting into a random location in the tree. That random location is probably not in memory, so the database has to load the leaf page from disk just to write to it, then write it back.
Do this millions of times and three bad things happen:
- Buffer cache thrashing. Each insert evicts a different page. Hot working sets don't stay hot.
- Page fragmentation. New rows fill existing pages, eventually causing splits that leave half-empty pages scattered throughout.
- Write amplification. Every insert rewrites a different page, multiplying disk I/O.
With a monotonically increasing primary key (like an auto-increment integer or a v7 UUID), all inserts target the rightmost page of the B-tree. That page is almost always in memory, and splits only happen at the end — no fragmentation.
Benchmarks from pgsql-hackers and MySQL Performance Blog consistently show 2–5× insert throughput improvements moving from v4 to v7 on large tables.
When v4 Is Still the Right Choice
Don't switch to v7 reflexively. v4 wins in these cases:
1. Public-facing IDs. If users see the ID in a URL, a v7 UUID tells them exactly when a record was created. For anti-enumeration or competitive-intelligence reasons, that's a leak. Use v4 for anything exposed externally.
2. Security tokens. Session IDs, password reset tokens, API keys — these must be unpredictable. Use v4 (or preferably, a 256-bit random token — an API key is a better primitive than a UUID for security purposes).
3. Sharding keys. If you shard by ID to distribute writes evenly, v7's monotonic prefix will funnel 100% of writes to the shard that owns the current time range.
4. Distributed systems with loose clock sync. v7 depends on clocks being roughly correct. If your system has nodes with significant clock skew, v7 UUIDs generated on different nodes may sort out of physical order.
When v7 Wins
1. Internal database primary keys. This is the main use case. If the UUID is only ever seen by your database and application, v7's index-friendliness is pure upside.
2. Event logs and append-only stores. Anything where rows are ordered by creation time benefits from sortable IDs.
3. Replacing auto-increment integers. If you're moving off BIGSERIAL to avoid ID collisions across services but want the same insert performance, v7 is exactly the right tool.
4. Time-ranged queries. WHERE id >= :cutoff can replace WHERE created_at >= :cutoff without an extra index.
Migrating from v4 to v7
You don't need a big-bang migration. A gradual approach works well:
- Generate new rows with v7. Leave existing v4 rows alone.
- Both versions have the same 128-bit layout, so no schema change is required (
UUIDcolumns don't care about version). - Over time, the mix of old v4 (randomly distributed) and new v7 (clustered at the end) means new inserts still cluster at the rightmost pages. Insert performance improves immediately.
- If you need consistent ordering, add a
created_atcolumn — don't rely onORDER BY idin a mixed table.
ULID and Other Alternatives
Before v7 was standardized, several popular "sortable UUID" alternatives emerged:
- ULID — 128 bits, Crockford-Base32 encoded, 48-bit timestamp + 80-bit randomness. Extremely popular from 2017–2023. Same idea as v7 but with a different text encoding.
- KSUID (Segment) — 160 bits, timestamp + random, Base62 encoded. Not a UUID.
- Snowflake (Twitter) — 64 bits, timestamp + machine ID + sequence. Much shorter but requires coordination.
- UUID v6 — a re-ordering of the old v1 MAC-address-based UUID. Sortable, but v7 is simpler and doesn't leak MAC addresses.
For new projects in 2026, UUID v7 is the preferred choice unless you need a specific property of one of the above (e.g., shorter IDs for URLs, in which case KSUID or a separately generated slug is better).
Generating UUIDs
In most languages, the standard library now supports both. Pseudocode:
import uuid
# v4 — pure random
id = uuid.uuid4()
# v7 — time-ordered (Python 3.13+)
id = uuid.uuid7()
import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
const v4 = uuidv4();
const v7 = uuidv7();
The UUID Generator produces both in the browser — useful for seeding test data or picking the shape of an ID before committing to it.
Quick Reference
- UUID v4 — 122 bits of randomness. Unpredictable. Bad for database indexes. Use for public-facing or security-sensitive IDs.
- UUID v7 — 48-bit ms timestamp + 74 random bits. Sortable. Great for database indexes. Leaks creation time.
- Both are 128 bits, the same length, and interchangeable at the column level.
- New projects: default to v7 for internal IDs, v4 for public/security-sensitive ones.
- Existing projects: start writing v7 for new rows — no schema migration needed.
- Don't use UUIDs as security tokens. Use API keys or cryptographically random strings.
- Generate test UUIDs instantly with the UUID Generator.