Generator ULID

Generuje leksykograficznie sortowalne unikalne identyfikatory

Liczba

Kliknij Generuj, aby utworzyć ULID

Czym jest ULID?

ULID (Universally Unique Lexicographically Sortable Identifier) to 128-bitowy format identyfikatora zaprojektowany tak, aby być zarówno unikalnym, jak i naturalnie sortowalnym. W odróżnieniu od UUID v4, który jest całkowicie losowy, ULID osadza uniksowy znacznik czasu z dokładnością do milisekundy w swoich wysokich bitach — zapewniając, że identyfikatory generowane później sortują się po identyfikatorach generowanych wcześniej.

ULID-y są kodowane przy użyciu alfabetu Crockford's Base32, produkując kompaktowy 26-znakowy ciąg bezpieczny dla URL, bez rozróżniania wielkości liter i wolny od wizualnie niejednoznacznych znaków.

Specyfikacja ULID została stworzona przez Alizaina Feerastę i jest szeroko stosowana w systemach rozproszonych, event sourcingu i kluczach głównych baz danych, gdzie liczy się zarówno unikalność, jak i porządek czasowy. ULID-y nie są standardem IETF — są specyfikacją społecznościową dostępną na ulid.github.io.

Struktura ULID

ULID ma 128 bitów szerokości, podzielonych na dwa komponenty:

Znacznik czasuLosowy
01ARZ3NDEKTSVE4RRFFQ69G5FAV
48 bitów — uniksowy znacznik czasu w milisekundach. Koduje czas generowania z precyzją 1ms. Obejmuje daty do roku 10889.80 bitów — kryptograficznie bezpieczne losowe dane. Generowane od nowa dla każdego ULID (chyba że używany jest tryb monotoniczności).

Kodowany jako 26 znaków Crockford Base32: pierwsze 10 znaków reprezentuje znacznik czasu, ostatnie 16 znaków reprezentuje komponent losowy.

Alfabet Crockford Base32

ULID-y używają kodowania Crockford's Base32, które korzysta z 32 ze 36 znaków alfanumerycznych. Alfabet to: 0123456789ABCDEFGHJKMNPQRSTVWXYZ

Dlaczego te 32 znaki?
0123456789ABCDEFGHJKMNPQRSTVWXYZ

Cztery znaki są celowo wykluczone:

  • I i L są wykluczone — łatwo mylone z cyfrą 1
  • O jest wykluczone — łatwo mylone z cyfrą 0
  • U jest wykluczone — unika przypadkowych nieprzyzwoitych słów w generowanych identyfikatorach
  • Kodowanie jest bez rozróżniania wielkości liter01ARZ3NDEKTSV4RRFFQ69G5FAV i 01arz3ndektsv4rrffq69g5fav to ten sam ULID

Crockford Base32 jest bardziej wydajny niż szesnastkowy (32 symbole vs 16) i bardziej czytelny dla człowieka niż Base64 (bez znaków + / =, bez rozróżniania wielkości liter).

ULID a UUID

ULID-y i UUID-y reprezentują 128-bitowe identyfikatory, ale znacznie różnią się kodowaniem i celami projektowymi:

WłaściwośćULIDUUID
FormatCrockford Base32Szesnastkowy z myślnikami
Długość26 znaków36 znaków
Znacznik czasu48-bitowy uniksowy msBrak (v4) lub 48-bitowy ms (v7)
SortowalnyTak — leksykograficznyNie (v4) / Tak (v7)
Bezpieczny dla URLTak (tylko alfanumeryczny)Tak (z myślnikami)
ZależnościWymaga bibliotekiNatywny (crypto.randomUUID)
Obsługa bazy danychPrzechowuj jako CHAR(26) lub BINARY(16)Natywny typ kolumny UUID
SpecyfikacjaSpecyfikacja społecznościowa (ulid.github.io)RFC 4122 / RFC 9562

Jeśli jesteś już w ekosystemie UUID (kolumny uuid PostgreSQL, REST API, ORM z obsługą UUID), UUID v7 jest zazwyczaj lepszym wyborem niż ULID. Jeśli wolisz bardziej przyjazne dla człowieka kodowanie i kontrolujesz cały stos, ULID jest doskonałym wyborem.

ULID a nanoid

Zarówno ULID, jak i nanoid produkują krótkie, bezpieczne dla URL identyfikatory, ale mają różne cele projektowe:

WłaściwośćULIDNanoID
Znacznik czasuTak — 48-bitowy uniksowy msNie
SortowalnyTakNie
Domyślna długość26 znaków21 znaków
Entropia80 bitów (komponent losowy)~126 bitów
AlfabetCrockford Base32 (32 znaki)URL-safe Base64 (64 znaki)
Konfigurowalna długośćNieTak (dowolna długość)
Przypadek użyciaPosortowane według czasu identyfikatory, klucze główne bazy danychLosowe tokeny, krótkie URL-e, klucze API

Wybierz ULID, gdy potrzebujesz porządku czasowego. Wybierz nanoid, gdy potrzebujesz maksymalnej entropii w krótkim, losowym ciągu.

Używanie ULID w bazach danych

ULID-y można przechowywać w bazach danych na kilka sposobów w zależności od wymagań:

Przechowuj jako CHAR(26)
Najprostsze podejście. Leksykograficzny porządek sortowania jest zachowany. Nieco większe niż przechowywanie binarne, ale czytelne dla człowieka i łatwe do debugowania.
Przechowuj jako BINARY(16)
Zdekoduj ULID do jego 16-bajtowej reprezentacji binarnej dla kompaktowego przechowywania. Wymaga kodowania/dekodowania w kodzie aplikacji. Porządek sortowania jest zachowany.
PostgreSQL
Przechowuj jako CHAR(26) lub użyj typu bytea do przechowywania binarnego. Nie ma natywnego typu ULID, ale leksykograficzne porządkowanie na CHAR(26) działa poprawnie.
MySQL / MariaDB
Użyj CHAR(26) CHARACTER SET ascii do wydajnego przechowywania. Przechowywanie binarne w BINARY(16) również działa i oszczędza miejsce.
SQLite
Przechowuj jako TEXT. Domyślne porównanie tekstu SQLite sortuje ULID-y poprawnie ze względu na ich leksykograficzny projekt.
MongoDB
Przechowuj jako pole ciągu. Leksykograficzna sortowalność ULID działa naturalnie z porównaniem ciągów MongoDB.

Przykłady kodu

Generowanie ULID wymaga biblioteki ulid (dostępnej dla JavaScript, Python, Go, Rust i innych):

JavaScript (ulid npm)
// Using the 'ulid' npm package
import { ulid } from 'ulid'

const id = ulid()          // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
const id2 = ulid()         // "01ARZ3NDEKXXXXXXXXXXXX..." (same ms, incremented random)

// With a custom timestamp
const id3 = ulid(1469918176385) // deterministic time portion

// Extract the timestamp back out
import { decodeTime } from 'ulid'
decodeTime(id)  // → 1469918176385 (Unix ms)
Python (python-ulid)
# Using python-ulid
from ulid import ULID

uid = ULID()
str(uid)                    # "01ARZ3NDEKTSV4RRFFQ69G5FAV"
uid.timestamp               # 1469918176.385
uid.datetime                # datetime(2016, 7, 30, 23, 36, 16, 385000, tzinfo=timezone.utc)

# Parse an existing ULID string
parsed = ULID.from_str("01ARZ3NDEKTSV4RRFFQ69G5FAV")
parsed.timestamp            # 1469918176.385
PostgreSQL
-- Store ULIDs as TEXT or CHAR(26)
CREATE TABLE events (
  id   CHAR(26) PRIMARY KEY DEFAULT gen_ulid(),  -- if using a PG extension
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Or pass the ULID from your application layer
INSERT INTO events (id, name, created_at)
VALUES ('01ARZ3NDEKTSV4RRFFQ69G5FAV', 'user.signup', NOW());

-- Range queries are efficient because ULIDs sort chronologically
SELECT * FROM events
WHERE id > '01ARZ3NDEKTSV4RRFFQ69G5FAV'
ORDER BY id
LIMIT 20;
JavaScript (pure — no dependencies)
// Pure browser / Deno / Node.js implementation (no dependencies)
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'

function encodeTime(ms) {
  let result = '', n = BigInt(ms)
  for (let i = 9; i >= 0; i--) {
    result = CROCKFORD[Number(n & 31n)] + result
    n >>= 5n
  }
  return result
}

function encodeRandom(bytes) {
  let n = 0n
  for (const b of bytes) n = (n << 8n) | BigInt(b)
  let result = ''
  for (let i = 15; i >= 0; i--) {
    result = CROCKFORD[Number(n & 31n)] + result
    n >>= 5n
  }
  return result
}

function generateULID() {
  const randomBytes = new Uint8Array(10)
  crypto.getRandomValues(randomBytes)
  return encodeTime(Date.now()) + encodeRandom(randomBytes)
}

Często zadawane pytania

Czy ULID-y są globalnie unikalne?
Tak — z bardzo wysokim prawdopodobieństwem. 80-bitowy komponent losowy zapewnia 2^80 ≈ 1,2 × 10^24 możliwych wartości na milisekundę na generator. Prawdopodobieństwo kolizji w tej samej milisekundzie w różnych generatorach jest pomijalnie małe dla każdego praktycznego systemu. Dla ścisłych gwarancji unikalności użyj trybu monotonicznego, który inkrementuje komponent losowy dla identyfikatorów generowanych w tej samej milisekundzie.
Czy ULID jest tym samym co UUID?
Nie — ULID-y i UUID-y to różne kodowania wartości 128-bitowej. ULID nie może być bezpośrednio zastąpiony UUID w systemach, które weryfikują format UUID (szesnastkowy z myślnikami). Można jednak konwertować między binarną reprezentacją ULID a UUID w razie potrzeby.
Jak działa tryb monotoniczności ULID?
W trybie standardowym 80-bitowy komponent losowy jest generowany od nowa dla każdego ULID. W trybie monotonicznym, gdy wiele ULID jest generowanych w tej samej milisekundzie, komponent losowy drugiego i kolejnych identyfikatorów to poprzednia losowa wartość powiększona o jeden. Zapewnia to ścisłe monotonicznie rosnące porządkowanie w ciągu milisekundy na jednym generatorze.
Czy mogę zdekodować ULID, aby uzyskać znacznik czasu generowania?
Tak. Pierwsze 10 znaków Crockford Base32 koduje 48-bitowy uniksowy znacznik czasu w milisekundach. Zdekoduj te znaki przy użyciu alfabetu Crockford Base32, zinterpretuj wynik jako uniksowy znacznik czasu w milisekundach i przekonwertuj na datę. To narzędzie robi dokładnie to.
Czy ULID jest oficjalnym standardem?
Nie. ULID to specyfikacja społecznościowa utrzymywana na ulid.github.io. Nie jest standardem IETF jak UUID. Oznacza to, że nie ma oficjalnego RFC do odniesienia, a implementacje mogą się nieznacznie różnić. UUID v7 (RFC 9562, 2024) jest standaryzowaną przez IETF alternatywą z podobnymi właściwościami porządku czasowego.