مولّد ULID

يولد معرفات فريدة قابلة للترتيب أبجدياً

العدد

انقر على توليد لإنشاء ULIDs

ما هو ULID؟

ULID (المعرّف الفريد العالمي القابل للترتيب لغوياً) هو تنسيق معرّف 128 بت مصمَّم ليكون فريداً وقابلاً للترتيب بطبيعته. خلافاً لـ UUID v4 العشوائي كلياً، يضمّ ULID طابعاً زمنياً Unix بدقة المللي ثانية في بتاته العليا — مما يضمن أن المعرّفات المولَّدة لاحقاً تُرتَّب بعد المعرّفات المولَّدة سابقاً.

تُرمَّز ULIDs باستخدام أبجدية Crockford Base32، منتجةً سلسلة مضغوطة مكوّنة من 26 حرفاً آمنة في عناوين URL وغير حساسة لحالة الأحرف وخالية من الأحرف الملتبسة بصرياً.

أنشأ مواصفة ULID Alizain Feerasta وتُستخدَم على نطاق واسع في الأنظمة الموزّعة وتوليد الأحداث والمفاتيح الأساسية لقواعد البيانات حيث يهمّ التفرّد والترتيب الزمني معاً. ULIDs ليست معياراً IETF — إنها مواصفة مجتمعية متاحة على ulid.github.io.

بنية ULID

ULID عرضه 128 بت، مقسَّمة على مكوّنين:

الطابع الزمنيعشوائي
01ARZ3NDEKTSVE4RRFFQ69G5FAV
48 بتاً — طابع زمني Unix بالمللي ثانية. يرمّز وقت التوليد بدقة 1 مللي ثانية. يغطّي التواريخ حتى عام 10889.80 بتاً — بيانات عشوائية آمنة تشفيرياً. تُعاد توليدها من جديد لكل ULID (ما لم يُستخدَم وضع الرتابة).

مرمَّزة كـ 26 حرفاً من Crockford Base32: أول 10 أحرف تمثّل الطابع الزمني، آخر 16 حرفاً تمثّل المكوّن العشوائي.

أبجدية Crockford Base32

تستخدم ULIDs ترميز Crockford Base32، الذي يستخدم 32 من أصل 36 حرفاً حروفياً رقمياً. الأبجدية هي: 0123456789ABCDEFGHJKMNPQRSTVWXYZ

لماذا هذه الـ 32 حرفاً؟
0123456789ABCDEFGHJKMNPQRSTVWXYZ

أربعة أحرف مستبعَدة عمداً:

  • I وL مستبعَدان — يُخلَطان بسهولة بالرقم 1
  • O مستبعَد — يُخلَط بسهولة بالرقم 0
  • U مستبعَد — يتجنّب ظهور كلمات مسيئة بالصدفة في المعرّفات المولَّدة
  • الترميز غير حساس لحالة الأحرف01ARZ3NDEKTSV4RRFFQ69G5FAV و01arz3ndektsv4rrffq69g5fav هما ULID ذاته

Crockford Base32 أكفأ من السداسي عشري (32 رمزاً مقابل 16) وأكثر قابلية للقراءة البشرية من Base64 (لا أحرف + / = وغير حساس لحالة الأحرف).

ULID مقابل UUID

كل من ULID وUUID يمثّلان معرّفات 128 بت، لكنهما يختلفان اختلافاً كبيراً في الترميز وأهداف التصميم:

الخاصيةULIDUUID
التنسيقCrockford Base32سداسي عشري بواصلات
الطول26 حرفاً36 حرفاً
الطابع الزمني48 بت Unix msلا شيء (v4) أو 48 بت ms (v7)
قابل للترتيبنعم — لغويلا (v4) / نعم (v7)
آمن في URLنعم (حروفي رقمي فقط)نعم (مع واصلات)
التبعياتيتطلب مكتبةأصيلي (crypto.randomUUID)
دعم قاعدة البياناتخزّن كـ CHAR(26) أو BINARY(16)نوع عمود UUID أصيلي
المواصفةمواصفة مجتمعية (ulid.github.io)RFC 4122 / RFC 9562

إذا كنت بالفعل في بيئة UUID (أعمدة uuid في PostgreSQL وواجهات برمجية وORMs بدعم UUID)، فإن UUID v7 عادةً أنسب من ULID. إذا كنت تفضّل ترميزاً أكثر ملاءمة للإنسان وتتحكّم في المكدّس الكامل، فـ ULID اختيار ممتاز.

ULID مقابل nanoid

كلٌّ من ULID وnanoid ينتجان معرّفات قصيرة آمنة في URL، لكن لكلٍّ منهما أهداف تصميم مختلفة:

الخاصيةULIDNanoID
الطابع الزمنينعم — 48 بت Unix msلا
قابل للترتيبنعملا
الطول الافتراضي26 حرفاً21 حرفاً
الإنتروبيا80 بتاً (المكوّن العشوائي)~126 بتاً
الأبجديةCrockford Base32 (32 حرفاً)Base64 آمن في URL (64 حرفاً)
طول قابل للتخصيصلانعم (أي طول)
حالة الاستخداممعرّفات مرتّبة زمنياً، مفاتيح أساسية في DBرموز عشوائية وعناوين URL قصيرة ومفاتيح API

اختر ULID حين تحتاج الترتيب الزمني. اختر nanoid حين تريد أقصى إنتروبيا في سلسلة قصيرة عشوائية.

استخدام ULIDs في قواعد البيانات

يمكن تخزين ULIDs في قواعد البيانات بعدة طرق حسب متطلباتك:

خزّن كـ CHAR(26)
النهج الأبسط. ترتيب الترتيب اللغوي محفوظ. أكبر قليلاً من التخزين الثنائي لكنه مقروء بشرياً وسهل تصحيح الأخطاء.
خزّن كـ BINARY(16)
فكّ ترميز ULID إلى تمثيله الثنائي البالغ 16 بايت للتخزين المضغوط. يتطلب الترميز وفكّ الترميز في كود التطبيق. ترتيب الترتيب محفوظ.
PostgreSQL
خزّن كـ CHAR(26) أو استخدم نوع bytea للتخزين الثنائي. لا يوجد نوع ULID أصيلي، لكن الترتيب اللغوي على CHAR(26) يعمل بشكل صحيح.
MySQL / MariaDB
استخدم CHAR(26) CHARACTER SET ascii للتخزين الكفء. التخزين الثنائي في BINARY(16) يعمل أيضاً ويوفّر المساحة.
SQLite
خزّن كـ TEXT. مقارنة النص الافتراضية في SQLite تُرتِّب ULIDs بشكل صحيح بفضل تصميمها اللغوي.
MongoDB
خزّن كحقل سلسلة. تعمل قابلية ترتيب ULID لغوياً بشكل طبيعي مع مقارنة سلاسل MongoDB.

أمثلة الكود

يتطلب توليد ULID مكتبة ulid (متاحة لـ JavaScript وPython وGo وRust وغيرها):

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)
}

الأسئلة الشائعة

هل ULIDs فريدة عالمياً؟
نعم — باحتمالية عالية جداً. يوفّر المكوّن العشوائي 80 بت 2^80 ≈ 1.2 × 10^24 قيمة ممكنة لكل مللي ثانية لكل مولِّد. احتمالية التصادم داخل المللي ثانية ذاتها عبر مولِّدات مختلفة ضئيلة لأي نظام عملي. للحصول على ضمانات تفرّد صارمة، استخدم الوضع الرتيب الذي يزيد المكوّن العشوائي للمعرّفات المولَّدة داخل المللي ثانية ذاتها.
هل ULID مماثل لـ UUID؟
لا — ULIDs وUUIDs ترميزات مختلفة لقيمة 128 بت. لا يمكن استبدال ULID مباشرةً بـ UUID في الأنظمة التي تتحقق من تنسيق UUID (سداسي عشري بواصلات). غير أنه يمكن التحويل بين التمثيلات الثنائية لـ ULID وUUID عند الحاجة.
كيف يعمل الوضع الرتيب لـ ULID؟
في الوضع القياسي، يُولَّد المكوّن العشوائي 80 بت من جديد لكل ULID. في الوضع الرتيب، حين تُولَّد ULIDs متعددة داخل المللي ثانية ذاتها، يكون المكوّن العشوائي للمعرّف الثاني وما يليه هو القيمة العشوائية السابقة زائد واحداً. هذا يضمن ترتيباً رتيباً صارماً داخل المللي ثانية لمولِّد واحد.
هل يمكنني فكّ ترميز ULID للحصول على طابع التوليد الزمني؟
نعم. أول 10 أحرف من Crockford Base32 ترمّز الطابع الزمني Unix بالمللي ثانية 48 بت. فكّ ترميز تلك الأحرف باستخدام أبجدية Crockford Base32 وفسّر الناتج كطابع زمني Unix بالمللي ثانية وحوّله إلى تاريخ. هذه الأداة تفعل ذلك تماماً.
هل ULID معيار رسمي؟
لا. ULID مواصفة مجتمعية محافَظ عليها على ulid.github.io. إنه ليس معياراً IETF كـ UUID. هذا يعني أنه لا يوجد RFC رسمي للاستشهاد به، وقد تتباين التطبيقات قليلاً. UUID v7 (RFC 9562، 2024) هو البديل الموحَّد من IETF بخصائص ترتيب زمني مماثلة.