Comprehensive comparison of UUID v4, UUID v7, and ULID: how to balance time order, efficiency, and readability
UUIDs and ULIDs are schemes for issuing “globally unique” IDs in distributed systems and databases. They look similar at first glance, yet differ in standardization status, storage efficiency, human readability, and the risk of leaking information. This article compares the three dominant approaches—UUID v4, UUID v7, and ULID—and closes with notes on UUID v1 and UUID v6.
If you want to experiment in the browser, the following tools handle both generation and timestamp extraction entirely client-side:
- Generate UUID v4
- Generate UUID v7
- Read timestamps from UUID v7
- Generate ULID
- Read timestamps from ULID
UUID v4: the purely random variant
UUID v4 dedicates 122 of its 128 bits to randomness, keeping collision probability astronomically low. The trade-off is that the IDs do not sort in chronological order, so B-tree indexes experience frequent page splits and poor insertion locality. Use v4 for session IDs, one-time tokens, and other cases where “pure randomness” is desirable.
UUID v7: the chronological evolution
Standardized in RFC 9562 (2024), UUID v7 stores the UNIX epoch timestamp in milliseconds in the top 48 bits and fills the remainder with randomness. Lexicographic order (string comparison) becomes identical to generation order (time order), so using v7 as a primary key preserves index locality. It also fits existing uuid
types or BINARY(16)
columns without modification.
- Randomness budget: apart from the version and variant bits, v7 keeps roughly 74 bits of randomness. Some implementations perform monotonic adjustments to avoid collisions within the same millisecond; even so, the entropy is more than sufficient.
- Information exposure: the ID reveals generation time with millisecond precision. Weak randomness combined with high issuance rates can make neighboring IDs easier to guess. Internal systems can generally accept this, but public endpoints that must hide ordering should evaluate the risk.
ULID: human-friendly IDs
ULID, proposed in 2016, has become a de facto standard. It uses Crockford Base32 (26 characters), omitting easily confused characters such as O/I/L/1, making it well-suited for URLs and copy-paste workflows. Like v7, the top 48 bits store the timestamp (ms), so lexicographic order matches chronological order.
- Sorting guarantee: ULIDs always compare in chronological order when treated as Base32 strings (monotonic generation is widely implemented to avoid collisions).
- Storage format: storing them as
TEXT(26)
preserves readability, but binary storage in databases requires Base32↔16-byte conversion. Most RDBMSUUID
types cannot store ULIDs directly. - Information exposure: just like v7, ULIDs reveal timestamps (ms). If you do not want API consumers to infer issuance order, consider alternatives.
Comparison table (practical view)
Aspect | UUID v4 | UUID v7 | ULID |
---|---|---|---|
Standardization | RFC 4122 | RFC 9562 (2024) | De facto, not standardized |
Bit layout | 128 bits with 122 random | 48-bit UNIX ms + remaining random (monotonic optional) |
48-bit UNIX ms + 80-bit random |
Random entropy | ≈122 bits | ≈74 bits (after version/variant) | 80 bits |
String form | 36 chars (hex with hyphens) | 36 chars (hex with hyphens) | 26 chars (Crockford Base32) |
Natural ordering (text) | ✗ (random) | ✓ (lexicographic = chronological) | ✓ (lexicographic = chronological) |
Natural ordering (binary) | ✗ | ✓ (BINARY(16) compares in ascending time order) |
✓ (when the 6-byte timestamp is big-endian before the random tail) |
Database fit | uuid / BINARY(16) are ideal |
uuid / BINARY(16) are ideal |
TEXT(26) keeps readability; BINARY(16) requires conversion and many UUID types reject it |
Index locality | Poor (random insert hot spots) | Good (append-friendly, though hot spots can appear) | Good (append-friendly, same caution) |
Equality check cost | Low (16-byte compare) | Low (16-byte compare) | TEXT comparisons cost more; BINARY is low |
Encoding overhead | Hex↔16 bytes (cheap) | Hex↔16 bytes (cheap) | Base32↔16 bytes (heavier) |
Human readability / URL suitability | Low | Low | High |
Information exposure | None | Timestamp (ms) | Timestamp (ms) |
Typical uses | Session IDs, one-time keys | Database primary keys, event IDs, log ordering | Public IDs and logs where readability helps |
Hot-spot caution for v7/ULID: if all inserts target a single shard or leader, append-heavy workloads can still produce hot B-tree pages. Techniques such as prefix shuffling or combining the ID with a shard key help spread the load.
Database implementation notes (PostgreSQL / MySQL / SQLite)
- PostgreSQL
- v4/v7: the
uuid
type is optimal. Import v7 as text, cast touuid
, and you get natural ordering. - ULID: use
char(26)
/text
orbytea(16)
after Base32 conversion.
- v4/v7: the
- MySQL / InnoDB
BINARY(16)
is compact and fast for v4/v7. v7 remains time-ordered in big-endian form. ULID fitsCHAR(26)
orBINARY(16)
with conversion.
- SQLite
- There is no native UUID type. Use
BLOB(16)
orTEXT
. Indexed BLOBs work well enough.
- There is no native UUID type. Use
Security considerations
- Guess resistance: v7/ULID share the same timestamp bits. If you issue many IDs within the same millisecond and the random generator is weak, nearby IDs become easier to predict. Use cryptographically secure PRNGs and keep monotonic adjustments unbiased.
- Metadata leakage: IDs expose the generation time and rough issuance volume. For public APIs, consider separating internal IDs from the identifiers you expose externally.
Summary: choosing the right option
- UUID v4: pure randomness. No inherent ordering. Theoretical collisions are negligible. Best suited for session IDs, CSRF tokens, and similar use cases.
- UUID v7: standardized ordered UUID. Ideal for internal systems and database primary keys. Monotonic generation makes collisions practically impossible.
- ULID: comparatively human-friendly. Great for URLs and logs when readability matters, but often less convenient than v7 for internal primary keys. Monotonic generation keeps collisions at bay.
Appendix: UUID v1 and v6 (briefly)
-
UUID v1 (time-based + MAC address) Uses a 60-bit timestamp (100 ns resolution) plus a node identifier (often the MAC address). It offers strong chronological order but risks exposing host information via the MAC address. Implementations must also handle clock rollback carefully. Modern systems often avoid v1 for privacy reasons.
-
UUID v6 (reordered v1) Rearranges v1’s timestamp into big-endian order for better sorting. Historically viewed as a “sortable v1”, but standardization converged on v7 instead. New designs should prefer v7 over v6.
Implementation checklist
- Use a cryptographically secure PRNG for randomness.
- When using monotonic generation for v7/ULID, avoid introducing biased randomness while preventing same-millisecond collisions.
- Favor
uuid
/BINARY(16)
columns to dodge collation costs on string columns. - Decide on an external ID policy (do you allow exposing issuance order?). Separate internal and external IDs when necessary.
Bottom line: with today’s tooling, UUID v7 is the default choice for internal primary keys, while ULID is compelling when human readability or legacy constraints matter. Reserve v4 or other schemes for the rare scenarios where timestamp exposure is unacceptable.