تُعد UUID وULID آليات لإصدار مُعرّفات فريدة في الأنظمة الموزعة وقواعد البيانات. تبدو متشابهة، لكن بينها فروق في توحيد المعايير، وكفاءة التخزين، وقابلية القراءة البشرية، والمخاطر المعلوماتية. سنقارن هنا بين المدارس الثلاث الأبرز — UUID v4 وUUID v7 وULID — ونختتم بملاحظات حول UUID v1 وUUID v6.

للتجربة داخل المتصفح — مع معالجة محلية بالكامل — تتوفر الأدوات التالية (واجهاتها الحالية باليابانية/الإنجليزية):


UUID v4: العشوائية الخالصة

يُخصص UUID v4 122 بتًا من أصل 128 بتًا للصدفة، ما يجعل احتمال التصادم ضئيلًا جدًا. لكن القيم لا تُرتَّب زمنيًا، فتواجه فهارس B-Tree عمليات تقسيم متكررة وتتدهور محلية الإدراج. يصلح للرموز المؤقتة ومعرّفات الجلسات وغيرها من الاستخدامات التي يكفي فيها العشوائي التام.


UUID v7: التطور القابل للفرز الزمني

حصل UUID v7 على معيارية رسمية في RFC 9562 (2024). يخزن الطابع الزمني بالمللي ثانية في أول 48 بتًا، بينما تُملأ البتات الباقية بعشوائية، فيصبح الترتيب القاموسي مطابقًا للترتيب الزمني. ميزة إضافية: يمكن تخزينه كما هو في الحقول uuid أو BINARY(16) المعتادة.

  • ميزانية العشوائية: بعد استثناء بتات النسخة والمتغير، يبقى نحو 74 بتًا للصدفة. بعض التنفيذات تضبط هذه البتات عند توليد قيم متتالية في نفس المللي ثانية («التوليد الرتيب») لتفادي التصادم، لكن التوزيع العشوائي يبقى قويًا.
  • تسريب المعلومات: يمكن استخلاص وقت التوليد بدقة مللي ثانية. إن كان المولد ضعيفًا أو معدل التوليد مرتفعًا جدًا، قد تصبح توقعات المعرّفات المجاورة أسهل نسبيًا. في الأنظمة الداخلية غالبًا ما يُقبل ذلك، لكن الخدمات العامة التي ترفض الكشف عن ترتيب الموارد يجب أن تحذر.

ULID: الصديق للإنسان

طُرح ULID عام 2016 كـمعيار فعلي يعتمد Crockford Base32 بطول 26 محرفًا ويستبعد الرموز المتشابهة (O/I/L/1)، ما يسهل لصقه ونسخه. مثل v7، يضع 48 بتًا من الزمن في المقدمة، لذا التسلسل القاموسي يطابق الترتيب الزمني.

  • ضمان الفرز: مقارنة سلاسل ULID (Base32) تعطي دائمًا ترتيبًا زمنيًا، ويُستخدم «التوليد الرتيب» على نطاق واسع لتجنب التصادم.
  • شكل التخزين: يمكن حفظه في TEXT(26) مع قابلية قراءة عالية، لكن التحويل إلى/من 16 بايت مطلوب للتخزين الثنائي. كثير من حقول UUID الأصلية لا تقبله مباشرة.
  • تسريب المعلومات: مثل v7، يكشف المللي ثانية. لذلك يجب تقييم استخدامه في واجهات عامة حيث لا نريد الكشف عن ترتيب التوليد.

جدول مقارنة (من منظور عملي)

البند UUID v4 UUID v7 ULID
التوحيد القياسي RFC 4122 RFC 9562 (2024) غير موحد رسميًا (معتمد فعليًا)
تركيب البتات 128 بت مع 122 بت عشوائية 48 بت = UNIX ms + عشوائية (دعم رتيب اختياري) 48 بت = UNIX ms + 80 بت عشوائية
إنتروبيا العشوائية ≈122 بت ≈74 بت (بعد النسخة والمتغير) 80 بت
الصيغة النصية 36 محرفًا (ست عشري + شرطات) 36 محرفًا (ست عشري + شرطات) 26 محرفًا (Base32 كروكفورد)
الفرز النصي الطبيعي ✗ (عشوائي) ✓ (القاموسي = الزمني) ✓ (القاموسي = الزمني)
الفرز الثنائي الطبيعي ✓ (BINARY(16) بترتيب تصاعدي) ✓ (عند تخزين 6 بايت الزمن بترتيب Big-Endian)
ملاءمة التخزين في DB uuid أو BINARY(16) مثاليان uuid أو BINARY(16) مثاليان TEXT(26) قابل للقراءة / BINARY(16) يحتاج تحويلًا ولا يتوافق دائمًا مع نوع UUID
محلية الفهرسة ضعيفة (إدراج عشوائي) جيدة (إدراج متتالٍ مع الحذر من النقاط الساخنة) جيدة (مع التحذير نفسه)
كلفة مقارنة التساوي منخفضة (مقارنة 16 بايت) منخفضة (مقارنة 16 بايت) أعلى قليلًا في النص (تأثير collation) / منخفضة عند التخزين الثنائي
عبء الترميز ست عشري ↔ 16 بايت (خفيف) ست عشري ↔ 16 بايت (خفيف) Base32 ↔ 16 بايت (أثقل)
قابلية القراءة/ملاءمة URL منخفضة منخفضة مرتفعة
تسريب المعلومات (وقت التوليد) لا نعم (مللي ثانية) نعم (مللي ثانية)
أبرز الاستخدامات معرّفات جلسات، رموز مؤقتة مفاتيح قواعد البيانات، معرفات الأحداث، ترتيب السجلات معرّفات عامة وروابط تتطلب قراءة بشرية

ملاحظة حول النقاط الساخنة في v7/ULID: إذا تركزت الإدخالات على شارد واحد أو قائد واحد، قد تسخن أطراف B-Tree بسبب الإضافة الدائمة. يمكن التخفيف باستخدام خلط بسيط للبتات العليا (prefix shuffle) أو الجمع مع مفتاح تقسيم إضافي.


ملاحظات التنفيذ (PostgreSQL / MySQL / SQLite)

  • PostgreSQL
    • v4/v7: نوع uuid هو الأفضل. يمكن إدخال v7 نصًا ثم تحويله إلى uuid للاستفادة من الترتيب.
    • ULID: استخدم char(26) أو text، أو bytea(16) بعد تحويل Base32.
  • MySQL / InnoDB
    • BINARY(16) هو الأمثل لـ v4/v7. يحافظ v7 على الترتيب الزمني عند المقارنة Big-Endian. ULID يناسب CHAR(26) أو BINARY(16) مع التحويل.
  • SQLite
    • لا يوجد نوع UUID مدمج؛ استعمل BLOB(16) أو TEXT. الفهارس على BLOB فعّالة بما يكفي.

اعتبارات أمنية

  • مقاومة التوقع: يشارك v7 وULID البتات الزمنية نفسها. إذا صدر عدد كبير في نفس المللي ثانية مع مولد ضعيف، قد تصبح توقعات المعرّفات المتجاورة أسهل. استخدم مولدات عشوائية آمنة وتأكد من أن التوليد الرتيب لا يخلق تحيزًا.
  • تسرّب البيانات الوصفية: يمكن للمعرّفات الكشف عن وقت التوليد وحجم الإصدارات تقريبًا. في الواجهات العامة من الأفضل فصل المعرّفات الداخلية عن المعرّفات الخارجية لتجنب كشف النمط.

خلاصة الاختيار

  • UUID v4: عشوائية صرفة بلا ترتيب زمني. احتمال التصادم ضئيل لكنه غير صفري. يناسب الجلسات والرموز المؤقتة.
  • UUID v7: UUID قابل للفرز مع معيارية حديثة. ممتاز للمفاتيح الداخلية وسلاسل الأحداث. التوليد الرتيب يجعل التصادم شبه معدوم.
  • ULID: أكثر وُدًا للبشر. رائع للروابط والمعرّفات العامة عندما تكون القراءة مهمة، لكنه أقل ملاءمة من v7 كمفتاح داخلي. يدعم التوليد الرتيب لتجنب التصادم.

ملحق: UUID v1 وUUID v6 (باختصار)

  • UUID v1 (زمني + MAC)

    • يستخدم طابعًا زمنيًا بطول 60 بت (دقة 100 نانو ثانية) مع معرف الجهاز (غالبًا عنوان MAC). يتميز بترتيب زمني قوي، لكنه يعرض معلومات عن الجهاز ويحتاج معالجة خاصة عند ارتداد الساعة. لأسباب الخصوصية أصبح غير مفضل.
  • UUID v6 (إعادة ترتيب v1)

    • يعيد ترتيب بتات الزمن إلى شكل Big-Endian لمنح قابلية فرز أفضل. كان جذابًا باسم «v1 قابل للفرز»، لكن التوحيد القياسي استقر على v7. في التصاميم الجديدة يُنصح باختيار v7 بدل v6.

قائمة تحقق عند التنفيذ

  • استخدم مولدًا عشوائيًا آمنًا تشفيريًا.
  • عند الاعتماد على التوليد الرتيب في v7/ULID، احذر من أي انحراف في توزيع العشوائية مع منع التصادم.
  • فضّل أعمدة uuid أو BINARY(16) لتجنب تكاليف المقارنة النصية.
  • قرر سياسة المعرّفات الخارجية (هل نقبل الكشف عن ترتيب التوليد؟). إن لم نقبل، فافصل بين المعرّفات الداخلية والخارجية.

الخلاصة: في الوقت الراهن يُعد UUID v7 الخيار الافتراضي للمفاتيح الداخلية، بينما يبقى ULID مرشحًا قويًا عندما تكون القراءة البشرية أو القيود التقنية أمرًا حاسمًا. نلجأ إلى v4 أو غيره فقط عندما يمنعنا شرط صارم من كشف الزمن.