ULID چیست؟
ULID (Universally Unique Lexicographically Sortable Identifier) یک قالب شناسه ۱۲۸ بیتی است که برای یکتا بودن و قابلیت مرتبسازی طبیعی طراحی شده است. بر خلاف UUID v4 که کاملاً تصادفی است، یک ULID یک زماننمای Unix با دقت میلیثانیه را در بیتهای ابتدایی خود جاسازی میکند — و این تضمین میکند که شناسههای جدیدتر بعد از شناسههای قدیمیتر مرتب شوند.
ULIDها با الفبای Crockford Base32 رمزگذاری میشوند و یک رشته فشرده ۲۶ کاراکتری تولید میکنند که برای URL امن، حروف کوچک و بزرگ در آن یکسان، و عاری از کاراکترهای مبهم بصری است.
مشخصات ULID توسط Alizain Feerasta ایجاد شده و در سیستمهای توزیعشده، معماریهای رویدادمحور، و کلیدهای اصلی پایگاه داده که هم یکتایی و هم ترتیب زمانی اهمیت دارد، بهطور گسترده استفاده میشود. ULIDها یک استاندارد IETF نیستند — بلکه یک مشخصه جامعهمحور هستند که در ulid.github.io در دسترس است.
ساختار ULID
یک ULID ۱۲۸ بیت پهنا دارد و به دو بخش تقسیم میشود:
| زماننما | تصادفی |
|---|---|
| 01ARZ3NDEK | TSVE4RRFFQ69G5FAV |
| ۴۸ بیت — زماننمای Unix بر حسب میلیثانیه. زمان تولید را با دقت ۱ میلیثانیه رمزگذاری میکند. تاریخها را تا سال ۱۰۸۸۹ پوشش میدهد. | ۸۰ بیت — داده تصادفی رمزنگاریشده امن. برای هر ULID از نو تولید میشود (مگر اینکه از حالت یکنواختی استفاده شود). |
به صورت ۲۶ کاراکتر Crockford Base32 رمزگذاری میشود: ۱۰ کاراکتر اول نشاندهنده زماننما و ۱۶ کاراکتر آخر نشاندهنده بخش تصادفی است.
الفبای Crockford Base32
ULIDها از رمزگذاری Crockford Base32 استفاده میکنند که ۳۲ کاراکتر از ۳۶ کاراکتر الفبای عددی را بهکار میبرد. الفبا عبارت است از: 0123456789ABCDEFGHJKMNPQRSTVWXYZ
چهار کاراکتر بهعمد حذف شدهاند:
IوLحذف شدهاند — بهراحتی با رقم1اشتباه گرفته میشوندOحذف شده است — بهراحتی با رقم0اشتباه گرفته میشودUحذف شده است — از ایجاد کلمات ناخواسته در شناسههای تولیدشده جلوگیری میکند- این رمزگذاری بین حروف کوچک و بزرگ تفاوتی قائل نمیشود (case-insensitive) —
01ARZ3NDEKTSV4RRFFQ69G5FAVو01arz3ndektsv4rrffq69g5favیک ULID یکسان هستند
Crockford Base32 نسبت به هگزادسیمال کارآمدتر (۳۲ نماد در برابر ۱۶) و نسبت به Base64 خواناتر برای انسان است (بدون کاراکترهای + / = و با حروف کوچک/بزرگ یکسان).
ULID در برابر UUID
ULIDها و UUIDها هر دو نشاندهنده شناسههای ۱۲۸ بیتی هستند، اما از نظر رمزگذاری و اهداف طراحی تفاوت قابل توجهی دارند:
| ویژگی | ULID | UUID |
|---|---|---|
| قالب | Crockford Base32 | هگز با خط تیره |
| طول | ۲۶ کاراکتر | ۳۶ کاراکتر |
| زماننما | ۴۸ بیتی Unix میلیثانیه | ندارد (v4) یا ۴۸ بیتی میلیثانیه (v7) |
| قابل مرتبسازی | بله — لغوی | خیر (v4) / بله (v7) |
| امن برای URL | بله (فقط الفبای عددی) | بله (با خط تیره) |
| وابستگیها | نیاز به کتابخانه دارد | بومی (crypto.randomUUID) |
| پشتیبانی پایگاه داده | ذخیره به صورت CHAR(26) یا BINARY(16) | نوع ستون UUID بومی |
| مشخصه | مشخصه جامعهمحور (ulid.github.io) | RFC 4122 / RFC 9562 |
اگر از پیش در اکوسیستم UUID هستید (ستونهای uuid در PostgreSQL، APIهای REST، ORMهای دارای پشتیبانی UUID)، UUID v7 معمولاً گزینه بهتری نسبت به ULID است. اگر رمزگذاری دوستانهتر برای انسان را ترجیح میدهید و کل پشته را کنترل میکنید، ULID انتخاب عالی است.
ULID در برابر nanoid
هر دو ULID و nanoid شناسههای کوتاه و امن برای URL تولید میکنند، اما اهداف طراحی متفاوتی دارند:
| ویژگی | ULID | NanoID |
|---|---|---|
| زماننما | بله — ۴۸ بیتی Unix میلیثانیه | خیر |
| قابل مرتبسازی | بله | خیر |
| طول پیشفرض | ۲۶ کاراکتر | ۲۱ کاراکتر |
| آنتروپی | ۸۰ بیت (بخش تصادفی) | ~۱۲۶ بیت |
| الفبا | Crockford Base32 (32 کاراکتر) | Base64 امن برای URL (64 کاراکتر) |
| طول قابل تنظیم | خیر | بله (هر طول) |
| موارد استفاده | شناسههای زمانمحور، کلیدهای اصلی پایگاه داده | توکنهای تصادفی، URLهای کوتاه، کلیدهای API |
ULID را انتخاب کنید وقتی به ترتیب زمانی نیاز دارید. nanoid را انتخاب کنید وقتی به حداکثر آنتروپی در یک رشته کوتاه و تصادفی نیاز دارید.
استفاده از ULIDها در پایگاههای داده
ULIDها میتوانند بسته به نیاز شما به چند روش در پایگاههای داده ذخیره شوند:
نمونههای کد
تولید ULID نیاز به کتابخانه ulid دارد (برای JavaScript، Python، Go، Rust و موارد دیگر در دسترس است):
// 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)# 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-- 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;// 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)
}