← Back to Blog

UUID Selection Guide: v1 vs v4 vs v7 and ULID Comparison

Why ID Selection Matters

In distributed systems, assigning a unique identifier to every record is a fundamental need. Choosing the wrong ID scheme can lead to database index fragmentation, sorting difficulties, or even privacy leaks. This article systematically compares UUID versions and ULID to help you make the right choice for your scenario.

UUID Basics

A UUID (Universally Unique Identifier) is a 128-bit globally unique identifier, formatted as an 8-4-4-4-12 hex string:

550e8400-e29b-41d4-a716-446655440000

The core value of UUIDs is generating unique IDs without central coordination, making them a cornerstone of distributed systems. The spec has evolved; the latest is RFC 9562 (May 2024, superseding RFC 4122).

Version Details

UUID v1: Timestamp + MAC Address

Method: current timestamp (60 bit, 100ns precision) + machine MAC address (48 bit) + clock sequence (14 bit)

Pros:

  • Sortable by generation time
  • No random number generator needed

Cons:

  • Privacy: MAC address exposes the machine's physical location
  • Security: generation time and hardware info can be inferred
  • Multithreading: clock sequence coordination needed on the same machine
  • MAC addresses may be duplicated in virtualized environments

Use case: legacy system compatibility only; not recommended for new projects.

UUID v4: Pure Random

Method: 122 bits of randomness (6 bits for version/variant)

Pros:

  • Simple implementation; no timestamp or machine ID needed
  • No privacy leakage
  • Negligible collision probability (2¹²² space)

Cons:

  • Not sortable: completely random, no temporal ordering
  • Database index disaster: random inserts cause frequent B+ tree page splits and fragmentation
  • 36 bytes when stored as a string

Collision intuition: after generating 2.71 × 10¹⁸ v4 UUIDs, collision probability reaches 50%. At 1 billion IDs/second, that takes ~85 years.

Use case: unique identification where sorting isn't needed—e.g., temporary tokens, message IDs.

UUID v6: Reordered v1

Method: same timestamp + MAC as v1, but timestamp high-order bits first, so lexicographic order matches chronological order.

Position: solves v1's sorting problem but retains the MAC address drawback. Low real-world adoption.

UUID v7: Timestamp + Random (Recommended)

Method: first 48 bits are a Unix millisecond timestamp; remaining 74 bits are random (including version/variant bits)

Pros:

  • Time-sortable: lexicographic order equals chronological order—perfect for DB indexes
  • High write performance: monotonically increasing; B+ tree appends without page splits
  • Distributed-friendly: no machine ID needed; nodes generate independently
  • No privacy leak: no MAC address
  • UUID-compatible: still a standard 128-bit UUID

Cons:

  • Millisecond precision; uniqueness within the same millisecond relies on randomness
  • Generation time can be roughly inferred (usually fine, but be aware)

Use case: database primary keys for new projects, distributed event IDs, sortable unique identifiers. The recommended choice in 2025.

UUID v8: Custom

RFC 9562's v8 allows a fully custom 128-bit layout. For special needs like embedding business data. Not recommended for general use.

ULID

ULID (Universally Unique Lexicographically Sortable Identifier) is an alternative to UUID, designed specifically for sortability.

Structure

01ARZ3NDEKTSV4RRFFQ69G5FAV
└── Timestamp (48 bit) ──┘└── Random (80 bit) ──┘
  • 128 bits total, same as UUID
  • Encoded as 26-character Crockford Base32 string
  • Millisecond Unix timestamp

ULID vs UUID v7

Feature UUID v7 ULID
Length 128 bit 128 bit
String form 36 chars (with hyphens) 26 chars
Encoding Hexadecimal Crockford Base32
Sortable By time, lexicographically By time, lexicographically
Case sensitivity Insensitive Insensitive
URL-friendly Must handle hyphens No special characters
Standardized RFC 9562 Community spec
Ecosystem Native UUID libraries Requires dedicated library
Time precision Millisecond Millisecond

Recommendation

  • Prefer UUID v7: if you want to reuse existing UUID infrastructure and DB native types
  • Prefer ULID: if you value shorter strings and URL-friendliness, and don't mind an extra dependency

Database Primary Key Deep Dive

Why Random IDs Hurt Index Performance

In MySQL InnoDB, the primary key is the clustered index—data is physically stored in key order. Random UUID v4 inserts mean:

  1. New rows land at arbitrary positions in the B+ tree
  2. Frequent page splits cause write amplification
  3. Index fragmentation means more disk pages read per query
  4. Buffer pool hit rate drops

UUID v7 / ULID are time-increasing: new rows always append to the end of the index, improving write performance several-fold.

Scheme Comparison

Scheme Sortable Index Perf Storage Uniqueness Scale
Auto-increment ✅ Best 4-8 bytes Central allocation Single machine / small
UUID v4 ❌ Poor 16 bytes Decentralized Small-medium
UUID v7 ✅ Excellent 16 bytes Decentralized Any scale
ULID ✅ Excellent 16 bytes Decentralized Any scale
Snowflake ✅ Excellent 8 bytes Needs machine ID Large-scale distributed

Practical Advice

  • New projects: prefer UUID v7 or ULID as primary key
  • Existing systems: keep the current scheme; migration cost usually isn't worth it
  • Ultra-large scale: consider Snowflake (8 bytes, more space-efficient)
  • Public APIs: avoid exposing auto-increment IDs (enumerable); use UUID/ULID

Security Notes

Random Number Quality

The random portion of UUID v4 and v7 must use a cryptographically secure RNG (CSPRNG):

  • ❌ Math.random(): not crypto-safe, predictable
  • ❌ Simple PRNGs
  • ✅ Node.js: crypto.randomBytes()
  • ✅ Python: secrets module (not random)
  • ✅ Java: SecureRandom
  • ✅ Go: crypto/rand

Don't Use UUIDs as Security Tokens

UUIDs are designed for uniqueness, not unpredictability. Session tokens, CSRF tokens, and API keys should use dedicated token schemes (e.g., 256-bit random + HMAC).

v1 Privacy Risk

UUID v1 contains the MAC address; attackers can infer:

  • The machine's physical network location
  • The approximate generation time
  • Whether IDs came from the same machine

Never use v1 in new systems.

Language Library Recommendations

Language UUID v4 UUID v7 ULID
JS/TS uuid uuid (v9.0+) ulid
Python uuid (stdlib) uuid-utils python-ulid
Java java.util.UUID com.github.f4b6a3:uuid-creator ulid-creator
Go google/uuid github.com/google/uuid (v1.6+) oklog/ulid
Rust uuid crate uuid crate (v1.10+) ulid crate

JavaScript Example

import { v4, v7 } from 'uuid';
import { ulid } from 'ulid';

const idV4 = v4();  // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
const idV7 = v7();  // '018f6b22-9c2a-7c8b-9e3f-4a5b6c7d8e9f'
const ul  = ulid();  // '01ARZ3NDEKTSV4RRFFQ69G5FAV'

Python Example

import uuid
from ulid import ULID

id_v4 = uuid.uuid4()
id_v7 = uuid.uuid7()  # Python 3.14+
ul = str(ULID())

Decision Tree

Need a globally unique ID?
├── Need time-based sorting?
│   ├── Want standard UUID format? → UUID v7 ✅
│   └── Want shorter strings? → ULID ✅
├── No sorting needed?
│   └── UUID v4 (general purpose)
└── Need max performance and short ID?
    └── Snowflake (requires machine ID allocation)
Advertisement

Frequently Asked Questions

Should a new project pick UUID v7 or ULID?

Both use a timestamp prefix plus randomness, so both fix the index-fragmentation problem of random primary keys, and the practical difference is small. UUID v7 is part of RFC 9562 with growing native support (PostgreSQL 18 ships uuidv7(), with Java and Python ecosystems following) and fits a native UUID column. ULID uses Crockford Base32: 26 characters instead of 36, case-insensitive and friendlier in URLs. Rule of thumb: choose v7 when your database has a native UUID type or you need standards compliance; choose ULID when you want shorter strings and can store them as char(26)/text. Never pick v4 for a primary key.

Why does a random UUID (v4) primary key hurt index performance?

Because a B+Tree index stores rows physically in primary-key order. With auto-increment IDs every new row appends to the rightmost page: sequential writes, high cache hit rate. A v4 value is pure randomness, so each new row inserts at an arbitrary position, which costs you three ways: page splits, when the target page is full it is split in two and half the rows move; random I/O, since each insert must first pull a different index page into the buffer pool; and fragmentation, because page fill factor drops and the index grows, slowing both full scans and range queries. On tables in the millions of rows, v4 insert throughput can be a fraction of auto-increment. The timestamp prefix of v7/ULID makes new values monotonic, turning inserts back into sequential writes, so you get distributed generation and sequential writes at once.

Can I use a UUID as a security token such as a session ID or password-reset link?

Not recommended. A security token must be unpredictable, and only v4 with its 122 random bits comes close: v1, v6 and v7 all start with an inferable timestamp, so leaking a single value lets an attacker narrow the guess space for tokens issued in the same window. Even with v4 there are three problems: the token is generated by your own code, so quality depends on the implementation and a non-cryptographic source like Math.random() is indefensible; a UUID carries no built-in expiry, revocation or binding, all of which a session token needs; and UUIDs show up in logs, URLs and Referer headers, so the leak surface is wider than for a purpose-built token. Use a CSPRNG to generate at least 128 random bits (crypto.randomBytes(32).toString('hex') or the equivalent) plus expiry and single-use handling.

What are the risks of using the MAC address in UUID v1?

UUID v1 encodes the host MAC address directly into the final 12 hex digits, so every generating machine carries a globally unique hardware fingerprint, and that fingerprint travels with the ID into your logs, database and public APIs. Three risks follow: privacy and tracking, since IDs from one machine can be clustered to correlate user behaviour across systems (the Melissa virus was traced to its source machine through the MAC in a v1 UUID); information disclosure, because the MAC prefix reveals the NIC vendor and hints at your infrastructure; and broken uniqueness in virtualised environments where MACs may be duplicated or assigned by the host. Modern alternatives are v4 (pure random) or v7 (timestamp plus random, no MAC).

← Back to Blog