The UUID Landscape Has Changed
If you learned UUIDs from RFC 4122 (2005), you learned UUID v1 through v5. In 2024, RFC 9562 superseded RFC 4122 and standardized v6, v7, and v8. UUID v7 is the standards-based choice to evaluate when a system needs decentralized identifiers with approximate creation-time order. RFC 9562 recommends v7 instead of v1 or v6 when possible; it does not require v7 instead of v4 for every application.
Direct answer: use UUID v4 when you want a random identifier without an embedded timestamp. Evaluate UUID v7 when index locality and time ordering matter, then test clock behavior, concurrency, storage format, and privacy. Neither version guarantees uniqueness or grants authorization; enforce a unique constraint and treat access control separately.
UUID Anatomy
A UUID is a 128-bit value, displayed as 32 hex digits in 8-4-4-4-12 format:
550e8400-e29b-41d4-a716-446655440000
^^^^
version nibble (position 13)
^
variant bits (position 17, first 2 bits = 10)
The 128 bits are allocated:
- 4 bits: version indicator
- 2 bits: variant indicator (always
10for RFC 9562) - 122 bits: version-specific content
Variant Field
The variant field (bits 64–65 of the UUID) identifies the UUID family:
| Variant bits | Family |
|---|---|
0xx |
NCS backward compatibility (obsolete) |
10x |
RFC 9562 (formerly RFC 4122) |
110 |
Microsoft COM/DCOM |
111 |
Reserved |
All UUIDs discussed here use variant 10x (RFC 9562).
UUID Versions: Complete Reference
UUID v1 — Time + MAC Address
Do not use for new systems.
Encodes a 60-bit Gregorian timestamp (100ns intervals since 1582-10-15) and the 48-bit MAC address of the generating machine.
Problems:
- Leaks the machine's MAC address (privacy/security)
- Leaks the creation timestamp (privacy)
- Timestamp bits are split across non-contiguous fields, preventing lexicographic sorting
- Requires access to a real MAC address (problematic in containers/VMs)
UUID v4 — Random
122 random bits. No embedded information. The workhorse of the past decade.
f47ac10b-58cc-4372-a567-0e02b2c3d479
^ variant = 10
4 = version
Strengths:
- Simple to generate
- No privacy concerns
- No hardware dependencies
Weakness:
- Not time-ordered — causes B-tree page splits when used as a database primary key
UUID v6 — Reordered Time (v1 Fix)
Takes v1's 60-bit Gregorian timestamp and reorders the bits so that lexicographic sorting equals chronological sorting. Still includes a node field (MAC or random).
Exists primarily as a migration path for systems already using v1 that need sortability.
UUID v7 — Unix Timestamp + Version-Specific Randomness
RFC 9562 §5.7 defines v7 and says implementations should use it instead of v1 or v6 when possible.
Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms (48 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a (12) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b (62 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 48 bits: Unix timestamp in milliseconds (good until year 10889)
- 4 bits: version (
0111) - 12 bits: random data or an optional monotonicity construction (
rand_a) - 2 bits: variant (
10) - 62 bits: random (
rand_b)
Why teams evaluate v7 for databases:
- Time-prefixed layout: canonical byte order groups values approximately by Unix millisecond.
- Potentially better B-tree locality: inserts are less scattered than uniformly random v4 values when clocks and generators behave as expected.
- Lexicographic chronology: canonical v7 strings sort by timestamp first, while same-millisecond order depends on the generator.
- Timestamp visibility: useful for diagnostics, but it leaks creation time and does not replace an authoritative
created_atfield. - Configurable monotonic behavior: RFC 9562 describes optional methods; strict ordering is an implementation property, not an inherent v7 guarantee.
UUID v8 — Custom/Experimental
Allows implementers to define their own layout within the 122 available bits (after version + variant). Used for domain-specific schemes.
Why Random UUIDs Break B-tree Performance
B-tree indexes (used by PostgreSQL, MySQL InnoDB, SQLite) maintain sorted order. When you insert a random UUID v4 as a primary key:
- The new value lands at a random position in the index
- The target leaf page is likely not in the buffer pool (cold read)
- The page may be full, requiring a page split
- Over time, pages are only ~69% full on average (fragmentation)
With UUID v7, timestamp-prefixed values can concentrate recent inserts in a narrower index region. They are not guaranteed to exceed every existing value: clocks can move backward, separate nodes can disagree, and random same-millisecond fields need not be monotonic. Page splits, fill factor, WAL volume, cache behavior, and throughput also depend on database implementation, index settings, row width, concurrency, and workload. Benchmark both schemes on a representative dataset rather than assigning a universal fill factor or speedup.
Collision Probability (Birthday Problem)
UUID v4 has 122 random bits. The birthday problem formula gives the probability of at least one collision after generating n UUIDs:
P(collision) ≈ 1 - e^(-n² / (2 × 2^122))
| UUIDs generated | Collision probability |
|---|---|
| 1 billion (10⁹) | ~9.4 × 10⁻²⁰ |
| 1 trillion (10¹²) | ~9.4 × 10⁻¹⁴ |
| 2.71 × 10¹⁸ | ~50% |
| 10¹⁸ | ~8.98% |
For UUID v7, collision analysis depends on how an implementation fills rand_a and rand_b. A fully random layout offers up to 74 random bits within one millisecond; RFC 9562 also permits monotonic methods that trade some random bits for counters or enhanced timestamps. A counter does not by itself eliminate cross-process, cross-node, restart, or clock-rollback conflicts. Follow the library's documented method and retain a unique constraint.
Code Examples
JavaScript / TypeScript
// Native: crypto.randomUUID() — UUID v4 (all modern runtimes)
const v4 = crypto.randomUUID();
// UUID v7: use a library (uuid@10+)
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// Extract timestamp from v7
function extractTimestamp(uuidV7: string): Date {
const hex = uuidV7.replace(/-/g, '');
const ms = parseInt(hex.substring(0, 12), 16);
return new Date(ms);
}
Python
import uuid
# UUID v4 (stdlib)
id_v4 = uuid.uuid4()
# UUID v7 (Python 3.14+)
id_v7 = uuid.uuid7()
# Extract timestamp from v7
def extract_timestamp_v7(u: uuid.UUID) -> float:
ms = u.int >> 80 # top 48 bits
return ms / 1000.0
Go
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// UUID v4
v4 := uuid.New()
fmt.Println(v4)
// UUID v7
v7, _ := uuid.NewV7()
fmt.Println(v7)
// Extract timestamp from v7
ts, _ := uuid.TimestampFromV7(v7)
fmt.Println(ts.Time())
}
PostgreSQL
-- UUID v4 (built-in since PG 13)
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
payload JSONB
);
-- UUID v7 (requires pg_uuidv7 extension or application-generated)
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
payload JSONB
);
-- Binary storage: UUID type IS binary (16 bytes) in PostgreSQL
-- No need for BINARY(16) hack — that's MySQL-specific
Storage Formats
| Format | Size | Sortable | Indexable | Use when |
|---|---|---|---|---|
| UUID type (PG) | 16 bytes | Yes (v7) | Optimal | PostgreSQL |
| BINARY(16) (MySQL) | 16 bytes | Yes (v7) | Good | MySQL InnoDB |
| CHAR(36) | 36 bytes | No (random) | Poor | Never for primary keys |
| VARCHAR(36) | 37 bytes | No (random) | Poor | Display/APIs only |
| BIGINT | 8 bytes | Yes | Optimal | When 64 bits suffice |
For MySQL, a v7 value in canonical byte order can be stored as BINARY(16). Do not apply the legacy v1 time-byte swap to v7 without a schema-specific reason:
-- MySQL: store canonical v7 bytes without a v1 byte swap
CREATE TABLE events (
id BINARY(16) PRIMARY KEY,
created_at TIMESTAMP(3) GENERATED ALWAYS AS
(FROM_UNIXTIME(CONV(HEX(LEFT(id, 6)), 16, 10) / 1000)) STORED
);
Decision Framework: Choosing an ID Scheme
| Requirement | UUID v4 | UUID v7 | ULID | Snowflake | KSUID | Auto-increment |
|---|---|---|---|---|---|---|
| Globally unique (no coordination) | Yes | Yes | Yes | No (needs worker ID) | Yes | No |
| Time-sortable | No | Yes | Yes | Yes | Yes | Yes |
| B-tree friendly | No | Yes | Yes | Yes | Yes | Yes |
| Standard (RFC) | Yes | Yes | No | No | No | N/A |
| Extractable timestamp | No | Yes | Yes | Yes | Yes | No |
| Privacy (no timing info) | Yes | No | No | No | No | No |
| 128-bit | Yes | Yes | Yes | No (64-bit) | 160-bit | 32/64-bit |
| Database native type | Yes | Yes | No | BIGINT | No | SERIAL |
When to Use What
- UUID v7: Evaluate for new systems that need standards-based time-prefixed identifiers and can accept timestamp exposure.
- UUID v4: When you need no timing information to be extractable (privacy requirement), or for backward compatibility.
- ULID: Similar benefits to UUID v7 but predates RFC 9562. Use if your ecosystem already uses ULID.
- Snowflake/Twitter IDs: When you need 64-bit IDs (fit in a JavaScript
Number) and can tolerate centralized worker ID assignment. - Auto-increment: Single-database, no merge/replication, maximum storage efficiency, acceptable security risk (sequential enumeration).
Security Considerations
UUID v1 Leaks Information
UUID v1 embeds the MAC address and exact creation time. Given a v1 UUID, an attacker can:
- Identify the physical machine that generated it
- Determine the exact time of creation
- Correlate UUIDs to track activity patterns
This has led to real privacy incidents in document metadata forensics.
UUID v4 Is Not a Security Token
UUID v4 generated by a CSPRNG has substantial random entropy, but a UUID-shaped value provides no authentication or authorization semantics. If generated with Math.random() or another weak source, it may be predictable. Security tokens need an explicit entropy target, lifecycle, storage, transport, revocation, comparison, and authorization design; do not assume that an identifier's format supplies those properties.
UUID v7 Timestamp Exposure
UUID v7 reveals creation time to millisecond precision. For applications where creation time is sensitive:
- Use UUID v4 instead
- Or expose a separate opaque public identifier through a reviewed mapping or token design
Nil and Max UUIDs
RFC 9562 defines two special values:
Nil UUID: 00000000-0000-0000-0000-000000000000 (all zeros)
Max UUID: ffffffff-ffff-ffff-ffff-ffffffffffff (all ones)
Use Nil as a sentinel for "no value" (similar to NULL but type-safe). Max UUID is useful as an upper bound in range queries.
Try the Formats
The UUID Generator creates RFC 9562 v4 and v7 test values locally and supports bulk output formatting. Use the UUID glossary for a concise version overview. The generator does not replace a database unique constraint or the maintained library used by your application.
Primary Standard
- RFC 9562: Universally Unique IDentifiers (UUIDs) — current layout, version, monotonicity, collision, security, and best-practice requirements
Summary
UUID v7 is the modern RFC option for time-prefixed UUIDs and is preferred by RFC 9562 over v1 or v6 when possible. UUID v4 remains appropriate when a random identifier without extractable creation time fits the requirement.
The case for v7 is workload-dependent: random keys can reduce B-tree locality, while time-prefixed keys may improve it. V7 does not remove clock, concurrency, collision, privacy, or authorization concerns.
Choose your ID scheme based on: coordination requirements, sort order needs, storage budget, privacy constraints, and ecosystem compatibility.