Developer tools · UUID generator
ULID, Snowflake, KSUID and UUIDv7: Sortable IDs Compared
· Background
uuid cryptography browser-apis
Random UUIDs do not sort by creation time, so several formats put a timestamp first. This post compares ULID, Snowflake, KSUID and UUIDv7 on layout, size, monotonicity and compatibility.
Random IDs and the index that hates them — the problem that time-ordered identifiers solve
Random v4 UUIDs scatter insertion points across a B-tree primary key as new records arrive, causing page splits and reorganization. Insertions at random positions degrade write performance and increase disk fragmentation significantly. High-throughput databases tolerate this cost—the price of truly independent, uncoordinated identifiers—but the cost is real. If you need UUIDs to sort by creation time, you can dramatically improve index characteristics by adding a timestamp prefix. Several formats have emerged: ULID, Snowflake, KSUID, and RFC 9562 v7. Each makes different tradeoffs in size (26 characters to 128 bits), timestamp precision (seconds to nanoseconds), UUID compatibility, and whether ID generator coordination requires centralization. Database benchmarks show insertion performance significantly improves.
ULID — a 48-bit millisecond timestamp plus 80 random bits in 26 Crockford base32 characters, with a monotonic option
ULID (Universally Unique Lexicographically Sortable Identifier) encodes a 48-bit millisecond timestamp and 80-bit random payload in 26 characters of Crockford base32. The text representation sorts correctly in lexicographic order, making ULIDs suitable for systems where timestamp ordering and readability matter—log processing, distributed tracing, microservices where identifiers need to be easily readable in human-facing output. ULID offers a monotonic variant where multiple identifiers generated within the same millisecond increment the random portion instead of repeating, ensuring even rapid ID bursts maintain strict generation order. The tradeoff is that ULID is not a UUID: it does not fit in a standard 128-bit UUID database column without encoding conversion. ULID precision covers approximately 8925 years.
Snowflake — 64-bit IDs from a timestamp, a worker ID and a sequence, and the coordination they require
Snowflake is a 64-bit identifier originally designed by Twitter, structured as a 41-bit millisecond timestamp, a 10-bit worker ID, and a 12-bit sequence number. The 41-bit timestamp covers approximately 69 years and overflows in 2106, requiring epoch coordination and migration planning. The worker ID distinguishes identifiers generated by different servers or processes—each Snowflake generator must know its own unique worker ID without conflicting with others. Snowflake is 64 bits instead of 128, making it half the size of a UUID, faster to index, and more storage-efficient per identifier. It sorts by time and worker ID, useful for routing requests or logs by source. The drawback is operational: every generator must be assigned a worker ID, clocks must be kept synchronized.
KSUID — a seconds timestamp with a large random payload, sorted as bytes
KSUID (K-Sortable Unique Identifier) is a 128-bit identifier consisting of a 32-bit Unix second timestamp and a 96-bit random payload, typically encoded as 27 base62 characters. The format is sortable in lexicographic order, and the random portion is cryptographically sound for its size. KSUID is less widely adopted than ULID or Snowflake but offers distinct semantics: the timestamp is easily decoded to a human-readable second (useful in logs and debugging), and the 96-bit random portion is large enough that multiple KSUIDs generated in the same second have effectively zero duplicate probability without sequence coordination. Unlike Snowflake, KSUID requires no worker ID coordination or central allocation. KSUID operates on seconds rather than milliseconds, so multiple IDs within one second sort randomly unless you implement additional logic.
UUIDv7 — the standards-track answer that fits existing uuid columns and tooling
RFC 9562 v7 is a 128-bit identifier consisting of a 48-bit Unix millisecond timestamp, 12 bits of sub-millisecond precision (usable as a sequence counter), and 62 random bits all combined. It sorts correctly both as a lexicographic string and as 128-bit bytes in databases. Crucially, it is a valid UUID—it sets the version nibble to 7 and variant bits to RFC 9562 standard, making it compatible with every tool, database column, and API that handles UUIDs. No encoding conversion is needed, and existing UUID infrastructure requires no modification. If multiple v7 identifiers are generated in the same millisecond, RFC 9562 recommends using the sub-millisecond field as a monotonic counter rather than random bits. V7 represents a pragmatic choice to maintain UUID compatibility.
Monotonicity within one millisecond — how each format handles bursts and why it matters for ordering guarantees
Monotonicity is the property that if two events occur in observable order, their IDs compare in that same order. At millisecond-level granularity on modern hardware, multiple events routinely occur within the same clock tick, so any sortable ID scheme must handle sub-millisecond ordering correctly. ULID offers an explicit monotonic mode where the random portion increments instead of randomizing. Snowflake includes a 12-bit sequence number that increments within a millisecond tick. KSUID lacks a built-in mechanism, so sub-second events sort randomly unless additional logic is added. RFC 9562 v7 recommends using the sub-millisecond field as a monotonic counter. If your system generates thousands of UUIDs per second, monotonicity within a millisecond significantly impacts query ordering.
What this does not cover — throughput benchmarks, which depend on hardware and language; the post stays qualitative
Throughput benchmarks and performance data are not included because they depend heavily on hardware architecture, language implementation, database engine, and caching strategy. Database performance characteristics vary significantly by whether you are measuring random inserts, range queries, index overhead, or total throughput under realistic production load. The post stays qualitative, comparing formats conceptually based on their designs rather than providing environment-specific numbers that could be misleading. Real-world performance assessment requires testing in your own environment with your own workload, codebase, and operational constraints. Benchmarking different ID formats is a valuable exercise.
Takeaway: compatibility often decides — the ToolAcre generator produces random UUIDs; use its well-formed check to confirm that a UUIDv7 from your library parses as a UUID
Compatibility often decides which format to choose. If your database schema already requires UUID columns, v7 is the modern answer to sortability without leaving the UUID ecosystem. If building a new system with custom ID types, ULID offers smaller text representation and millisecond precision advantages. If you need 64-bit storage and can manage worker ID coordination through centralized allocation, Snowflake is a proven choice in high-volume systems. The fundamental tradeoff is between standard compatibility (choose v7) and alternative properties like smaller size (Snowflake) or base32 readability (ULID). Make choices based on system constraints and ecosystem decisions.