DevToys Web Pro iconDevToys Web Proब्लॉग
अनुवादित LocalePack logoLocalePack
आम्हाला रेट करा:
ब्राउझर विस्तार वापरून पाहा:
← Back to Blog

UUID Guide: v1 vs v4 vs v7, ULID Comparison, and Database Best Practices

12 min read

Every distributed system eventually needs to generate IDs without a central coordinator. UUIDs (Universally Unique Identifiers) solve this: any node can generate an ID that will not collide with IDs produced by any other node, anywhere, at any time — with overwhelming probability. But not all UUID versions are equal. Choosing the wrong one can fragment your database indexes, leak timing information, or produce IDs that are hard to sort. Use the UUID Generator to follow along and generate IDs in any version covered here.

This guide covers everything from the bit-level anatomy of a UUID to the practical question of whether you should use UUID v4, v7, or ULID for your next database table. It also links to the generators guide for a broader look at random and structured ID generators.

UUID Anatomy: 128 Bits in 36 Characters

A UUID is a 128-bit value, conventionally displayed as 32 hexadecimal digits grouped into five sections separated by hyphens:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
         ^        ^    ^    ^    ^
         32 bits  16   16   16   48 bits

The M nibble encodes the version (1–8). The top two bits of the N byte encode the variant. For all modern UUIDs (RFC 4122 and RFC 9562), the variant bits are 10 in binary, leaving 62 bits of the N group for data. That gives you a total of 122 bits of actual payload in a v4 UUID — the remaining 6 bits are fixed by the spec.

Example v4 UUID with version and variant bits highlighted:

550e8400-e29b-4137-a716-446655440000
               ^         ^
               4 = version 4 (random)
                         a = 1010 in binary variant 10xx

UUID Versions Compared

Seven versions are standardized. RFC 4122 (2005) defined v1–v5. RFC 9562 (2024) added v6, v7, and v8, standardizing formats that had existed as drafts for years.

VersionSource of uniquenessSortable?RFCTypical use
v160-bit UTC timestamp + MAC address + clock sequencePartially (time in wrong byte order)RFC 4122Legacy systems, Cassandra
v3MD5 hash of a namespace UUID + nameNoRFC 4122Deterministic IDs from names (legacy)
v4122 bits of cryptographically random dataNoRFC 4122General purpose, most widely used
v5SHA-1 hash of a namespace UUID + nameNoRFC 4122Deterministic IDs from names (preferred over v3)
v6Reordered v1 timestamp (time-high first) + MAC + clockYesRFC 9562Drop-in sortable replacement for v1
v748-bit Unix millisecond timestamp + 74 bits of randomYesRFC 9562Database primary keys, modern systems
v8Custom — vendor-defined layoutDependsRFC 9562Application-specific schemes

v3 vs v5: Both produce deterministic UUIDs from a (namespace, name) pair. v5 uses SHA-1 instead of MD5 — prefer v5 for any new work. Neither is cryptographically secure as a MAC; they are identity functions, not authentication tokens.

v6 vs v7: v6 reorders the v1 timestamp bytes to make it lexicographically sortable while keeping the MAC address component. v7 replaces the MAC with random bits and uses the simpler Unix epoch milliseconds rather than the 100-nanosecond intervals counted from October 15, 1582. For new systems, v7 is the right choice.

Why UUID v7 Wins for Databases

The dominant database index structure is a B-tree. B-trees maintain sorted order: new entries go to their sorted position, and the tree rebalances as needed. When UUIDs are random (v4), every insert lands at a random position in the index. This causes two problems at scale:

  • Page splits: Inserting in the middle of a full B-tree page forces a page split — the database must allocate a new page, copy half the entries, and update parent pointers. At high insert rates, this becomes a significant overhead.
  • Cache thrashing: With v4 UUIDs, inserts touch random pages throughout the index. Even a moderately large table will not fit in the buffer pool. Every insert becomes a disk read before the write — cache hit rate for the index drops near zero.

UUID v7 inserts are nearly always appended to the rightmost leaf of the B-tree because the timestamp prefix increases monotonically. The database only keeps the last few leaf pages warm in cache. Page splits become rare. Insert throughput with v7 primary keys can be 3–5x higher than with v4 on a table with hundreds of millions of rows — the exact figure depends on row size, hardware, and database engine, but the directional advantage is consistent across PostgreSQL, MySQL/InnoDB, and SQL Server benchmarks.

PostgreSQL 17 added a native uuidv7() function. MySQL does not have one yet, but you can store v7 UUIDs as BINARY(16) and generate them in application code. Avoid storing UUIDs as VARCHAR(36) — it wastes space and slows index comparisons.

-- PostgreSQL 17+
SELECT uuidv7();
-- 018f3c2a-1b7e-7a3d-b4c2-9f1e2d3a4b5c

-- Earlier PostgreSQL: generate in app, store as UUID type
CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4, swap for app-generated v7
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  payload JSONB
);

-- MySQL: store as BINARY(16) for space efficiency
CREATE TABLE events (
  id BINARY(16) PRIMARY KEY,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  payload JSON
);

ULID: The Alternative to UUID v7

ULID (Universally Unique Lexicographically Sortable Identifier) was proposed in 2016, before UUID v7 was standardized, to solve the same database-index problem. Understanding it helps you choose between the two.

A ULID is 128 bits, encoded as 26 Crockford Base32 characters:

01ARZ3NDEKTSV4RRFFQ69G5FAV
^          ^^^^^^^^^^^^^^^^^^
10 chars   16 chars
48-bit     80-bit
timestamp  randomness
(ms unix)  (CSPRNG)
PropertyUUID v7ULID
Bit width128128
Timestamp bits48 (Unix ms)48 (Unix ms)
Random bits7480
String length36 chars (with hyphens)26 chars
AlphabetHex (0–9, a–f)Crockford Base32 (0–9, A–Z minus I, L, O, U)
Lexicographic sortYes (same as temporal order)Yes (same as temporal order)
RFC standardRFC 9562Community spec (not IETF)
Database type supportNative UUID type in PostgreSQL, MySQLTypically stored as CHAR(26) or BINARY(16)
URL-safeNo (contains hyphens)Yes

The practical difference between 74 and 80 random bits is negligible for collision resistance (both are astronomically safe). The more important difference is ecosystem support: UUID v7 has an RFC, native database types, and growing library support in every language. ULID has no IETF standard and is less commonly supported natively.

Choose ULID when: you need short, URL-safe IDs and your stack already uses ULID libraries. Choose UUID v7 when: you want RFC-backed standardization, native database type storage, and broad language library support.

Collision Probability Math

UUID v4 has 122 bits of randomness (6 bits are fixed by the version and variant). By the birthday paradox, the probability of at least one collision when generating n UUIDs is approximately:

P(collision) ≈ 1 - e^(-n²/(2 × 2^122))

At n = 1 billion (10^9):
  P 1 - e^(-10^18 / (2 × 5.3 × 10^36))
  P 1.2 × 10^-19  (essentially zero)

To reach P = 50% (expected first collision):
  n sqrt(ln(2) × 2^122)
  n 2.71 × 10^18  (2.71 quintillion UUIDs)

In practice: if you generate one UUID per millisecond, it takes about 86 million years to reach 2.71 quintillion. Collision is not a practical concern for v4.

UUID v7 has 74 random bits, making the collision space smaller than v4. But the timestamp prefix means collisions can only occur within the same millisecond:

Within one millisecond, 74 random bits 2^74 18.9 quadrillion possible values
Expected first collision within one ms: ~sqrt(ln(2) × 2^74) ≈ 3.6 billion UUIDs/ms

Realistic peak: a large system might generate 10,000 UUIDs/ms
At 10,000/ms: P(collision in 1ms)  (10,000)² / (2 × 2^74) ≈ 2.6 × 10^-15

Even at 10,000 IDs per millisecond, the collision probability per millisecond is effectively zero. If your system generates millions of IDs per millisecond, consider a monotonic counter in the random field (the spec allows this) or use a central sequence generator.

When collisions actually matter: They don't, for UUIDs, unless you are using a broken random number generator. The risk is not mathematical — it's implementation. A seeded PRNG (not a CSPRNG), a VM cloned without re-seeding, or a misconfigured container can produce predictable or repeated values. Use the platform's cryptographically secure RNG.

Security Considerations

UUID versions differ significantly in what information they leak:

  • v1 leaks MAC address and timestamp. A v1 UUID reveals the network interface address of the generating machine and the exact time of generation down to 100 nanoseconds. Never expose v1 UUIDs in public-facing APIs if you need to protect server identity or generation timing.
  • v7 leaks the millisecond timestamp. Anyone with a v7 UUID knows when it was generated, to the nearest millisecond. For most applications this is acceptable — row creation time is often public or low-sensitivity. But if the generation timestamp is a secret (e.g., it reveals when a user signed up or when a transaction occurred), use v4 instead.
  • v4 leaks nothing — if generated correctly. A v4 UUID from a CSPRNG reveals no information about the generator, the time, or other generated values. But if your runtime uses a weak PRNG, an attacker may be able to predict future UUIDs from observed ones. This has been exploited in real applications (PHP's mt_rand()-based UUID generation before PHP 7 was a known vulnerability class).
  • v3/v5 are deterministic and reversible by brute force. If the name space is known and the name is low-entropy (e.g., a sequential integer), an attacker can enumerate all possible UUIDs. Do not use v3/v5 as opaque tokens.

Never use UUIDs as authentication tokens or secrets. A UUID is an identifier, not a credential. Use a dedicated secret generator (e.g., crypto.randomBytes(32) encoded as base64url) for session tokens, API keys, and password reset links.

Code Examples

Node.js — built-in (v4)

// Node 14.17+: native crypto.randomUUID() — no dependency needed
const id = crypto.randomUUID();
// '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

// Works in browsers too (Web Crypto API)
const id2 = globalThis.crypto.randomUUID();

Node.js — uuid package (v4, v5, v7)

npm install uuid
import { v4 as uuidv4, v5 as uuidv5, v7 as uuidv7 } from 'uuid';

// v4: random
const id4 = uuidv4();
// '110e8400-e29b-41d4-a716-446655440000'

// v5: deterministic from namespace + name
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
const id5 = uuidv5('example.com', DNS_NAMESPACE);
// Always produces the same UUID for the same inputs

// v7: timestamp + random (preferred for database PKs)
const id7 = uuidv7();
// '018f3c2a-1b7e-7a3d-b4c2-9f1e2d3a4b5c'

Python

import uuid

# v4: random
id4 = uuid.uuid4()
print(id4)  # UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

# v5: deterministic
id5 = uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com')
print(id5)  # UUID('cfbff0d1-9375-5685-968c-48ce8b15ae17')

# v7: requires Python 3.12+
import uuid
id7 = uuid.uuid7()  # Python 3.12+
print(id7)

# Earlier Python: use the uuid6 package
# pip install uuid6
import uuid6
id7 = uuid6.uuid7()
print(id7)

PostgreSQL

-- v4 (available since PostgreSQL 9.4 via pgcrypto extension)
SELECT gen_random_uuid();
-- a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

-- v7 (native, PostgreSQL 17+)
SELECT uuidv7();
-- 018f3c2a-1b7e-7a3d-b4c2-9f1e2d3a4b5c

-- Extract timestamp from a v7 UUID
SELECT to_timestamp(
  (('x' || translate(split_part('018f3c2a-1b7e-7a3d-b4c2-9f1e2d3a4b5c', '-', 1) ||
   left(split_part('018f3c2a-1b7e-7a3d-b4c2-9f1e2d3a4b5c', '-', 2), 4),
   '-', ''))::bit(48)::bigint) / 1000.0
);

Rust

# Cargo.toml
uuid = { version = "1", features = ["v4", "v7"] }
use uuid::Uuid;
use std::time::SystemTime;

fn main() {
    // v4: random
    let id4 = Uuid::new_v4();
    println!("{}", id4);

    // v7: timestamp + random
    let ts = uuid::Timestamp::now(uuid::NoContext);
    let id7 = Uuid::new_v7(ts);
    println!("{}", id7);
}

Choosing a Version: Decision Guide

Work through these questions in order to pick the right UUID version:

  • Do you need a deterministic ID from a known name? Use v5 (SHA-1 namespace hash). Example: stable IDs for DNS names, URLs, or product SKUs where the same input must always produce the same ID.
  • Do you need a database primary key and want good index performance? Use v7. It is monotonically increasing within a millisecond, which keeps B-tree indexes compact and avoids page splits. PostgreSQL 17+ has native support; for earlier versions, generate in application code.
  • Do you need sortability but must avoid exposing a timestamp? This is a genuine tension. UUID v4 gives no temporal signal. If you need opaque but sortable IDs, consider encrypting a v7 UUID with a format-preserving cipher (FPE), or use a separate non-sequential public ID alongside an internal v7.
  • Do you need pure randomness with no timestamp leakage? Use v4. Ensure your runtime uses a CSPRNG (it does in Node 14.17+, Python, Go, Rust, and modern PHP).
  • Are you replacing a v1-based legacy system and need sortability? Use v6 as a drop-in replacement — same data sources as v1, bytes reordered for lexicographic sort.
  • Do you need short, URL-safe IDs with the same sort properties as v7? Consider ULID (26 chars vs 36 for UUID). Trade-off: no RFC standard, less native database support.
NeedRecommendedAvoid
Database primary keyv7v4 (index fragmentation at scale)
Deterministic from namev5v3 (MD5, deprecated)
Pure random, no timestampv4v1 (leaks MAC + timestamp)
Short, URL-safe sortable IDULIDv4 (not sortable), v7 (hyphens)
Legacy v1 replacementv6v1 (privacy concerns)
Authentication token / secretcrypto.randomBytes(32)Any UUID version

Generate UUID v4, v7, and other versions directly in your browser with the UUID Generator — no server, no data leaving your machine. For a broader look at ID and data generators available in DevToys Pro, see the generators guide.