The UUID version you choose for a primary key is not a cosmetic detail. UUID v4 generates identifiers that are random across the full 122-bit space. UUID v7 generates identifiers that are time-ordered to millisecond precision. In a write-heavy table with a B-tree index, those two properties produce measurably different insertion performance. The wrong choice can cost you 20–30% throughput on high-volume tables.
Picking between v4 and v7 also has security implications. v7 encodes a Unix timestamp in the first 48 bits — any party who receives a v7 UUID can determine (within milliseconds) when the record was created. For public-facing identifiers in user accounts or sensitive resources, that information disclosure may be a design constraint.
UUID Structure — 128 Bits in Five Groups
A UUID is a 128-bit value formatted as xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,
split into five hyphen-delimited groups of 8-4-4-4-12 hex digits. The M nibble
encodes the version (1–8). The N nibble encodes the variant (for all standard
UUIDs, the top bits of N are 10, leaving 62 bits for the variant-specific
random data).
xxxxxxxx - xxxx - Mxxx - Nxxx - xxxxxxxxxxxx
^ ^
version variant Example UUIDs:
- v4:
f47ac10b-58cc-4372-a567-0e02b2c3d479(M=4, mostly random) - v7:
018f4a3b-c210-7123-b456-426614174000(M=7, first 48 bits are timestamp)
A Short History of UUID Versions
v1: Timestamp + MAC Address (2005)
UUID v1 encodes a 60-bit timestamp (100-nanosecond intervals since October 1582) and the MAC address of the generating machine. It is monotonically increasing on the same machine but leaks the MAC address, which is a privacy problem — it was deprecated in many systems after concerns were raised. The 100ns timestamp resolution meant v1 UUIDs were not strictly monotonic under high concurrency.
v3 and v5: Deterministic Namespace-Based
UUID v3 and v5 are not random — they deterministically derive a UUID from a namespace UUID and a name using MD5 (v3) or SHA-1 (v5). They are useful when you need a stable, reproducible identifier for the same logical entity (e.g., always deriving the same UUID for a given URL or email). Not relevant for primary key generation.
v4: Pure Random (Most Widely Used)
UUID v4 fills 122 bits with cryptographically random data (the remaining 6 bits are fixed as
version and variant markers). The Web Crypto API's crypto.randomUUID() generates
v4. It is the current default UUID version in most libraries and languages. Its defining property
is that there is no relationship between any two v4 UUIDs — they are as close to statistically
independent as hardware allows.
v7: Unix Timestamp + Random (RFC 9562, 2024)
UUID v7 was standardized in RFC 9562 (April 2024). It encodes a 48-bit Unix timestamp in milliseconds in the most significant bits, followed by 12 bits of sequence data (for sub-millisecond ordering), then 62 bits of random data. Because the timestamp is in the most significant position, v7 UUIDs sort chronologically — earlier creation times produce lexicographically smaller values.
48 bits 12 bits 62 bits
timestamp | seq+ver | random Example: 018f4a3b-c210-7abc-b456-426614174000
018f4a3b-c210= Unix timestamp in milliseconds = 1715894234128 ms = ~May 2024
The Database Index Problem
How B-Tree Indexes Work
B-tree indexes store keys in sorted order within fixed-size pages (typically 8–16 KB). When you insert a row, the database finds the correct leaf page for the new key and writes it there. If the page is full, it splits — half the entries move to a new page, and a pointer is added to the parent level.
Why v4 Fragments Indexes
With v4 UUIDs, each new key is random across the full 122-bit space. The correct insertion point is equally likely to be anywhere in the index. The database must constantly read a different leaf page from disk, insert the new key, and write the page back. Under high write volume, nearly every insert hits a cold page — a page not in the buffer cache.
The result is high random I/O. On spinning disks this is immediately catastrophic. On SSDs the performance degradation is smaller but still significant because SSDs handle random writes less efficiently than sequential writes. Index page splits also increase the write amplification — the total bytes written per logical insert.
PostgreSQL documentation recommends ULIDs or UUIDv7 when primary key ordering matters for performance. MySQL InnoDB's clustered index makes this especially acute: the primary key determines physical row ordering, so random UUIDs cause the table data itself to fragment.
How v7 Solves the Problem
UUID v7 generates values that are monotonically increasing within each millisecond (with the sequence bits providing sub-millisecond ordering). New inserts go to the rightmost leaf page of the index — the "hot" page that is already in the buffer cache. Page splits are rare and happen at the right edge of the tree rather than randomly throughout.
The throughput improvement in benchmarks ranges from 15–35% for write-heavy workloads on large tables, depending on the database and hardware. Read performance for range scans also improves because records created around the same time cluster on the same pages.
Collision Probability — The Birthday Problem
For any application generating UUIDs, the probability of a collision is governed by the birthday problem.
UUID v4 Collision Risk
With 122 bits of randomness, the probability of a collision after n UUIDs is approximately:
P(collision) ≈ n² / (2 × 2^122) To reach a 50% probability of at least one collision:
n ≈ 2.71 × 10^18 At a rate of 1 billion UUIDs per second, it would take 86 years to reach that threshold. For practical purposes, v4 collision is not a concern.
UUID v7 Collision Risk
UUID v7 has 74 bits of randomness (12 sequence bits + 62 random bits per millisecond slot). Within the same millisecond on the same generator, the 12 sequence bits provide ordering, but the 62 random bits still provide enormous collision resistance:
P(collision within 1ms) ≈ k² / (2 × 2^74) where k = UUIDs generated in that ms In pathological cases where a single generator emits millions of UUIDs per millisecond, the sequence bits may overflow and randomness alone must handle disambiguation. Production applications rarely approach this limit.
Security Considerations
v4 Reveals Nothing About Creation Time
A v4 UUID f47ac10b-58cc-4372-a567-0e02b2c3d479 gives an attacker no information about
when the resource was created. This is preferable for user IDs, order IDs, or any resource identifier
that should not reveal timing information to external parties.
v7 Timestamps Are Readable
The first 12 hex characters of a v7 UUID encode the Unix millisecond timestamp. Anyone who receives a v7 UUID can decode the creation time:
function v7Timestamp(uuid) {
const hex = uuid.replace(/-/g, "").slice(0, 12);
return new Date(parseInt(hex, 16));
}
v7Timestamp("018f4a3b-c210-7abc-b456-426614174000")
// Date: 2024-05-17T03:57:14.128Z For internal database primary keys that never appear in public APIs, this is irrelevant. For identifiers returned in URLs or API responses, the timestamp leakage is a deliberate design trade-off that must be evaluated per use case.
ULID — An Alternative Worth Knowing
ULID (Universally Unique Lexicographically Sortable Identifier) predates UUID v7 and solves
the same ordering problem with a different encoding. A ULID is 128 bits encoded in 26
Crockford Base32 characters: 01ARZ3NDEKTSV4RRFFQ69G5FAV.
| Property | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| Sortable | No | Yes | Yes |
| RFC standard | Yes | Yes (RFC 9562) | No |
| Timestamp exposed | No | Yes | Yes |
| String length | 36 chars | 36 chars | 26 chars |
| Case-sensitive | No | No | No (Base32) |
| Collision resistance | 122 bits | 74 bits | 80 bits |
ULID's advantages are a shorter string representation and case-insensitivity. Its disadvantage is that it lacks a formal RFC and library support is less universal. UUID v7 is the recommended choice for new systems starting after 2024.
Decision Guide
Use UUID v4 when:
- The identifier appears in public-facing URLs or API responses where creation time must not be disclosed.
- You are adding UUIDs to an existing system standardized on v4 and migration complexity is not justified.
- Your write volume is low enough that index fragmentation is not a measurable concern.
Use UUID v7 when:
- The UUID is a primary key on a high-write table (typically >10k inserts/sec or >100M rows).
- The identifier is internal — never exposed to users as a URL parameter or API field.
- You need natural chronological ordering without a separate
created_atcolumn (though having both is best practice). - You are starting a new system and have no legacy constraint.
To generate a UUID v4 for testing or
development, an online UUID generator creates them in the browser using the Web Crypto API — no server required. For production systems,
use your language's cryptography library (crypto.randomUUID() in Node.js 15+, uuid.uuid4() in Python, java.util.UUID.randomUUID() in Java).
UUID format mismatches — v4 where v7 was expected, or a UUID in the wrong group count — are a subset of common JSON parsing errors in API integrations where identifiers are transmitted as strings inside JSON payloads.