UUID v4 and v7 at a Glance
Both UUID v4 and v7 are 128-bit identifiers formatted as 32 hex characters grouped as 8-4-4-4-12. The difference is in what those bits contain.
A UUID v4 looks like this:
f47ac10b-58cc-4372-a567-0e02b2c3d479
^^^^
version nibble = 4
A UUID v7 looks like this:
018e2b3c-d4a1-7f2e-b8c9-1234567890ab
^^^^^^^^^^^^^
48-bit ms timestamp
^
version nibble = 7
The structural difference has real performance implications for databases.
UUID v4: Structure
UUID v4 allocates 122 bits to randomness. The remaining 6 bits are reserved:
- 4 bits encode the version (
0100= 4) - 2 bits encode the variant (
10for RFC 4122)
xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
The x characters are random. The 13th character is always 4. The 17th character is always one of 8, 9, a, or b.
Generation in most languages is a single call:
import uuid
str(uuid.uuid4())
# 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
crypto.randomUUID() // built-in since Node 14.17 / all modern browsers
// 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
import "github.com/google/uuid"
uuid.New().String()
UUID v7: Structure
UUID v7, standardized in RFC 9562 (May 2024), uses the first 48 bits for a Unix millisecond timestamp, followed by version bits and random data:
[48-bit ms timestamp][4-bit version = 7][12-bit random][2-bit variant][62-bit random]
The 48-bit timestamp prefix means UUIDs generated at the same millisecond are nearly identical in their most significant bits, and UUIDs generated later are lexicographically larger. This is the property that makes v7 friendly to B-tree indexes.
# Python 3.11+ has native uuid7 in the standard library
import uuid
str(uuid.uuid7()) # Python 3.11+
// No native support yet, use a library
import { v7 as uuidv7 } from 'uuid';
uuidv7()
// '018e2b3c-d4a1-7f2e-b8c9-1234567890ab'
// Java has no native UUID v7, use java-uuid-generator
import com.fasterxml.uuid.Generators;
Generators.timeBasedEpochRandomGenerator().generate().toString();
Why Random UUIDs Kill Database Performance
B-tree indexes (used by PostgreSQL, MySQL, SQL Server, and most others) maintain sorted order. When you insert a new row, the database finds where the new key belongs in the sorted index and places it there.
With UUID v4, new keys are distributed uniformly at random across the 128-bit space. The new key almost never belongs at the end of the index. The database must:
- Read the index page where the key belongs
- If the page is full, split it (allocating a new page and redistributing entries)
- Update parent index nodes
At high insert rates, this causes constant page splits, high write amplification, and poor cache utilization. Index pages are evicted from the buffer pool as soon as they are written because new inserts almost never touch the same page twice.
With UUID v7, new keys are always larger than all previous keys (within the same millisecond). Inserts go to the rightmost leaf page of the B-tree. Pages fill sequentially, splits are rare, and the hot pages stay in the buffer pool.
The difference is measurable. Benchmarks on PostgreSQL with UUID v4 vs. v7 primary keys typically show 20-50% better insert throughput for v7 on large tables, with significantly lower index bloat.
When to Use Each Version
Use UUID v4 when:
- Generating security tokens, API keys, session IDs, or password reset tokens where unpredictability matters. The full 122 bits of randomness means the ID cannot be guessed or sequenced by an attacker.
- The IDs are not used as database primary keys, or the table is small enough that index performance does not matter.
- You need broad compatibility. v4 has been available in every major language and library for 20 years.
Use UUID v7 when:
- Using UUIDs as database primary keys, especially on tables that receive high insert rates.
- You need sortable, time ordered IDs that can be compared chronologically without a separate timestamp column.
- You are designing a distributed system and want to reduce coordination overhead. The timestamp prefix provides a natural ordering without a central sequence generator.
- You are building an event log, audit trail, or any system where insertion order matters.
UUID v7 vs. ULID
ULID (Universally Unique Lexicographically Sortable Identifier) solves the same problem as UUID v7 (sortable, time ordered IDs) with a different format. ULIDs use 48-bit millisecond timestamps and 80 bits of randomness, encoded as 26 Crockford Base32 characters (e.g., 01ARZ3NDEKTSV4RRFFQ69G5FAV).
The practical difference is format compatibility. If your system expects UUID shaped strings (36 characters, hex, with hyphens), use UUID v7. If you want shorter, URL safe identifiers and control the format end to end, ULID is a reasonable choice. Both are ordered by generation time; both avoid the B-tree problem.
UUID v7 has the advantage of being an IETF standard (RFC 9562), which means ecosystem support is growing quickly.
Identifying UUID Versions in the Wild
Given a UUID string, the version is always at position 14 (0-indexed), the first character of the third group:
xxxxxxxx-xxxx-Vxxx-xxxx-xxxxxxxxxxxx
^
position 14, always the version digit
1→ v1 (time based, MAC address)3→ v3 (name based, MD5)4→ v4 (random)5→ v5 (name based, SHA-1)7→ v7 (time ordered, random)
The variant is at position 19 (the first character of the fourth group): 8, 9, a, or b for RFC 4122 UUIDs. If you see a 0 or f at position 14, the value is likely not a standard UUID. It may be a nil UUID, a max UUID, or a nonstandard identifier shaped like a UUID.