UUID vs ULID: Which Unique Identifier to Choose

May 27, 20266 min read

The Short Answer

Use UUID v7 if you want broad ecosystem support with sortability. Use ULID if you want sortability plus a compact, URL-safe string. Both are better than UUID v4 for database-heavy applications.

Format Comparison

PropertyUUID v4UUID v7ULID
Length36 chars36 chars26 chars
EncodingHex + hyphensHex + hyphensCrockford's Base32
TimestampNone48-bit ms48-bit ms
Random bits1226280
SortableNoYesYes
URL-safeMostlyMostlyYes (no hyphens)

Visual Comparison

UUID v4:  f47ac10b-58cc-4372-a567-0e02b2c3d479   (random)
UUID v7:  0190a6a8-7b1c-7d3e-8f2a-5c9b1d3e4f5a   (time-prefixed)
ULID:     01ARZ3NDEKTSV4RRFFQ69G5FAV              (time-prefixed, compact)

Notice how v7 and ULID share the same timestamp prefix — both encode the generation time at the start of the identifier.

Sortability: The Real Difference

UUID v4 generates identifiers in random order. When inserted into a B-tree index, each new row lands in a random leaf node, causing:

  • Page splits: Database constantly reorganizes index pages
  • Cache misses: Random page access defeats the buffer pool
  • Write amplification: Fragmented inserts trigger more I/O

UUID v7 and ULID solve this by prefixing a timestamp. New rows append to the right edge of the index, just like auto-increment IDs.

Benchmark Impact

In PostgreSQL with 10M rows:

MetricUUID v4UUID v7 / ULID
Insert throughput~35K rows/sec~55K rows/sec
Index size~1.4× largerBaseline
Table sizeSimilarSimilar

The exact numbers vary by hardware and configuration, but the pattern is consistent: time-ordered identifiers significantly outperform random ones at scale.

UUID v7 vs ULID: Which One?

Since both are time-ordered, the decision comes down to practical differences.

Choose UUID v7 When

  • Your database has native UUID type support (PostgreSQL, Cassandra)
  • You want RFC-standardized identifiers
  • You rely on existing UUID parsing libraries
  • You store identifiers as 128-bit binary (16 bytes) rather than text

Choose ULID When

  • You want the most compact text representation (26 vs 36 characters)
  • Identifiers appear in URLs, logs, or user-facing strings
  • You need case-insensitive comparison (Crockford's Base32 avoids ambiguous characters like 0/O)
  • You want monotonic generation within the same millisecond

Monotonicity

ULID's spec defines monotonic generation: within the same millisecond, each new ULID increments the random portion instead of regenerating it. This guarantees strict ordering even for sub-millisecond inserts.

UUID v7's spec allows monotonic generation but does not mandate it. Implementations vary — check your library.

Migration Considerations

From UUID v4 to UUID v7

  • Drop-in replacement in most systems — same 128-bit width, same text format
  • Existing v4 IDs remain valid; no data migration needed
  • Mixed v4/v7 columns still work; new rows simply benefit from sortability

From UUID to ULID

  • Column type may need to change (UUID → text or binary)
  • String length changes (36 → 26), affecting foreign keys and indexes
  • Existing UUID data must be migrated or stored alongside new ULIDs
  • Higher migration cost — plan accordingly

Practical Recommendations

  1. New project, PostgreSQL: Use UUID v7 — native type support keeps storage at 16 bytes
  2. New project, MySQL/SQLite: Use ULID — text encoding avoids binary column complexity
  3. Existing project with UUID v4: Switch new inserts to UUID v7 — zero migration cost
  4. High-throughput logging: Use ULID — compact strings reduce I/O and storage
  5. Client-generated IDs: Either works; ULID's shorter string is easier to pass in URLs

Try It Yourself

Generate and compare UUID v4, v7, and ULID formats instantly with our free UUID Generator. See sortability in action — paste a batch of v4 vs v7 IDs and watch the ordering difference.