Why ULID Beats UUID: A Deep Dive into Unique, Sortable IDs
This article explains what ULID is, why it often outperforms UUID by combining timestamp and randomness for collision‑free, lexicographically sortable identifiers, details its specification, binary layout, encoding, and shows practical Python usage and common application scenarios.
ULID: Universally Unique Lexicographically Sortable Identifier
UUID: Universally Unique Identifier
Why Not Choose UUID
UUID currently has five versions:
Version 1 requires a unique, stable MAC address, which is impractical and vulnerable.
Version 2 replaces the first four timestamp bits with a POSIX UID or GID, inheriting the same issues.
Version 3 uses MD5 hashing; generating random IDs needs a unique seed, leading to data‑structure fragmentation.
Version 4 is based on random or pseudo‑random numbers and provides no additional information.
Version 5 uses SHA‑1 hashing with similar fragmentation concerns.
Version 4 is the most commonly used, but even random UUIDs carry a risk of collision. Unlike UUIDs, ULID combines a millisecond‑precision timestamp with a large random component (1.21 × 10²⁴ possibilities per millisecond), eliminating collision risk and producing a more readable string.
ULID Features
ulid() # 01ARZ3NDEKTSV4RRFFQ69G5FAV128‑bit compatibility with UUID
1.21 × 10²⁴ unique ULIDs per millisecond
Lexicographically sortable (alphabetical order)
Encoded as 26 characters instead of UUID's 36
Uses Crockford's Base32 for efficiency and readability (5 bits per character)
Case‑insensitive
No special characters (URL‑safe)
Monotonic ordering to correctly handle IDs generated within the same millisecond
ULID Specification
The following shows the current ULID specification as implemented in the Python library ulid‑py. The binary format is defined.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
10chars 16chars
48bits 80bitsComponents
Timestamp
48‑bit integer
UNIX time in milliseconds
Space will not be exhausted until year 10889.
Randomness
80‑bit random number
Cryptographically secure randomness is used when possible.
Sorting
The leftmost character must appear first and the rightmost last (lexicographic order). The default ASCII character set is required. Ordering cannot be guaranteed within the same millisecond.
Encoding
ULID uses Crockford's Base32, which excludes the letters I, L, O, and U to avoid confusion.
0123456789ABCDEFGHJKMNPQRSTVWXYZ
Binary Layout and Byte Order
Components are encoded as 16 octets, each in network byte order (big‑endian).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Application Scenarios
Replace auto‑increment primary keys in databases, eliminating the need for DB‑side key generation.
In distributed environments, replace UUIDs with a globally unique, millisecond‑ordered identifier.
Use the embedded timestamp for time‑based sharding or partitioning of tables.
If millisecond precision is acceptable, sort records directly by ULID instead of a separate created_at column.
Usage (Python)
Installation pip install ulid-py Create a brand‑new ULID. The 48‑bit timestamp is derived from time.time() with millisecond precision, and the 80‑bit random part comes from os.urandom().
>> import ulid
>>> ulid.new()
<ULID('01BJQE4QTHMFP0S5J153XCFSP9')>Create a ULID from an existing 128‑bit value such as a UUID. Supported input types include int, bytes, str, and UUID.
>> import ulid, uuid
>>> value = uuid.uuid4()
>>> value
UUID('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
>>> ulid.from_uuid(value)
<ULID('09GF8A5ZRN9P1RYDVXV52VBAHS')>Create a ULID from an existing timestamp (e.g., a datetime object). Supported timestamp types include int, float, str, bytes, bytearray, memoryview, datetime, Timestamp, and ULID.
>> import datetime, ulid
>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))
<ULID('00TM9HX0008S220A3PWSFVNFEH')>Create a ULID from an explicit random value. Supported random types include int, float, str, bytes, bytearray, memoryview, Randomness, and ULID.
>> import os, ulid
>>> randomness = os.urandom(10)
>>> ulid.from_randomness(randomness)
<ULID('01BJQHX2XEDK0VN0GMYWT9JN8S')>Once you have a ULID object, you can interact with it using various methods. The timestamp() method returns the 48‑bit timestamp snapshot, and the randomness() method returns the 80‑bit random snapshot.
>> import ulid
>>> u = ulid.new()
>>> u
<ULID('01BJQM7SC7D5VVTG3J68ABFQ3N')>
>>> u.timestamp()
<Timestamp('01BJQM7SC7')>
>>> u.randomness()
<Randomness('D5VVTG3J68ABFQ3N')>Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
