← Back to UltraToolkit | All Posts

UUID in Distributed Systems: Why Every Developer Should Understand Unique Identifiers

A technical guide to UUID versions, primary key strategies, collision probability, performance implications, and when UUIDs are the right choice for distributed databases.

Sequential integer IDs work perfectly until you have multiple databases, multiple data centres, or multiple microservices generating records simultaneously. UUID solves the coordination problem that sequential IDs cannot β€” at a performance cost that is worth understanding before committing to the approach.

The Coordination Problem

Sequential integer auto-increment IDs require a single authoritative source to assign the next ID. In a distributed system with multiple write nodes, this creates a bottleneck: every insert must coordinate with a central sequencer, or nodes must use ID ranges allocated ahead of time. UUIDs eliminate this entirely β€” any node can generate a globally unique ID independently.

UUID Versions: Which One to Use

UUID v1 uses a timestamp and the host machine's MAC address. The embedded MAC address is a privacy concern. UUID v4 uses pure random numbers β€” the version used by this tool. UUID v6 and v7 are recent standards that restore time-ordering (v7 uses Unix milliseconds as the prefix) while preserving global uniqueness. UUID v7 is increasingly recommended for database primary keys because it sorts chronologically, improving B-tree index performance.

Generate UUID v4 identifiers in bulk with the UltraToolkit UUID Generator. Generate up to 100 UUIDs at once β€” useful for seeding databases, test fixtures, and batch record creation.

The Performance Trade-off

UUID primary keys in traditional B-tree indexes (used by PostgreSQL, MySQL, and most SQL databases) cause index fragmentation because random UUIDs insert in random positions rather than always appending to the end. This degrades insert performance at scale. UUID v7 solves this by making UUIDs time-ordered, so new records mostly append to the end of the index.

UUID Collision Probability

UUID v4 has 122 bits of randomness. To have a 50% probability of a collision, you would need to generate 2.71 quintillion UUIDs β€” approximately 86 years of generating one billion UUIDs per second. In practice, UUID collisions essentially never happen. The risk is not worth engineering around in any real application.

ULID: The Time-Ordered Alternative to UUID

ULID (Universally Unique Lexicographically Sortable Identifier) is an increasingly popular alternative to UUID v4 for database primary keys. A ULID encodes a 48-bit millisecond timestamp in the first 10 characters and 80 bits of randomness in the remaining 16 characters, producing a 26-character Crockford base32 string like 01ARZ3NDEKTSV4RRFFQ69G5FAV. Unlike UUID v4, ULIDs sort lexicographically in creation order, are URL-safe without encoding, and are slightly more compact than UUID's canonical hyphenated string form.

The timestamp prefix means that records inserted in the same millisecond may interleave, but records from different milliseconds always sort in chronological order. For B-tree database indexes, this time-ordering dramatically reduces page splits and index fragmentation compared to fully random UUID v4 β€” approaching the performance of sequential integer IDs while maintaining the distributed generation capability of UUIDs.

Namespace UUIDs: Deterministic Generation with UUID v5

UUID v3 and v5 generate deterministic UUIDs by hashing a namespace UUID and a name string. UUID v5 uses SHA-1 and is preferred over v3 (which uses MD5). The key property of namespace UUIDs is that the same namespace and name always produce the same UUID β€” useful for scenarios where you need a stable identifier for a resource based on its content or location rather than when it was created.

Practical applications include: generating a stable UUID for a URL (useful for deduplicating web scraper results), creating a stable identifier for a user based on their email address (allows matching records across systems that use different identifier schemes), and generating deterministic IDs for test fixtures that must be stable across test runs. The UUID v5 of the DNS namespace and the domain 'example.com' is always 2ed6657d-e927-568b-95e3-af7e4e0d3f5c regardless of when or where it is calculated.

Generate UUID v4 identifiers in bulk with the UltraToolkit UUID Generator. Generate up to 100 at once and copy all with a single click β€” ideal for seeding test databases and creating mock fixtures.

UUID Storage Formats in Databases

How you store UUIDs in a database has significant implications for storage efficiency and query performance. Storing a UUID as a VARCHAR(36) β€” the canonical hyphenated string form like 550e8400-e29b-41d4-a716-446655440000 β€” requires 36 bytes of storage per value and is not byte-efficient. PostgreSQL has a native UUID type that stores the value as 16 bytes, cutting storage to less than half. MySQL does not have a native UUID type; storing as BINARY(16) (packing the 16 bytes directly) achieves the same efficiency.

For MySQL specifically, the byte order of the UUID affects index performance. UUID v4 bytes are in random order, causing the same index fragmentation problems as the string form. Rearranging the bytes to put the most-significant time bytes first β€” a technique called UUID ordering or UUID swap β€” converts a random UUID into a time-ordered byte sequence that behaves like a sequential integer for index purposes. MySQL 8.0 introduced the UUID_TO_BIN() function with a swap_flag parameter that performs this reordering automatically.

Security Considerations for UUID as Public Identifiers

UUID v4 values are commonly used as public-facing resource identifiers β€” in URLs, API responses, and public APIs β€” because they reveal no sequential information. Unlike integer IDs where ID 1042 implies the existence of IDs 1 through 1041, UUID v4 IDs give no information about the total number of resources or the order in which they were created. This prevents enumeration attacks where an attacker increments an integer ID to access other users' resources.

However, UUID v4 alone is not an access control mechanism. A URL like /api/documents/550e8400-e29b-41d4-a716-446655440000 is not secure simply because the UUID is hard to guess β€” the application must verify that the authenticated user has permission to access that specific resource. UUIDs provide obscurity, not security. Combine them with proper authorisation checks rather than relying on the UUID itself to restrict access.

UUIDs in API Design and REST Interfaces

Using UUIDs as API resource identifiers is a widely adopted convention in REST API design, particularly for public APIs and microservice architectures. The /api/users/550e8400-e29b-41d4-a716-446655440000 URL pattern reveals no information about the resource beyond its identity, whereas /api/users/1042 tells an observer that there are at least 1,042 users and allows systematic enumeration. For APIs accessed by third parties, this information hiding is a minor security benefit.

UUID resource identifiers also simplify client-side optimistic updates β€” when creating a new resource, the client can generate a UUID locally, immediately reflect the new resource in the UI, and send the UUID to the server. If the server request fails, the client removes the locally-created resource. With server-assigned integer IDs, the client must wait for the server to respond before knowing the new resource's ID, requiring either a loading state or a complex response reconciliation step.

Comparing UUID to Other Unique Identifier Approaches

Several identifier schemes compete with UUID in distributed systems. Snowflake IDs, developed by Twitter and now used by many large platforms, encode a timestamp, machine ID, and sequence number in a 64-bit integer. They are time-ordered, compact (fitting in a standard database integer column), and can be generated without coordination. The tradeoff is that Snowflake IDs reveal the approximate creation time and, for systems that have been reverse-engineered, potentially the machine and sequence information.

NanoID is a URL-safe, smaller alternative to UUID β€” 21 characters by default versus 36 for UUID canonical format β€” using a cryptographically random character set. NanoID IDs are more compact in URLs and JSON payloads. They are not time-ordered and have no version metadata. The collision probability at 21 characters with the default alphabet is acceptably low for most applications β€” to have a 1% probability of collision, you would need to generate approximately 149 billion IDs.

UUID in Event Sourcing and CQRS Architectures

Event sourcing architectures β€” where the state of the system is derived from a sequence of immutable events rather than mutable database records β€” rely heavily on UUIDs for three categories of identifiers: aggregate IDs (the UUID identifying the entity whose state is described by events), event IDs (a UUID uniquely identifying each individual event), and correlation IDs (a UUID tracking a user action across all the events it triggers, useful for distributed tracing and debugging).

In CQRS (Command Query Responsibility Segregation) systems, commands are typically assigned a UUID when created by the client. This UUID serves as an idempotency key β€” if the same command is submitted twice (due to network retry, duplicate form submission, or user error), the server can detect the duplicate by checking whether the command UUID has already been processed and return the original result rather than processing the command again. This pattern prevents duplicate effects from network retries without requiring complex distributed locking.

Debugging UUID-Related Issues in Production

UUID-related production bugs tend to fall into a small number of predictable categories. Case sensitivity issues arise when UUIDs are stored and compared inconsistently β€” some databases and programming languages treat UUID strings as case-sensitive, so 550e8400-E29B-41D4-A716-446655440000 and 550e8400-e29b-41d4-a716-446655440000 are different strings even though they represent the same UUID value. Standardising to lowercase UUID strings throughout the system β€” at the database level, at the API level, and in client code β€” prevents this category of bug entirely.

Format inconsistency is another common source of UUID bugs: some systems use the canonical hyphenated format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx), others use the compact format without hyphens, and others use curly-brace-enclosed formats. When UUID strings are compared or looked up across system boundaries, format normalisation must occur before comparison. Storing UUIDs as BINARY(16) in MySQL rather than as strings eliminates format inconsistency entirely at the database layer.

UUID generation quality issues occur when a UUID generator has a poor source of randomness β€” generating UUIDs that are not truly random, potentially creating collisions at scale. In browser environments, Math.random() is not a cryptographically secure random source and should not be used to generate UUIDs. The Web Crypto API's crypto.getRandomValues() function provides cryptographically secure random data suitable for UUID generation. The UltraToolkit UUID Generator uses this API, ensuring that generated UUIDs have the statistical randomness guarantees that UUID v4 requires.

The decision between UUID and integer IDs is not binary β€” many production systems use both strategically. Integer auto-increment IDs serve as internal primary keys in relational databases, providing optimal B-tree index performance and compact join key storage. UUID v4 or v7 IDs serve as external identifiers β€” the values exposed in API responses, URLs, and client applications. The internal integer ID is never exposed externally. This hybrid approach captures the performance benefits of sequential integers for database operations and the security and distribution benefits of UUIDs for external interfaces. The mapping between internal integer ID and external UUID is maintained as a dedicated column in the table.

UUID adoption in new projects is straightforward β€” start with UUID v7 for database primary keys (time-ordered, excellent index performance) and UUID v4 for external identifiers (random, no temporal information leakage). For existing projects migrating from integer IDs to UUIDs, the migration path requires adding a UUID column alongside the existing integer primary key, backfilling UUID values for all existing records, updating all foreign key references, updating API responses to expose the UUID rather than the integer ID, and finally dropping the integer primary key after all clients have migrated to UUID-based lookups. This is a significant migration that typically spans multiple deployment cycles. The transition period β€” where both integer and UUID are available β€” must be managed carefully to avoid inconsistency between old and new client versions.

For teams starting a new project and deciding on an identifier strategy, the practical recommendation is straightforward. Use UUID v7 (time-ordered UUID) as the primary key in your database if your database supports it natively or via a library. If UUID v7 is not available, use UUID v4 for low-to-medium scale applications β€” the index fragmentation issue only becomes a meaningful performance concern above tens of millions of rows. Use ULID as an alternative to UUID v7 if you prefer a URL-safe, Crockford base32 encoding. Reserve integer auto-increment IDs only for internal lookup tables and reference data where sequential ordering is meaningful and external exposure is not required. Whatever strategy you choose, document it in your project's architecture decision records before implementation, because changing identifier strategies after data is in production is one of the most disruptive database migrations imaginable β€” affecting every table, every foreign key, every API endpoint, and every client application simultaneously.

The UUID Generator on UltraToolkit generates RFC 4122 compliant UUID v4 identifiers using the browser's Web Crypto API, which provides cryptographically secure random number generation suitable for all production UUID generation use cases. Generate individual UUIDs for immediate use, or generate up to 100 at once for bulk database seeding, test fixture creation, and mock data generation. All generation happens client-side β€” no UUIDs are transmitted to any server, and each UUID is guaranteed to be unique within the bounds of UUID v4's collision probability guarantees.

References: RFC 4122 β€” UUID Standard · RFC 9562 β€” UUID v7 Standard

Open UUID Generator

Free, browser-based, no signup, no data stored.

Generate UUIDs →
← Back to UltraToolkit All Posts →