For 15 years, UUID v4 was the only answer. Random 128-bit identifiers, generated in your browser, copy-pasted into a database column, used everywhere. It worked. Then in 2024, RFC 9562 standardised v7, and the conversation changed.
The short version: use v7 for new database primary keys, use v4 for security tokens and session IDs. Below is the why.
What a UUID actually is
A UUID is a 128-bit identifier, usually written as 32 hex characters separated by dashes: 550e8400-e29b-41d4-a716-446655440000. The dashes are cosmetic; the bytes are what matter. The 128 bits give you 2¹²⁸ possible values — about 340 undecillion. You’re not going to run out.
The version number (the first digit after the second dash — 4 in 550e8400-e29b-**4**1d4-a716-446655440000) tells you how the UUID was generated. That’s the part that matters.
UUID v4: pure random
UUID v4 is what most people mean when they say “UUID”. 122 of the 128 bits are random, the other 6 encode the version and variant. The randomness is the whole point — there’s no pattern to exploit, no way to guess the next ID, no time correlation.
Generate one in any modern browser:
crypto.randomUUID() // → "550e8400-e29b-41d4-a716-446655440000"
It’s beautifully simple. It’s also, as it turns out, terrible for databases.
The problem is B-tree indexing. A B-tree is the data structure behind most indexes (Postgres, MySQL, SQLite, MongoDB). It keeps data sorted so range queries are fast and inserts go to roughly the same place on disk. When you insert a random UUID, the database has to put it somewhere in the middle of the tree — every insert becomes a random I/O. With millions of rows, this kills write throughput.
The historical fix was “use UUID v4 but with sequential ordering” — except v4 is, by definition, random. People hacked around it (MySQL’s UUID_TO_BIN(..., 1) swaps the time bits to the front). RFC 9562 made the hack the standard.
UUID v7: time-ordered
UUID v7 puts a millisecond-precision timestamp in the high bits and randomness in the low bits:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms | ver | rand_a |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|var| rand_b |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| rand_b |
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
The first 48 bits are the Unix timestamp in milliseconds. The next 4 bits are the version (7). Then 74 random bits, then 2 variant bits, then 48 more random bits.
The result: UUIDs generated in the same millisecond sort in insertion order. UUIDs generated later sort later. Inserts go to the right side of the B-tree, not the middle. Write throughput goes up by 2-10x depending on the database.
The benchmark that matters
PlanetScale published a benchmark in 2024 that put numbers on it. With MySQL 8 on a primary key index:
| Format | Inserts/sec | Notes |
|---|---|---|
| Auto-increment INT | 35,000 | Fastest, but leaks information |
| UUID v7 | 18,000 | Comparable to INT for most apps |
| UUID v4 | 4,500 | Random inserts = page splits |
| UUID v1 (with timestamp) | 14,000 | Older time-ordered variant |
The 4x difference between v4 and v7 is the cost of doing random I/O. For a high-traffic service, that translates directly to dollars.
When to use v4
UUID v4 is still the right choice when the ID itself is the security boundary:
- Session tokens — you don’t want a session ID to leak its creation time. UUID v7 sorts by time, which means an attacker who knows one ID can guess neighbouring IDs. (Practical exploitability is low — 74 bits of randomness is still a lot — but the principle matters.) For more on token security, see our password security for developers guide.
- Password reset tokens — same reasoning.
- API keys distributed to third parties — when the ID is meant to be unguessable, time-ordering is a feature you don’t want.
- Anything used as a CSPRNG output — v4 is what
crypto.randomUUID()returns. If you’re using the ID for cryptographic randomness, v4 is the natural fit.
When to use v7
UUID v7 is the right default for:
- Database primary keys — this is the headline use case. Most new schemas should pick v7.
- Event log IDs — sortable by time without a separate timestamp column.
- Message queue message IDs — FIFO ordering with uniqueness.
- Distributed system identifiers where you want coarse ordering without coordinating a sequence.
How to generate them
In the browser, v4 is built in:
crypto.randomUUID() // v4
For v7, you need a small polyfill — crypto.getRandomValues plus a timestamp prefix. The UUID generator on DevSpeedTools produces both v4 and v7 in the browser using crypto.getRandomValues directly, so you can paste the IDs straight into your schema without installing anything.
In Node.js 22+, the built-in crypto.randomUUID() still returns v4 only. The npm uuid package (v9+) added v7(). In Python, uuid.uuid7() landed in 3.14 (released June 2025) and is now the recommended choice in the standard library docs. In Postgres, the gen_random_uuid() function still returns v4 — to get v7, generate client-side and pass it in. MySQL 9 (released 2024) added UUID_V7_BIN() as a first-class type, and MariaDB followed in 11.7. Cloudflare D1, PlanetScale, and Supabase all generate v7 by default for new primary-key columns in 2026.
Migrating from v4 to v7
If you have an existing table with v4 primary keys, you don’t have to migrate. The two versions coexist in the same column (the uuid type accepts both). The performance improvement is on inserts, so it only matters if you’re adding new rows. The simplest migration: start generating v7 for new inserts, leave existing rows as v4. They’ll sort slightly out of order with new v7 rows (since v4 has no timestamp), but the index will be fine.
For a fresh schema in 2026, v7 is the default. The 4x write throughput gain is real, the spec is stable, every major language runtime has a v7 generator, and managed database providers are defaulting to v7 for new primary keys. Reach for v4 only when you specifically need unguessable, time-correlated-free IDs.