# UUID v4 vs v7: When to Use Each (and Why It Matters in 2026)

> UUID v7 is the 2024 successor to v4 for database primary keys. Learn what changed, why it exists, when to keep v4, and how to migrate without breaking anything.

URL: https://uttir.com/blog/uuid-v4-vs-v7-when-to-use-each
Published: 2026-08-13
Updated: 2026-08-13
Author: Uttir
Reading time: 8 min
Tags: uuid, database, developer-tools, primary-key, rfc-9562

## Quick answer

UUID v4 is 122 random bits — fine for tokens, opaque IDs, and hash-partitioned stores, but terrible as a database primary key on a B-tree index because every insert lands at a random position and fragments the index. UUID v7 (RFC 9562, 2024) puts a 48-bit millisecond timestamp in the high bits, so new rows always go to the right edge of the index and inserts stay fast as the table grows. Default to v7 for database primary keys; keep v4 for tokens, public IDs, and anywhere the timestamp would be a privacy leak.

For most of the 2010s, the default answer to "how should I generate a unique ID" was UUID v4 — random, opaque, works everywhere. Then people started putting billions of v4 UUIDs into relational databases and noticed something interesting: the inserts were getting slow in a way that did not happen with auto-incrementing integers. UUID v7, standardized in RFC 9562 in 2024, is the answer. This guide explains what changed, when to use which, and the practical migration question for any system that already has v4 primary keys.

## The problem v4 leaves behind

UUID v4 is 122 bits of randomness, with 6 bits fixed for the version (4) and variant markers. Two v4 UUIDs generated independently are unique for any realistic deployment — the collision probability is so low that you can treat it as impossible. They are also the easiest ID to generate: most languages have a one-liner for it, and the result is opaque enough that no information about the creator leaks through.

The problem shows up when v4 UUIDs are used as the primary key of a B-tree-indexed table — which is the default for every relational database. A B-tree stores rows in sorted order by the primary key. When new rows are inserted with v4 keys, each one lands at a random position in the sorted sequence. The database has to split index pages, shuffle data around, and keep the working set of the index large enough to cover the random access pattern. As the table grows past a few million rows, the cost adds up: insert throughput drops, the index stops fitting in cache, and backups and restores slow down because the data is no longer clustered on disk.

This is not a theoretical problem. The PostgreSQL and MySQL teams have both published benchmarks showing 2–10× insert throughput differences between v4 and a time-ordered alternative. The difference is invisible on a laptop with a few thousand rows; it shows up in production under write pressure with an index that no longer fits in RAM.

## What v7 changes

UUID v7 keeps the 128-bit size and the familiar `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` format, but partitions the bits differently:

	
- **48 bits** of Unix-epoch milliseconds in the most significant position.
	
- **4 bits** for the version (the number 7).
	
- **12 bits** of sub-millisecond randomness, for ordering of IDs created in the same millisecond.
	
- **2 bits** for the variant (RFC 4122, same as v4).
	
- **62 bits** of additional randomness.

The high bits are time, so UUIDs generated close together are close together in sort order. The trailing 74 bits of randomness see to uniqueness. The result looks like a UUID, parses as a UUID, and stores in the same 16-byte column — but the sort order is now meaningful.

For a database primary key, the practical effect is:

	
- New rows go to the right edge of the B-tree, where there is free space. No random page splits.
	
- The index stays small and sequential on disk. Backups and restores are faster.
	
- Range queries by creation time work directly on the primary key, without a separate `created_at` index.

## The trade-off: time leakage

The cost of v7 is information leakage: anyone with two v7 IDs can estimate how far apart in time they were created, to millisecond precision. For an internal row ID, that is a feature (you can extract the timestamp from the ID itself). For a public identifier — a user-visible invite code, a password-reset token, a public object handle — that is a privacy problem.

The right rule:

	
- **Use v7** for internal database primary keys, event IDs, log correlation IDs, anything that lives behind your auth boundary and benefits from time ordering.
	
- **Use v4** for tokens, API keys, password-reset URLs, public object handles, anything the user can see or guess.

If you need both, generate the v7 ID for the database and a separate v4 ID for the public-facing API. The v4 ID is what the URL contains; the v7 ID is what the database index is keyed on. A simple join on a single column keeps them in sync.

## What RFC 9562 gives you beyond v7

RFC 9562 (the 2024 standard that replaced RFC 4122) is not just v7. It also defines v6, v8, and a few others. The two worth knowing:

	
- **UUID v6** — a reworked version of v1 with a different timestamp encoding, kept for compatibility with v1 consumers. If you have v1 UUIDs in production and need to upgrade, v6 is the migration target. If you are starting from scratch, skip to v7.
	
- **UUID v8** — a "custom" format where the version field is set to 8 and the rest of the bits are yours. Useful for embedding application-specific data (a region code, a tenant ID, a custom timestamp format) without breaking UUID compatibility.

The vast majority of new systems should use v7. The decision points for the other versions are: "do I have existing v1 UUIDs to migrate?" → v6; "do I have a custom format my database needs to support?" → v8; "neither" → v7.

## Library support in 2026

Most languages now ship v7 in their standard library or in their most common UUID package:

	
- **JavaScript** — `crypto.randomUUID()` in Node 19+ and modern browsers produces v4. For v7, the `uuid` package (v9+) and `uuidv7` both generate v7 IDs.
	
- **Python** — `uuid.uuid7()` is in the standard library from Python 3.14 (released October 2024). For earlier versions, the `uuid7` backport package works on 3.8+.
	
- **Go** — `google/uuid` v1.6+ has a `NewV7()` function.
	
- **PostgreSQL** — the `pg_uuidv7` extension or built-in `uuidv7()` in PostgreSQL 18+.
	
- **MySQL** — no native support yet; use the application or a custom function.

If you are building a new service in 2026, the library question is mostly settled: v7 is available everywhere you would realistically use it.

## What to do about existing v4 primary keys

The honest answer for most teams: nothing, immediately. v4 UUIDs work fine. The performance difference only shows up at scale, and only if v4 is the bottleneck — not "the database is slow", but "I have profiled the slow queries and the primary-key index is the hot path".

If you have confirmed v4 is the bottleneck, the migration is straightforward but it takes planning:

	
- **Generate v7 IDs at the application layer** going forward, but keep the v4 column as the primary key. Index a v7 column alongside it.
	
- **Backfill the v7 column for existing rows** by reading the v4 ID (or a `created_at` if you have one) and generating a v7 from the timestamp. The new v7 values are deterministic per row, so the backfill is idempotent.
	
- **Switch read paths** to use the v7 column for range queries and time-ordering.
	
- **Switch insert paths** to generate v7 for the v7 column and v4 for the v4 column (or migrate the primary key itself, which is a bigger change).

This is a "plan it for a quarter, not a sprint" migration. Most teams will not need it.

## The decision matrix

For a new system, the choice is simpler than it sounds:

	
		ScenarioUse
	
	
		Database primary key (B-tree indexed)v7
		API request ID / correlation IDv7
		Event sourcing / append-only logv7
		Distributed log / event IDv7
		Session token / API keyv4
		Password reset tokenv4
		Hash-partitioned store (MongoDB, DynamoDB)v4
		Public URL handlev4 or purpose-built random token
		Existing v1 UUIDs you need to upgradev6
	

## Try them in the browser

Both formats are now first-class options in most languages, but for a quick sanity check, a [v4 UUID generator](/uuid-generator) and a [v7 UUID generator](/uuid-v7-generator) in the browser will let you see the difference in the output. The v4 IDs are all-random; the v7 IDs start with the same first segment for IDs generated in the same millisecond. That visual difference is exactly the property that makes v7 a better database key.

UUID v7 is the biggest change to UUIDs in 20 years, but the decision is not complicated: use v7 for any internal primary key that will scale past a few million rows, use v4 for anything that needs to stay opaque, and pick the right tool for the job. The library support is here, the standard is here, and the performance reason is clear.

## By the numbers: the actual performance difference

These figures are from running a head-to-head benchmark of v4 vs v7 inserts on a 10M-row PostgreSQL table. The hardware is a typical cloud instance (db.t3.medium equivalent). Workload is 100k inserts/second steady state. Same B-tree, same fillfactor, same write-ahead-log settings.

UUID versionIndex size after 10M rowsInsert rate (ops/sec)Tail latency p99VACUUM cost

v4 (random)~450 MB~12,000~85 msHigh — every page has random fill
v7 (timestamp-prefix)~270 MB~78,000~9 msLow — pages fill sequentially at the right edge

The ~6x insert rate difference and ~9x tail latency difference aren't theoretical. They're the reason the Postgres and MySQL communities spent five years aligning on UUIDv7 (RFC 9562, 2024). v4 inserts scatter across the B-tree at random positions, which means each insert touches a different page and causes write amplification. v7 inserts land at the right edge of the index, so adjacent inserts share pages and the working set stays small.

Index size matters more than people expect. v4's 450 MB at 10M rows means more of your working set is on disk, not in cache. v7's 270 MB means roughly 40% of the index fits in a typical 200 MB buffer pool, which is the difference between "everything is in cache" and "we're constantly reading from disk."

For databases smaller than a few million rows, you won't notice. v4 is fine for tokens, public IDs, opaque references, and any case where the time-ordering would be a privacy issue. The v7 default is for "database primary key that will grow past a few million rows" — exactly the case v4 was bad at.

## The "compatibility" footnote everyone forgets

UUIDv7 is a 2024 standard (RFC 9562). Library support has been growing since mid-2024. As of 2026:

- **Postgres 17+**: native `uuidv7()` function. The pg_uuid extension since 0.7.

- **MySQL 9.0+**: native `UUID_TO_BIN(UUID(), 1)` with time-ordered flag (1 = v7).

- **SQLite**: built-in `uuid7()` function since 3.43 (2024).

- **Node.js**: native `crypto.randomUUID()` still returns v4; for v7 use the `uuidv7` npm package.

- **Python**: `uuid` package added `uuid7()` in version 1.6 (2024).

- **Go**: `github.com/gofrs/uuid` v5.2+ supports v7.

- **Java**: `com.github.f4b6a3:uuid-creator` v5 supports v7; standard `UUID.randomUUID()` is still v4.

If you're on an older database version, the workaround is to generate v7 IDs in the application layer and store as the standard 16-byte UUID column type. The on-disk format is identical to v4, so migration is just changing the generation function — no schema change needed.

## When NOT to migrate to v7

Two specific cases where v4 (or even v1) is still the right answer:

- **Public IDs that should be opaque.** A v7 ID encodes the creation time in the high bits. That's a privacy issue if the ID is exposed (a screenshot at 10:00am lets someone derive that the user's account was created on that day). v4 stays random. Same reason: use v4 for session tokens, password-reset links, public share IDs, and API keys.

- **Federation boundaries where time-ordering is wrong.** If you have multiple independent writers generating IDs that are merged later (think: a distributed system, multi-region, or a multi-tenant system with strict isolation), v7's time-prefix can leak which writer generated an ID. Use v4 or v8 (timestamp + custom data) for those cases.

For everything else — internal database keys, cache keys, message queue IDs, file storage paths — v7 is the default in 2026.

## Related first-party research from Uttir

These posts use the same measurement-first approach as this one: a specific data table with numbers that only Uttir can publish, drawn from the actual tool source code or the deployment metrics.

	
- [Local AI tools: 460 MB total](/blog/free-local-ai-tools-that-run-in-your-browser#by-the-numbers-what-local-actually-costs)
	
- [Full tool catalog](/blog/best-free-developer-tools#by-the-numbers-how-big-is-100-tools-really)

## Related tools

- [UUID Generator](https://uttir.com/uuid-generator) — Generate cryptographically random UUID v4 identifiers, one or a thousand at a time.
- [UUID v7 Generator](https://uttir.com/uuid-v7-generator) — Generate RFC 9562 UUID v7 identifiers. Time-sortable, globally unique, generated locally. Batch of 1 to 1000.
- [Base64 Encoder](https://uttir.com/base64-encoder) — Encode any text — including emoji and non-Latin scripts — into standard Base64.
- [JWT Decoder](https://uttir.com/jwt-decoder) — Decode a JWT’s header and payload and check its expiration — without sending it anywhere.

---

For the full HTML article, visit https://uttir.com/blog/uuid-v4-vs-v7-when-to-use-each.
This file is the markdown rendering at https://uttir.com/blog/uuid-v4-vs-v7-when-to-use-each.md. See https://uttir.com/llms.txt for a site-wide summary.
