UUID vs ULID — Which Unique ID Format Is Better?
Compare UUID and ULID unique identifier formats. Understand sortability, performance implications, and when to choose each for databases and APIs.
| Feature | UUID | ULID |
|---|---|---|
| Length | 36 chars (with hyphens) | 26 chars |
| Sortable by Time | No (v4), Yes (v7) | Yes, always |
| Database Index Performance | Poor for random v4 | Excellent (sequential) |
| Standardization | RFC 4122 (official) | Community spec (unofficial) |
| Timestamp Embedded | No (v4), Yes (v7) | Yes (48-bit ms precision) |
| Randomness Bits | 122 bits (v4) | 80 bits |
| Encoding | Hex with hyphens | Crockford Base32 |
| Language Support | Universal | Good but not universal |
Verdict
For new systems with high write throughput, ULID is the better choice — its time-sortable nature dramatically improves database index performance and query patterns. Use UUID (preferably v7) when you need RFC compliance, universal tooling compatibility, or are working within an existing UUID-based system.
The Real Cost of Random UUIDs in Databases
When you insert a row with a random UUIDv4 as the primary key, the database index (typically a B-tree) must insert that key value somewhere in the middle of the existing sorted key space. This causes 'page splits' where the database splits a full index page to make room, leading to fragmentation over time. On tables with millions of rows and high insert rates, this measurably degrades performance. A Percona benchmark showed that switching from random UUIDs to sequential IDs can improve insert throughput by 3-5x on large tables. ULID solves this by being lexicographically increasing, so new inserts always append to the 'right end' of the index.
Time-Sortable IDs and Their Practical Benefits
Beyond database performance, time-sortable IDs unlock practical query patterns. With ULIDs, you can efficiently query for records created after a specific time without a separate created_at column — just query WHERE id > ulid_from_timestamp(cutoff). Pagination becomes more reliable because IDs are stable and ordered. Log analysis and debugging benefit from being able to see at a glance which records are older or newer. These benefits compound at scale, making time-sortable IDs a genuinely valuable architectural choice for any system that cares about creation order.
Migration Paths and Compatibility
If you're starting a new project, ULID or UUIDv7 are compelling choices for primary keys on high-write tables. For existing systems using UUIDv4, migration is possible but risky — changing primary key types requires updating all foreign key relationships and rebuilding indexes. The pragmatic approach is to use UUIDv7 for new tables (since it's RFC-standard and UUID-compatible) and accept UUIDv4 in legacy tables. ULID is best chosen from the start of a project, especially if you want the compact Base32 encoding in your URLs and API responses.
Frequently Asked Questions
UUIDv7 (finalized in RFC 9562) offers time-ordered UUIDs similar to ULID, addressing the main database performance criticism of UUIDv4. However, ULID remains more compact (26 vs 36 chars) and uses Base32 encoding which avoids some readability issues.
Significantly for large tables. Random UUIDs cause B-tree index page splits on every insert because new values land randomly within the existing key space. This leads to fragmentation, increased I/O, and degraded write performance. Sequential IDs (ULID, UUIDv7, auto-increment) insert at the 'end' of the index, avoiding this issue.
Yes, ULID's 128 bits can be stored as a UUID. Some libraries provide direct conversion, allowing you to store ULIDs in UUID database columns while retaining sortability benefits.
With care. The timestamp component leaks creation time, which may be undesirable for IDs that must not reveal when a resource was created. For session tokens or secret identifiers, use UUIDv4 or a purpose-built token format instead.