UUID v4 vs v7: what changed and which to use for new projects

UUID v7 is now in the RFC 9562 standard and is widely supported. Here is what changed from v4, when to switch, and how sortable identifiers improve database performance.

J
Jan Stepien·

UUID v4 has been the default choice for randomly generated identifiers for over two decades. It is simple, well-supported, and statistically collision-resistant. But in 2024, RFC 9562 formalised UUID v7 as an official version, and it fixes the biggest practical problem with v4: random UUIDs are terrible for database primary keys because they destroy index locality. This guide explains what v7 changes, when to switch, and how to generate both.

UUID structure recap

A UUID is a 128-bit identifier, conventionally written as 32 hex digits in five groups separated by hyphens: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. The M nibble indicates the version (4 = random, 7 = timestamp-ordered). The N nibble indicates the variant (always 8,9, a, or b for standard UUIDs).

UUID v4: pure randomness

Version 4 UUIDs are generated from a cryptographically secure random number generator. 122 of the 128 bits are random; the remaining 6 are fixed version/variant flags. The probability of a collision is so low it is essentially zero for practical purposes: generating 1 trillion UUIDs per second for 85 years gives a 50% chance of a single collision — roughly the lifetime of the sun.

// UUID v4 example
550e8400-e29b-4d13-a456-426614174000
         ^^^^                        <- version 4
              ^                      <- variant bit

The downside of pure randomness is that sequential UUIDs are completely non-sequential. When you insert rows with random UUID primary keys into a B-tree index (PostgreSQL, MySQL, SQLite), each new insertion lands in a random position in the index. At high insert rates, this causes page splits — the database must constantly reorganise the index pages. On large tables, this creates significant write amplification and fragmentation that degrades both insert performance and read locality.

UUID v7: timestamp-ordered

UUID v7 uses a Unix timestamp (millisecond precision) in the most significant 48 bits, followed by random data. Because the timestamp is at the front, v7 UUIDs generated in order are lexicographically ordered — newer UUIDs sort after older ones, both as strings and as raw bytes.

// UUID v7 structure (128 bits total)
// [unix_ts_ms 48 bits][ver 4 bits][rand_a 12 bits][var 2 bits][rand_b 62 bits]

// Example — three v7 UUIDs generated 1ms apart sort in insertion order:
019184e6-7000-7a1c-b2f3-4d6e89f01234   <- generated at T+0ms
019184e6-7001-7b4a-9f12-8c3e56d07856   <- generated at T+1ms
019184e6-7002-7c88-a531-1b7f23e04512   <- generated at T+2ms

The 7 in position 13 (the version nibble) identifies these as v7. The first 12 hex characters encode the Unix timestamp in milliseconds — you can decode the creation time directly from the UUID.

Why ordering matters for databases

When UUID v7 primary keys are inserted in time order, each new row appends near the end of the index rather than at a random position. This is the same behaviour as auto-incrementing integers, which are well-known to produce efficient B-tree indexes. The practical results on production-scale PostgreSQL tables (10M+ rows):

  • Insert throughput can be 2–5× higher for UUID v7 vs v4 at high write rates.
  • Index size is smaller because fragmentation is lower.
  • Range queries on the primary key (e.g., "all rows from the last 5 minutes") use a sequential index scan rather than a scattered random-access pattern.
  • Cache hit rates improve because recently accessed rows are near each other in the index.

When to use v4 vs v7

Use caseRecommended versionReason
Database primary keys (new table)v7Index locality, insert performance
Distributed event IDsv7Sortable, timestamp embedded, no coordination needed
Session tokens / CSRF tokensv4Must be unpredictable; timestamp leakage undesirable
Existing table with v4 PKsv4 (keep)Migration cost outweighs benefit unless table is small
Security-sensitive identifierv4v7 leaks creation timestamp (6 bytes); v4 leaks nothing
Name-based deterministic IDv5 (SHA-1)Hashes a name within a namespace to a stable UUID

Generating UUID v7 in code

// Node.js 22+ / Bun — crypto.randomUUID() is v4 only; use a library
import { uuidv7 } from "uuidv7";
console.log(uuidv7()); // 019184e6-7002-7c88-a531-1b7f23e04512

// PostgreSQL — gen_random_uuid() is v4; use pg_uuidv7 extension
CREATE EXTENSION IF NOT EXISTS "pg_uuidv7";
SELECT uuid_generate_v7(); -- 019184e6-7002-7000-8000-000000000000

// Python 3.11+ — uuid.uuid7() draft; use python-uuid7 library
from uuid_extensions import uuid7
str(uuid7())  # '019184e6-7002-7000-8000-000000000000'

// Go
import "github.com/google/uuid"
id := uuid.Must(uuid.NewV7())

A note on timestamp precision

RFC 9562 mandates millisecond precision for the timestamp field. If multiple UUIDs are generated within the same millisecond (very common in high-throughput systems), the 12-bit rand_a field is typically used as a monotonic counter to maintain ordering within the same millisecond. Good implementations handle this automatically; check your library's documentation to confirm.

Try it now

Generate UUID v4 or v7 (in bulk, with optional count) at quickhelp.dev/uuid-generator. The tool lets you choose version, count (1–100), and format (uppercase/lowercase/braces). It also works as an API: POST /api/uuid-generator with {"version":"v7","count":10}. For related tools, see Hash Generator (SHA-256 checksums) and Timestamp Converter (decode the timestamp embedded in a v7 UUID).

We use cookies to serve ads and measure traffic. Cookie policy · Privacy policy