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.

PropertyUUID v4UUID v7ULID
SortableNoYesYes
RFC standardYesYes (RFC 9562)No
Timestamp exposedNoYesYes
String length36 chars36 chars26 chars
Case-sensitiveNoNoNo (Base32)
Collision resistance122 bits74 bits80 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_at column (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.