UUIDs and Unique Identifiers: Choosing a Practical Format
Why picking the right identifier matters: a real order-processing problem
Your e-commerce system receives orders from a web frontend, mobile apps, and a batch import process. Orders flow into multiple microservices: payment, inventory, shipping, and analytics. You need IDs that let services join events, keep indexes efficient, help ops trace failures, and avoid leaking customer data. Simple integers from a central database are tempting, but they create scaling bottlenecks and replication headaches. Random UUIDs avoid a central counter but make indexes and human comparison painful. Time-ordered IDs improve database locality and logs but can expose ordering or host information.
High-level identifier types for order systems
Below are the common categories you’ll choose from. Each suits different trade-offs around uniqueness, sortability, privacy, and operational simplicity.
- Auto-increment integers (database SERIAL, BIGSERIAL): simple, compact, human-friendly, but require a central sequence and are hard to merge between shards.
- Random IDs (UUID v4, random strings): good for distributed generation, low collision risk when long enough, not naturally ordered.
- Time-ordered IDs (UUID v1, ULID, UUID v7): encode a time prefix so new rows are roughly sequential—better for indexes and querying recent records.
- Comb-style IDs (time + random suffix): hybrid approach to get some locality while keeping randomness.
- Application-generated composite keys (e.g., tenant:timestamp:sequence): human-readable but larger and require careful validation.
How identifiers affect database design and index performance
For relational databases, primary key choice matters because of clustering and index bloat. Random 128-bit UUIDs distribute inserts across the index, causing page splits and larger indexes. Time-ordered keys (ULID, UUIDs with time prefix) keep new writes near the end of the index, improving insertion speed and cache locality.
Practical guidance
If you expect high insertion rates and rely heavily on ordered scans (recent orders), prefer time-ordered IDs or a monotonic sequence per shard. If you need easy distributed generation and don’t care about index locality, random UUIDs are acceptable.
Collision assumptions and how safe are ‘random’ IDs?
Collisions mean two different orders get the same ID. For a 128-bit random UUID (v4), the probability of collision is astronomically small for practical system sizes, but not zero. Use the birthday paradox approximation for rough reasoning: the chance of any collision among N uniformly random 128-bit values is approximately 1 – exp(-N*(N-1)/(2*2^128)).
Concrete example calculations:
// Rough collision magnitude examples (approximate, not exact calculations)
// - 1 million (1e6) random UUIDs: collision probability ~ 1e-23
// - 1 billion (1e9) random UUIDs: collision probability ~ 1e-14
Those probabilities are effectively zero for most businesses. However, if you intentionally reduce entropy (shorter IDs, custom alphabets), you must redo the math.
Validation boundaries: what to check and enforce
Define clear validation rules at service boundaries so malformed IDs do not propagate. Typical checks:
- Length and allowed characters (e.g., 36 characters with hyphens for a canonical UUID string).
- Canonical form normalization (case, hyphen placement).
- Domain-specific rules: reject IDs meant for another tenant or environment.
Example regex for a canonical UUID (v1–v5, v4 included)
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
Logging, traceability, and observability
Choose IDs that are easy to include in logs and traces. For human troubleshooting, shorter readable prefixes are helpful (order-12345 or ORD_20260817_0001). For distributed tracing and analytics, stable, unique IDs that are generated at the edge let all services correlate events without a lookup.
Time-prefixed IDs help when you need to trace a recent set of events by sorting on ID. If you use random UUIDs, rely on a separate timestamp field in logs for order analysis.
Privacy considerations and what identifiers can leak
Some ID formats encode metadata—timestamp or machine identifiers. UUID v1, for example, includes a node identifier derived from MAC address in many implementations. That can expose infrastructure details or create a fingerprinting surface. Time-ordered formats can leak order timing and, if combined with other leaks, allow reconstruction of user activity patterns.
To protect privacy:
- Prefer versions without host-identifying bits (e.g., random v4 or v7 with randomized suffix).
- Strip internal identifiers from public URLs when you cannot guarantee privacy.
- Rotate or salt any scheme that might be guessable (but note salting must be deterministic or stored if it’s used for lookup).
Comparison table: quick reference
| Format | Pros | Cons |
|---|---|---|
| Auto-increment int | Compact, human-friendly, good for ordering | Single point of allocation, hard to merge shards |
| Random UUID (v4) | Easy distributed generation, strong uniqueness | Poor DB locality, long to read by humans |
| Time-ordered (ULID/UUIDv7) | Sorted insertion, good for indexes and logs | Can leak time; newer libraries may be required |
Two concrete implementation examples
Below are practical snippets you can paste into development environments to try different approaches.
Example A — PostgreSQL with UUID primary key
-- Uses the uuid-ossp extension in PostgreSQL
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
total_cents BIGINT
);
INSERT INTO orders (total_cents) VALUES (1999) RETURNING id;
-- Returns something like: 550e8400-e29b-41d4-a716-446655440000
Example B — ULID (time-ordered) generation in Node.js
// npm install ulid
const { ulid } = require('ulid');
const orderId = ulid();
console.log(orderId); // e.g. 01F8MECHZX3TBDSZ7XRADM79XV
ULID provides lexicographic sorting by generation time and is shorter and URL-safe compared to UUID string form.
Ordered workflow: how to pick the right format
- List your primary needs: distributed generation, ordering, human-readability, privacy.
- Decide whether DB locality matters: if yes, prefer time-ordered IDs or shard-local sequences.
- Consider privacy: disallow IDs that embed host MAC or user-identifying data for public-facing IDs.
- Prototype with a single service and gather metrics: index size, insert latency, and log ease-of-use.
- Reassess at scale: if inserts or merges cause issues, migrate using a stable key translation layer.
Checklist before deploying a new ID scheme
- Have you documented the exact format and canonical representation?
- Is there validation at every API boundary?
- Have you measured index and storage impact with sample data?
- Have you considered what leaks the ID might cause in public contexts?
- Is generation deterministic where needed (e.g., idempotent retries)?
Common mistakes
- Using database auto-increment IDs as public-facing identifiers — they reveal growth and are hard to merge across shards.
- Assuming zero collision without doing entropy math — shorter custom IDs can collide much sooner than you expect.
- Embedding sensitive metadata (e.g., user IDs or server MACs) without thinking about leakage.
- Mixing multiple ID formats without a canonical normalization policy — causes validation and join bugs.
Limitations and privacy considerations
No single identifier format perfectly solves all needs. Time-ordered IDs trade query performance for some leakiness about event timing. Random IDs protect privacy more but hurt index performance. Compact human-friendly IDs reduce entropy and raise collision risk. When privacy regulations matter, treat identifiers as potentially personal data if they can be linked back to a person.
Tool note: the placeholder tool UUID Generator can help you generate example UUIDs and experiment with formats. It is useful for development and testing, but it cannot certify uniqueness across distributed production environments or replace a thorough collision risk assessment.
FAQ
1. Should I expose internal IDs in public URLs?
Generally avoid exposing raw internal IDs unless they are intentionally designed for public use. If you must expose them, consider a separate, opaque public ID or HMACed token to avoid leaking order counts or timestamps.
2. Are UUID v4 values sortable?
No — UUID v4 is random and not lexicographically ordered by creation time. If you need approximate sort by creation, use a time-ordered scheme (ULID, UUIDv7) or maintain a separate timestamp column and index it.
3. How do I migrate from integers to UUIDs without downtime?
Common migration patterns use a new UUID column with a default generator, backfill existing rows, and update application code to write both keys for a transitional period. Use consistent joins and feature flags so you can roll back if necessary.
Sources
Editorial note: This guide is an educational overview. Confirm the output against the documentation and workflow that apply to your project.
