The Complete Guide To Identifiers - Performance, Privacy, And Compliance In Modern Systems
ID selection is a critical early design step; poor choices cause long-term failures in DB sorting under heavy writes, system clock drift reactions, & public metadata leaks via API payloads/URLs.
Introduction
Choosing how to identify records is one of those quiet, early design steps that can silently break an application years down the line. The layout you pick dictates database sorting behavior under heavy write loads, how your services react when system clocks inevitably drift, and exactly what kind of internal metadata you leak to the public whenever an ID ends up in an API payload or URL.
Core principles
Before comparing formats, let's establish a few basic realities of systems architecture:
- Identifiers are not permission slips. RFC 9562 explicitly warns against assuming UUIDs are too hard to guess. Never rely on them as credentials or proof of authorization.
- If an ID is sorted by time, it leaks time. Making identifiers sequential exposes when they were created. If your application requires birth times to remain private, RFC 9562 recommends stick to fully random UUIDv4.
- Index performance is tied to representation. RFC 9562 defines UUIDs as 16-byte (128-bit) values encoded in network byte order, but how your database actually indexes and processes those bytes varies by engine.
UUIDs: versions, specifications, and sentinel values
UUID version overview (RFC 9562)
Under RFC 9562, which updated the classic RFC 4122 standard, the version field indicates the creation algorithm. Some are standard; others are historical footnotes:
- Version 0: Unused.
- Version 1: Time-based, combining a 60-bit Gregorian timestamp with a physical hardware MAC address (or pseudo-random node).
- Version 2: Reserved for "DCE Security" (POSIX UIDs/GIDs), with the layout details managed outside RFC 9562.
- Versions 3 & 5: Name-based (deterministic) using MD5 (v3) or SHA-1 (v5).
- Version 4: Fully random or pseudorandom.
- Version 6: A reordered version of Version 1 designed to make Gregorian timestamps sortable.
- Version 7: The modern standard, combining a 48-bit Unix millisecond epoch timestamp with random bits.
- Version 8: A sandbox for entirely custom layouts.
- Versions 9 through 15: Reserved for future standardization.
UUIDv2 (why it's omitted and what it represents)
You almost never see UUIDv2 in modern web apps, and for good reason: RFC 9562 leaves its implementation details out of scope.
If you dig into the Open Group's old DCE 1.1 security specification, you find that v2 replaces the lowest bits of the timestamp with local POSIX identifiers (like a user or group ID) to restrict uniqueness within a specific DCE "cell" or domain context. Because it embeds raw local user and group IDs, it leaks OS-level metadata by design. Unless you are maintaining deep legacy integrations with ancient distributed computing systems, you have no reason to use it.
Nil and Max UUIDs as sentinel values
The specification reserves two extreme values to act as sentinels:
-
Nil UUID
(
00000000-0000-0000-0000-000000000000): Represents the absence of a value. -
Max UUID
(
FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF): Represents the maximum possible UUID, often used as a sorting end-cap or list sentinel.
Treat these strictly as internal sentinels. It is a good practice to validate incoming payloads at your API gateway and reject these values if a client attempts to pass them in as regular entity IDs. If you need to represent "no value" in a database, use a standard nullable column rather than smuggling a Nil UUID into your primary key, unless you have a highly constrained performance requirement that justifies it.
The identifier ecosystem
Comparison of production formats
NanoID entropy math
With a 64-character default alphabet and a length of 21 characters, the math works out as follows:
$$21 \times \log_2(64) = 126\text{ bits of entropy}$$
This matches the collision safety profile of UUIDv4, without the rigid hex-and-hyphen formatting.
Locality-first alternatives (the "ObjectId" family)
When write throughput is your primary constraint, traditional random UUIDs can be problematic for database B-trees because they force random writes across the entire index. To solve this, a family of time-first formats has emerged. They prioritize database locality by starting with a timestamp, then appending random bytes to ensure uniqueness.
Common formats and how they differ
- MongoDB ObjectId: A compact 12-byte (96-bit) format split between a 4-byte timestamp, a 5-byte process-unique random value, and a 3-byte counter. The spec notes that the random portion doesn't need to be cryptographically secure.
- XID: A 12-byte variant that implements the ObjectId concept but uses a different, highly efficient base32 string serialization to avoid binary parsing overhead.
- KSUID: A larger 20-byte (160-bit) identifier. It uses a 32-bit second-precision timestamp followed by a 128-bit random payload, providing high randomness at the cost of a larger storage footprint.
- ULID: A 128-bit format that fits cleanly into existing UUID columns. It uses a millisecond-precision 48-bit timestamp and 80 bits of randomness, serialized as a 26-character base32 string.
- UUIDv7: The standardized modern solution. It has the same 128-bit size as older UUIDs but puts a 48-bit millisecond timestamp upfront, resolving database clustering problems natively.
- Firebase Realtime Database Push Keys: These are string-based, chronologically sorted keys generated by the client. While Firebase docs don't specify the exact bit layout, they guarantee alphabetical ordering based on timestamps.
- Snowflake (Twitter's layout): A 64-bit format designed for distributed generation. A common layout uses a 41-bit millisecond timestamp, 10 bits of machine/worker ID, and 12 bits of sequential counter.
- Sonyflake: A 64-bit variation of Snowflake that shifts bit allocations (using more bits for device IDs and a larger timestamp window) to support highly distributed setups without a central coordinator.
Database performance metrics
Native UUIDv7 support in PostgreSQL 18
With the release of PostgreSQL 18, UUIDv7 is now a first-class
citizen. The engine supports native generation using
uuidv7() (along with uuidv4() as an
alias for the old gen_random_uuid()).
It also introduces uuid_extract_timestamp(uuid),
which allows you to extract the embedded creation time directly
from any v1 or v7 UUID. Just keep in mind that the extracted
timestamp depends on the accuracy of the client generator that
built the UUID; do not treat it as an immutable audit log.
Real-world benchmark analysis
Analyzing public benchmarks testing heavy insert loads under PostgreSQL 18 reveals a clear performance gap between random and sequential IDs. In a single-threaded insert run:
- Throughput: Sequential UUIDv7 sees roughly a 16% to 17% increase in operations per second compared to random UUIDv4.
- Latency: Average write latency drops by about 33% under the same test conditions.
The reason for this isn't that generating the bytes is faster; it's the B-tree index structure. Because UUIDv7 is sequential, Postgres appends new rows to the end of the index page. With UUIDv4, writes are scattered randomly, forcing the database to constantly split B-tree pages and read/write random blocks from disk.
Security and privacy trade-offs
The RFC 9562 security baseline
- Identifiers are not security tokens. If an attacker can guess or predict an ID, and that ID is the only thing protecting a resource, your app is broken. RFC 9562 explicitly warns against using UUIDs as credentials.
- MAC addresses are a liability. Early UUID specs (v1) baked in the host machine's physical MAC address. Exposing these gives attackers an easy way to map your internal network topology. Modern implementations should use cryptographically secure pseudorandom numbers (CSPRNG) instead.
- UUIDv4 is the baseline for secrecy. If you need to generate identifiers for password reset links or other security-sensitive operations, use UUIDv4 to ensure there is no predictable structure.
The illusion of secrecy with deterministic IDs (UUIDv5)
Don't mistake a SHA-1 namespace-based UUIDv5 for a secure hash. If an attacker can guess the namespace and the input value, they can generate the exact same ID. It is useful for deduplication, but useless for secrecy.
Compliance considerations
Payment card security (PCI SSC guidelines)
The PCI SSC guidelines specify that payment tokens must not be mathematically derived from the Primary Account Number (PAN). While there's no rule banning timestamps in identifiers, keeping external tokens entirely random (like UUIDv4) minimizes correlation risks and ensures you can't accidentally reconstruct a card number.
Data privacy and tracking (GDPR)
GDPR defines personal data very broadly. While a random string of numbers isn't personal data in a vacuum, European regulators (like the UK ICO) point out that online identifiers can easily become personal data when combined with other system logs. Exposing time-ordered IDs allows an observer to link events and trace user activity over time, potentially pulling those IDs into GDPR scope.
Security standards (ISO 27001/27002)
These standards are highly authoritative but paywalled, which leads to a lot of online myths. ISO does not mandate or forbid specific identifier formats. They care about data classification, access control, and minimizing exposure. Decisions like using opaque IDs externally are implementation details that support those broader security controls.
Decision framework
External-facing identifiers (APIs and URLs)
We recommend using opaque, non-sequential IDs like UUIDv4, NanoID, or CUID2 here. It prevents third parties from reverse-engineering business metrics, such as sign-up volumes or daily transactions, and eliminates the risk of chronological tracking.
Internal database primary keys
For tables with high write volume, time-ordered formats like UUIDv7, ULID, or Snowflake-style IDs are highly effective. They keep index page splits minimal and write throughput high, all while allowing you to generate unique keys in a distributed environment without needing a central lock.
The hybrid approach: dual-ID architecture
The most robust architectural pattern is often to use both:
-
Store a sequential
internal_id(like UUIDv7) for database joins, indexing, and clustering. -
Generate an opaque
public_id(like UUIDv4) to expose through public APIs and URLs.
This cleanly separates internal performance optimization from external security and privacy requirements. It does require slightly more storage, but it resolves the trade-offs on both sides.
Operational considerations
Managing clock drift and monotonicity
Because time-ordered IDs rely on system clocks, clock rollbacks or NTP sync jumps pose a real risk to ordering and uniqueness. RFC 9562 provides strategies for handling this:
- Wait for the clock to catch up before generating the next ID, or
- Increment a monotonic counter in the random bits block if the timestamp matches or falls behind the previous value.
Make sure the library you choose implements proper monotonic guards.
Efficient database storage
Always store UUIDs in their native binary formats (like the
16-byte UUID column in Postgres or
BINARY(16) in MySQL) rather than as 36-character
strings. It saves significant disk space and speeds up index
lookups dramatically.
Experimental formats
SNID
You will occasionally run into formats like SNID that claim to offer UUIDv7 compatibility while packing in custom feature sets like logical "families." Treat these with caution. Unless they are backed by an open standard or widely adopted in your runtime's ecosystem, you risk locking yourself into a custom library that might be abandoned next year.
Conclusion
There is no single "best" identifier. The trick is recognizing where the boundaries of your system lie. Keep your database indexes healthy by using sequential keys internally, protect your users and your business metrics by using opaque random keys externally, and always treat UUIDs as names, never as permission slips.
References
Standards / specifications (primary)
- RFC 9562 (UUIDs): https://www.rfc-editor.org/rfc/rfc9562
- RFC 9562 PDF (IETF mirror): https://www.ietf.org/rfc/rfc9562.pdf
- Open Group DCE 1.1: Authentication and Security Services (C311): https://pubs.opengroup.org/onlinepubs/9696989899/toc.pdf
- Open Group DCE 1.1 (Privilege services chapter): https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm
Databases (vendor documentation)
- PostgreSQL 18 UUID type: https://www.postgresql.org/docs/18/datatype-uuid.html
- PostgreSQL 18 UUID functions: https://www.postgresql.org/docs/18/functions-uuid.html
Benchmarks (public, reproducible repos)
- UUIDv7 Postgres benchmark repo: https://github.com/spa5k/uuidv7-postgres-benchmark
Identifier formats (project/vendor specifications)
- ULID spec: https://github.com/ulid/spec
- KSUID (Go docs): https://pkg.go.dev/github.com/segmentio/ksuid
- MongoDB ObjectId spec: https://github.com/mongodb/specifications/blob/master/source/bson-objectid/objectid.md
- MongoDB BSON types (ObjectId description): https://www.mongodb.com/docs/current/reference/bson-types/
- XID: https://github.com/rs/xid
- NanoID: https://github.com/ai/nanoid
- TypeID: https://github.com/jetify-com/typeid
- TypeID Go docs: https://pkg.go.dev/go.jetify.com/typeid/v2
- TSID Creator: https://github.com/f4b6a3/tsid-creator
- Twitter Snowflake archived repo: https://github.com/twitter-archive/snowflake
- Tracking Twitter’s growth after Snowflake (paper): https://jbum.com/papers/TrackingTwittersGrowthAfterSnowflake.pdf
- SnowflakeTwitterId (Rust docs): https://docs.rs/ferroid/latest/ferroid/id/struct.SnowflakeTwitterId.html
- Sonyflake: https://github.com/sony/sonyflake
- Firebase RTDB push() key ordering statement: https://firebase.google.com/docs/database/admin/save-data
- CUID2: https://github.com/paralleldrive/cuid2
- SNID docs.rs (spec link inside): https://docs.rs/snid/latest/snid/
Compliance / legal / governance (authoritative + source-access notes)
- PCI SSC Tokenization Guidelines (Aug 2011): https://www.pcisecuritystandards.org/documents/Tokenization_Guidelines_Info_Supplement.pdf
- ICO “What are identifiers and related factors?”: https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/personal-information-what-is-it/what-is-personal-data/what-are-identifiers-and-related-factors/
- ICO “What is personal data?” PDF: https://ico.org.uk/media/for-organisations/guide-to-data-protection/guide-to-the-general-data-protection-regulation-gdpr/what-is-personal-data-1-0.pdf
- ISO/IEC 27001:2022 (publication page, paywalled text): https://www.iso.org/standard/82875.html
- ISO/IEC 27002:2022 (publication page, paywalled text): https://www.iso.org/standard/75652.html