UUID โ€” a 128-bit string you see everywhere, from REST APIs to database primary keys โ€” is not just one thing. It is a family of eight distinct versions, each with radically different trade-offs for privacy, sortability, and database performance. Choosing the wrong version can silently fragment every B-tree index in your production database. This guide explains them all.

1. Introduction

A Universally Unique Identifier (UUID) is a 128-bit standard for uniquely identifying information in computer systems. In the design of modern distributed systems, microservice architectures, and multi-instance databases, generating unambiguous identifiers without consulting a central registry is a fundamental performance and reliability requirement.

In the era of monolithic applications backed by a single relational database, sequential integer counters (AUTO_INCREMENT or SERIAL) served as primary keys. As systems scaled horizontally, this approach became obsolete. A centralized ID generator is a Single Point of Failure and introduces a network round-trip on every write. UUID was created to allow independent nodes to generate unique identifiers in a decentralized manner with zero or negligible probability of collision.

In engineering practice the term GUID (Globally Unique Identifier), used mainly in the Microsoft ecosystem, is also frequently encountered. Structurally, UUID and GUID are conceptually identical โ€” both represent 128-bit binary values. The difference comes down to history and implementation-level specifications. While the RFC defines data storage in network byte order (Big-Endian), Microsoft COM and older .NET implementations use Little-Endian internally for selected fields. This difference is significant when sorting and binary-indexing in relational database engines.

2. Standards and Specification Evolution

The standardization of unique identifiers evolved over four decades through several key milestones. Apollo NCS (late 1980s) introduced the Variant 0 concept. The Open Software Foundation (OSF) incorporated UUIDs into the Distributed Computing Environment (DCE) spec, formally splitting the 128-bit structure into time and hardware fields. RFC 4122 (2005) was the main industry standard for nearly two decades, defining versions v1โ€“v5. RFC 9562 (May 2024) is the current IETF standard that obsoletes RFC 4122, officially standardizing UUIDv6, UUIDv7, and UUIDv8 in direct response to the B-tree performance problems caused by random UUIDs.

Every UUID contains two key control fields. The Variant field (bits 64โ€“65 for RFC 9562 Variant 1) defines the general interpretation of the remaining bits โ€” the most popular Variant 1 is marked by setting the first two bits of byte 8 to binary 10. The Version field is a 4-bit code at bits 48โ€“51 that identifies the specific algorithm used to generate the identifier (values 0x1 through 0x8).

lightbulb

Endianness matters: RFC 9562 mandates Big-Endian (Network Byte Order) for binary UUID storage and transmission โ€” the most significant byte is written first. Modifying this ordering at the library level destroys sortability and causes B-tree fragmentation. Always verify which byte order your language runtime uses before writing UUIDs to a database.

3. The 128-Bit Structure

The canonical text format consists of 32 hexadecimal digits arranged in five groups separated by hyphens in the layout 8-4-4-4-12, giving 36 ASCII characters total: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx, where M is the 4-bit version and N contains 1โ€“3 variant bits. In the hexadecimal representation the N position always starts with one of: 8, 9, a, or b.

Bit rangeBytesRFC 4122 nameRFC 9562 name (v7)Function
0 โ€“ 310 โ€“ 3time_lowunix_ts_ms (part 1)First 32 bits of timestamp or random data
32 โ€“ 474 โ€“ 5time_midunix_ts_ms (part 2)Next 16 bits of timestamp or random data
48 โ€“ 516 (high nibble)versionversionUUID version code (e.g. 0111 for v7)
52 โ€“ 636 โ€“ 7time_hirand_aFurther timestamp bits or randomness
64 โ€“ 658 (bits 0โ€“1)variantvariantVariant bits (always 10 for RFC 9562)
66 โ€“ 1278 โ€“ 15clock_seq / noderand_bClock sequence, MAC address, or primary entropy

4. History of UUID and the Birth of RFC 9562

YearDocument / StandardVersions introducedKey characteristic
1997OSF DCE Specv1, v2First official definition in the DCE environment description.
2005RFC 4122v1, v2, v3, v4, v5IETF standardization. Introduction of randomness and MD5/SHA-1 hashing.
2024RFC 9562v6, v7, v8Obsoletes RFC 4122. Dedicated support for B-tree structures and chronological sortability.

RFC 4122 promoted UUIDv4 as the universal identifier. Although UUIDv4 provides excellent uniqueness through 122 bits of randomness, its completely disordered structure causes performance degradation when writing to relational databases. Inserts of completely random identifiers lead to B-tree fragmentation, frequent page split operations, and a drastic drop in cache locality. UUIDv1 had a timestamp component but arranged the time bits in the wrong order (least-significant bits first), making chronological sorting impossible, while also creating serious privacy risks by exposing the MAC address of the network interface.

5. All UUID Versions (v1 โ€“ v8)

UUIDv1 โ€” Time-based + MAC Address

UUIDv1 is based on a 60-bit timestamp measured in 100-nanosecond intervals since 15 October 1582 (the Gregorian calendar reform), a 14-bit clock sequence, and a 48-bit MAC address. The critical design flaw: the most significant time bits land at positions 52โ€“63 while positions 0โ€“31 hold the least significant time_low bits. This layout makes chronological sorting of the text or binary string impossible.

Bit fieldBit rangeWidth (bits)Description
time_low0 โ€“ 3132Least significant 32 bits of Gregorian timestamp
time_mid32 โ€“ 4716Middle 16 bits of Gregorian timestamp
version48 โ€“ 514Fixed value 0001 (version 1)
time_hi52 โ€“ 6312Most significant 12 bits of Gregorian timestamp
clock_seq66 โ€“ 7914Clock sequence protecting against system clock rollback
node80 โ€“ 12748Physical MAC address of the generating network interface
warning

UUIDv1 exposes the physical MAC address of the generating machine and the exact creation timestamp of every document or record. Use it only in legacy systems requiring backward compatibility with older software. RFC 9562 marks it as deprecated.

UUIDv2 โ€” DCE Security

UUIDv2 is reserved for the OSF DCE Security specification. It replaces the first 32 bits of the time_low field with a local security identifier (POSIX UID or GID), and part of the clock sequence field encodes the security domain. This version is rarely used in modern engineering practice. Its reduced space for the timestamp and clock sequence means uniqueness is only guaranteed within short time windows โ€” approximately 7 minutes per UID/GID per node.

UUIDv3 โ€” Name-based MD5

UUIDv3 is a deterministic identifier. It is produced by computing the MD5 hash of a combination of a namespace UUID (in binary form) and a resource name string. The appropriate Variant (10) and Version (0011) bits are set in the resulting 128-bit value. The same namespace + name input always produces the same UUID. Due to known MD5 collision vulnerabilities, RFC 9562 recommends UUIDv5 (SHA-1) over UUIDv3 in every deterministic scenario.

UUIDv4 โ€” Random

UUIDv4 is based entirely on random numbers. Of the 128 bits, 6 are reserved for the version (0100) and variant (10) bits, leaving exactly 122 bits of pure entropy. Generation must rely on a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG). The probability of collision at 50% would require generating approximately 2.71 ร— 10ยนโธ identifiers. Generating a billion IDs per second for a hundred years still yields a collision probability of roughly 10โปโน. UUIDv4 became the most popular version due to its simplicity โ€” it requires no knowledge of system time, clock state, or MAC address.

UUIDv5 โ€” Name-based SHA-1

UUIDv5 works analogously to UUIDv3 but uses the SHA-1 cryptographic algorithm instead of MD5. From the 160-bit SHA-1 output, the first 128 bits are taken, the version bits are overwritten with 0101, and the variant bits with 10. Because of MD5's vulnerability to collisions, RFC 9562 recommends choosing UUIDv5 in every scenario that requires identifiers deterministically generated from names.

UUIDv6 โ€” Reordered Time-based

UUIDv6 is a direct modification of UUIDv1 designed to provide lexical sortability in databases while maintaining structural compatibility with v1. The 60-bit Gregorian timestamp bits are rearranged so that the most significant bits come first: bits 0โ€“32 hold time_high (most significant), bits 33โ€“48 hold time_mid, bits 49โ€“52 hold the version (0110), and bits 53โ€“64 hold time_low (least significant). This bit reordering enables lexical and binary sorting and reduces B-tree fragmentation, but since it still uses Gregorian timestamps and MAC addresses it is considered a transitional version for systems migrating from UUIDv1.

UUIDv7 โ€” Unix Epoch Time-based (Recommended)

UUIDv7 is the solution recommended by RFC 9562 for all new systems and databases. It combines a 48-bit Unix Epoch timestamp (milliseconds since 1 January 1970) with 74 bits of random entropy.

SectionBit rangeWidth (bits)Contents
unix_ts_ms0 โ€“ 4748Unix time in milliseconds (sufficient until year 10889 AD)
version48 โ€“ 514Fixed value 0111 (version 7)
rand_a52 โ€“ 6312Random entropy or sub-millisecond monotonic counter
variant64 โ€“ 652Fixed value 10 (Variant 1 per RFC)
rand_b66 โ€“ 12762Random entropy from CSPRNG
lightbulb

UUIDv7 eliminates the fundamental drawbacks of its predecessors: unlike v1 it does not expose the MAC address, and unlike v4 it provides excellent database sortability by preserving chronological continuity. The 74 bits of randomness yield 2โทโด โ‰ˆ 1.89 ร— 10ยฒยฒ combinations within the same millisecond โ€” making collisions astronomically unlikely even at very high generation rates.

UUIDv8 โ€” Custom

UUIDv8 provides a flexible format for custom solutions. It only mandates the 4 version bits (1000) and the 2 variant bits (10). The remaining 122 bits can be used freely by software architects. It is used in scenarios requiring domain-specific data embedded in the identifier โ€” such as a tenant ID, custom nanosecond timestamp precision, or geolocation coordinates. UUIDv8 has no universal semantics and requires a custom parser at the application layer.

6. Comparison of All UUID Versions

Featurev1v3v4v5v6v7v8
TimestampGregorianNoneNoneNoneGregorianUnix EpochCustom
Random bitsNoneNone (MD5)122 bitsNone (SHA-1)Partial74 bitsCustom
HashNoneMD5NoneSHA-1NoneNoneCustom
DeterministicNoYesNoYesNoNoCustom
SortabilityPoorNoneNoneNoneGoodExcellentCustom
PrivacyPoor (MAC exposed)GoodExcellentGoodGoodExcellentCustom
RFC 9562 statusDeprecatedRarely usedStill OKOKDiscouragedRecommendedSpecial purpose

7. Database Performance Comparison

Using completely random UUIDv4 as a primary key causes performance problems in relational database engines. Most databases use B-tree indexing for primary keys. Data is stored on pages (typically 8 KB or 16 KB). When inserting random identifiers (UUIDv4), the database must insert new records into random positions in the tree. When the target page is full, the engine performs a page split โ€” creates a new page and moves 50% of data to it. With UUIDv4 this results in thousands of page split operations per million inserts, whereas for sequential IDs the number is negligible.

  • Index Fragmentation: Frequent page splits leave B-tree pages only 60โ€“70% full, causing index bloat and doubling disk usage.
  • Cache Locality: Inserting entirely random keys invalidates pages in the RAM buffer pool, forcing the database to constantly perform physical disk I/O.
  • PostgreSQL 18: Introduces native uuidv7() function. Benchmarks show a 35โ€“50% reduction in index size and faster writes at scale compared to UUIDv4. PostgreSQL also provides uuid_extract_timestamp() and uuid_extract_version() helper functions.
  • MySQL (InnoDB): Enforces a Clustered Index, meaning entire table rows are physically arranged on disk by primary key order. Using UUIDv4 in MySQL causes entire rows to be moved during page splits, producing high IOPS overhead.
  • SQL Server: The uniqueidentifier type uses an atypical binary sort order (bytes 10โ€“15 first, then 8โ€“9, etc.). Generating standard UUIDv7 without proper byte reordering in .NET will cause the same fragmentation in SQL Server as UUIDv4.
  • Oracle: Stores UUID in RAW(16). Without sortability, requires regular ALTER INDEX REBUILD maintenance operations.
Metric / EngineUUIDv4UUIDv7Performance impact
Page splits (PostgreSQL)~5,000โ€“10,000 per 1M inserts~10โ€“50 per 1M insertsOver 95% I/O reduction
B-tree fill factor~60โ€“70%~95โ€“98%Disk and RAM savings
MySQL InnoDB writesScattered writes across B-treeAppends to rightmost leafEliminates table-rewrite phenomenon
RAM buffer cache usagePoor (buffer trashing)Optimized (hot pages)Significant drop in disk reads

8. UUID and Databases: Migration

Choosing a primary key type involves architectural trade-offs between size, uniqueness, and performance. AUTO_INCREMENT (BIGINT) offers the smallest footprint (8 bytes) and excellent performance but reveals database size (enumeration attack) and prevents generating IDs outside the database. Classic UUIDv4 guarantees distribution but destroys B-tree indexes. UUIDv7 mitigates these drawbacks by combining sequential write performance with distributed generation without conflicts.

Migrating from UUIDv4 to UUIDv7 does not require changing the column type, since both versions are represented by the same 128-bit UUID or BINARY(16) type. The migration steps are: (1) swap ID generators in application code from v4 to v7; (2) the database gradually begins placing new rows on the right edge of the B-tree; (3) rebuild old indexes (e.g., REINDEX TABLE in PostgreSQL or OPTIMIZE TABLE in MySQL) to compact previously fragmented pages.

warning

In .NET 9, Guid.CreateVersion7() preserves the internal Little-Endian Guid format. Without an explicit conversion using .ToByteArray(bigEndian: true) when writing to PostgreSQL, the timestamp bytes are stored in Little-Endian order, destroying chronological sortability in the B-tree index and causing index bloat of over 35% compared to the correct Big-Endian binary form.

warning

SQL Server sorts the uniqueidentifier type starting from the last bytes (bytes 10โ€“15 first, then 8โ€“9, etc.), which is the opposite of standard UUID byte order. Storing UUIDv7 in a uniqueidentifier column without the appropriate byte permutation will cause B-tree fragmentation identical to UUIDv4.

9. UUID vs ULID vs KSUID vs Snowflake

FeatureUUIDv4UUIDv7ULIDKSUIDSnowflake
Binary size16 bytes (128b)16 bytes (128b)16 bytes (128b)20 bytes (160b)8 bytes (64b)
Text length36 chars (Hex)36 chars (Hex)26 chars (Base32)27 chars (Base62)18โ€“20 digits
SortabilityNoneYes (chronological)Yes (chronological)Yes (chronological)Yes (chronological)
IETF standardRFC 9562RFC 9562No RFCNo RFCNo RFC
B-tree performanceLowHighHighMediumVery high
Coordination requiredNoneNoneNoneNoneRequired (Node ID)
Timestamp precisionNone1 ms1 ms1 s1 ms

Since the publication of RFC 9562, ULID is no longer a clear winner over UUID. ULID and UUIDv7 offer comparable technical parameters (48-bit millisecond timestamp plus entropy). The advantage of UUIDv7 is official IETF standardization and native support in databases and programming languages. Snowflake IDs offer the best B-tree performance and smallest binary size (8 bytes) but require coordinated node ID assignment, making them unsuitable for fully decentralized systems.

10. Historical UUID Problems

  • MAC Address Exposure: Early software (e.g., Windows COM objects) used UUIDv1 for document identification, allowing digital files to be traced back to the physical computer of their creator.
  • Insufficient Entropy and Predictable RNG: Using weak pseudo-random number generators (e.g., rand() in C or Math.random() in JavaScript) led to security vulnerabilities. In several systems, generated UUIDv4 values overlapped, enabling session hijacking.
  • .NET 9 and Guid.CreateVersion7(): The new function preserves the internal Little-Endian Guid format. Without explicit bigEndian: true conversion when writing to PostgreSQL, index sizes grow over 35% compared to correct Big-Endian binary form.
  • Atypical Sorting in MS SQL Server: Storing UUIDv7 in a uniqueidentifier column without proper byte permutation causes B-tree fragmentation because SQL Server sorts this type starting from the last bytes.

11. Common Misconceptions

  • "UUID guarantees 100% uniqueness" โ€” Uniqueness for random versions is a probabilistic property. The mathematical collision risk exists, though with a proper CSPRNG it is astronomically small.
  • "Every UUID is random" โ€” Only v4 consists almost entirely of random bits. Versions v1, v6, and v7 contain precise timestamps; v3 and v5 are deterministic cryptographic hashes.
  • "UUID is faster than Integer" โ€” Comparing 128-bit fields in databases requires more CPU cycles than operating on 64-bit integers.
  • "UUID cannot collide" โ€” Incorrect clock configuration, lack of access to a strong entropy source, or faulty monotonic counters can produce duplicates.
  • "UUID always has 36 characters" โ€” The 36-character representation is only the text presentation form. At the disk layer, databases store UUID as pure 16 bytes.

12. Implementation in Programming Languages

The following libraries provide RFC 9562-compliant UUIDv7 generation. In Java, the uuid-creator library (by Fabio Lima) is the recommended choice, providing both Guid.getUuidV7() for standard generation and a non-cryptographic fast variant for extreme throughput scenarios. In .NET 9+, Guid.CreateVersion7() is built into the runtime โ€” always use .ToByteArray(bigEndian: true) when extracting bytes for PostgreSQL. In Python, the uuid6 module covers v6, v7, and v8; Python's standard library uuid module covers only v1, v3, v4, and v5. In Go, the google/uuid package provides uuid.NewV7(). In Rust, the uuid crate supports v7 with the "v7" feature flag. In Node.js/TypeScript, the uuid npm package (v11.0.0+) exports v7 as uuidv7.

Highest performance UUID library for the JVM. Supports all RFC 9562 versions including v7 and v7 non-cryptographic (fastest variant, ~39 ns per call, zero allocations).

Native UUID v7 support in the .NET runtime since .NET 9. Use Guid.CreateVersion7() and always call .ToByteArray(bigEndian: true) when writing bytes to PostgreSQL or other Big-Endian databases.

Lightweight Python module implementing UUID versions 6, 7, and 8 per RFC 9562. Drop-in companion to the standard library uuid module.

De facto standard UUID library in the Go ecosystem. Provides uuid.NewV7() for RFC 9562 UUIDv7 and uuid.NewRandom() for UUIDv4.

UUID library for Rust. Enable the "v7" feature flag in Cargo.toml to unlock Uuid::now_v7() for time-ordered generation.

Most popular UUID library for JavaScript/TypeScript environments. Full RFC 9562 support since v11.0.0. Import v7 as: import { v7 as uuidv7 } from "uuid".

13. Library Comparison

LanguageLibraryRFC 9562 supportCSPRNGNotes
Javajava.util.UUID (JDK)No (v3, v4 only)YesBasic JDK class. No native v7 support.
Javauuid-creatorFullYesHighest JVM performance (~39 ns for v7nc).
C#System.Guid (.NET 9+)Full (from .NET 9)YesNative runtime support. Beware Little-Endian byte order for databases.
Pythonuuid (stdlib)Partial (v1, v3, v4, v5)YesWork on v7 inclusion in Python 3.13+ is ongoing.
Pythonuuid6FullYesLightweight module fulfilling RFC 9562 requirements.
Gogoogle/uuidFullYesDeclarative standard in the Go ecosystem.
Node.jsuuidFull (from v11.0.0)YesMost popular library for JS/TS environments.

14. Performance Benchmarks

UUID versionGeneration speedThroughputMemory per operation
UUIDv7 (fast / non-crypto)~39 ns~25.6M ops/sec0 B (reusable buffer)
UUIDv1~100 ns~10.0M ops/secSmall (clock sequence state)
UUIDv6~100 ns~10.0M ops/secSmall (clock sequence state)
UUIDv4 (CSPRNG)~140 ns~7.1M ops/sec0 B (native random allocation)
UUIDv5 (SHA-1)~260 ns~3.8M ops/secSHA-1 context buffer allocation

The performance advantage of UUIDv7 stems from the fact that reading a 48-bit timestamp register is a CPU operation that does not require invoking the full CSPRNG entropy block for the entire bit string โ€” only the 74 random bits need a CSPRNG call. The non-cryptographic fast variant foregoes CSPRNG entirely for the random fields, using an atomic monotonic counter instead, which is safe only when the timestamp already provides uniqueness guarantees.

15. Decision Matrix

ScenarioBest UUIDArchitectural rationale
REST API / MicroservicesUUIDv7Provides uniqueness and seamless chronological sortability.
PostgreSQL / MySQL Primary KeyUUIDv7Ideal index locality, eliminates page split phenomenon.
Apache Kafka / System eventsUUIDv7Events can be sorted directly by key value.
Event Sourcing / Event StoreUUIDv7Guarantees event ordering and collision-free distributed aggregation.
Legacy system integrations (v1-based)UUIDv6Provides sortability while maintaining backward-compatible v1 structure.
Deterministic resource identifierUUIDv5Computes a stable ID from a Namespace + Name pair using SHA-1.
Privacy โ€” hiding creation timeUUIDv4Prevents reading the exact moment the ID was generated.
Domain-specific custom formatUUIDv8Allows embedding custom metadata in 122 bits.

16. Best Engineering Practices

  • Adopt UUIDv7 as the default in new projects โ€” the recommended choice for resource identifiers, primary keys, and event log entries.
  • Always use a CSPRNG source โ€” ensure cryptographically secure entropy is available in the runtime environment.
  • Never use UUID as an authorization mechanism โ€” UUID is for identification, not authentication. Knowing a UUID must not represent an access permission.
  • Store UUID in native binary types โ€” use the native uuid type in PostgreSQL or BINARY(16) in MySQL. Avoid VARCHAR(36) text format.
  • Avoid UUIDv1 and UUIDv2 in new systems โ€” their outdated design and potential MAC data leaks disqualify them.
  • Use UUIDv5 for deterministic IDs โ€” when you need to compute a stable identifier from a name string, always prefer UUIDv5 (SHA-1) over UUIDv3 (MD5).

17. Frequently Asked Questions

Is UUID truly unique?

For software engineering purposes โ€” yes. Uniqueness of random versions (v4, v7) relies on mathematical probability. The probability of generating a duplicate when using a proper entropy source is so small that it is considered zero in practice.

Can UUID repeat?

It can only repeat in the case of a runtime environment failure: damaged random number generator, no CSPRNG access, system clock reset without monotonic counters, or a faulty multithreaded library implementation.

Will UUIDv7 replace UUIDv4?

UUIDv7 is progressively displacing UUIDv4 in all database and state-storage applications. UUIDv4 will remain in use in areas where the creation time of the identifier must deliberately be hidden.

Why does the canonical UUID form have 36 characters?

The 128-bit structure maps to 32 hexadecimal digits. Separating them with 4 hyphens in the standard 8-4-4-4-12 format gives a total length of 36 ASCII characters.

Is ULID better than UUID?

No, not since RFC 9562 was published. ULID and UUIDv7 offer comparable technical parameters. The advantage of UUIDv7 is official IETF standardization and native support in databases and programming language runtimes.

When should numeric IDs be used instead of UUID?

Numeric identifiers (e.g., BIGINT) remain the better choice in closed, single-instance databases with very high load, where index size is critical, data is not exposed externally through APIs, and there is no need to generate keys on independent distributed nodes.