Understanding ULID: A Lexicographically Sortable Unique Identifier and Its Python Implementation
ULID (Universally Unique Lexicographically Sortable Identifier) offers a 128‑bit, time‑based and random identifier that is URL‑safe, sortable, and more collision‑resistant than UUID, with detailed specifications, binary layout, and Python usage examples including generation, conversion, and component extraction.
ULID (Universally Unique Lexicographically Sortable Identifier) is presented as an alternative to UUID, providing a 128‑bit identifier that combines a millisecond‑precision timestamp with a large random component, resulting in lexicographically sortable, URL‑safe IDs with a negligible risk of collision.
Unlike UUID’s five versions—where version 1 requires a MAC address, version 2 replaces part of the timestamp with UID/GID, versions 3 and 5 rely on hash functions, and version 4 is purely random—ULID merges time and randomness, offering better ordering and readability.
Key ULID characteristics:
128‑bit compatibility with UUID
1.21×10²⁴ unique IDs per millisecond
Lexicographic (dictionary) ordering
Encoded as a 26‑character Crockford Base32 string (no I, L, O, U)
Case‑insensitive and URL‑safe
Monotonic ordering within the same millisecond
ULID specification: The identifier consists of a 48‑bit timestamp (UNIX time in milliseconds) and an 80‑bit random component. The binary layout is encoded in network byte order across 16 bytes.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
10 chars 16 chars
48 bits 80 bitsComponents:
Timestamp
48‑bit integer representing UNIX time in milliseconds
Valid until the year 10889
Randomness
80‑bit random number, preferably generated with a cryptographically secure source
Sorting: Characters are ordered lexicographically; the leftmost character is the most significant. Within the same millisecond, strict ordering cannot be guaranteed.
Encoding: Uses Crockford’s Base32 alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ) to improve efficiency and readability.
0123456789ABCDEFGHJKMNPQRSTVWXYZBinary layout and byte order: The 16‑byte representation is big‑endian (network byte order).
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16_bit_uint_time_low | 16_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32_bit_uint_random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Typical use cases:
Replace auto‑increment primary keys in databases without DB involvement
Distributed systems needing globally unique, time‑ordered IDs
Database sharding/partitioning based on embedded timestamps
Sorting records by ULID instead of a separate timestamp column when millisecond precision suffices
Python usage (ulid‑py library):
pip install ulid-pyGenerate a new ULID:
>> import ulid
>>> ulid.new()Convert an existing UUID to ULID:
>> import ulid, uuid
>>> value = uuid.uuid4()
>>> ulid.from_uuid(value)Create a ULID from a specific timestamp:
>> import datetime, ulid
>>> ulid.from_timestamp(datetime.datetime(1999, 1, 1))Generate a ULID from custom randomness:
>> import os, ulid
>>> randomness = os.urandom(10)
>>> ulid.from_randomness(randomness)Access ULID components:
>> u = ulid.new()
>>> u.timestamp()
>>> u.randomness()For more information, see the project repository at https://github.com/ahawker/ulid .
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.