هل UUID لا يتصادم؟ أنماط تشغيل وتنفيذ خاطئة تجلب التكرار

· آخر تحديث: · · UUID, المعرّفات, الأنظمة الموزّعة, تصميم البيانات, التنفيذ

سجل التعديلات (3 تحديثات، آخر تحديث 3 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240890)
أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621452)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). هل UUID لا يتصادم؟ أنماط تشغيل وتنفيذ خاطئة تجلب التكرار. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621452 https://comcomponent.com/ar/blog/2026/03/24/001-uuid-collision-bad-implementation-patterns/

DOI (أحدث نسخة)
10.5281/zenodo.21621452
DOI (هذه النسخة)
10.5281/zenodo.22279887

كنت تستخدم UUID مفتاحاً رئيساً، ثم ظهر يوماً duplicate key. في هذه اللحظة، باحتمال غير قليل، يصير الحديث «إذن UUID يتصادم في النهاية».

غير أنّ أكثر تكرار UUID في العمل ليس مشكلة معيار UUID نفسه بقدر ما هو حالات يكسر فيها التنفيذ أو التشغيل شروط التوليد التي يفترضها المعيار. في RFC 9562 يملك UUIDv4 منطقة عشوائيّة من 122 بتّاً، وUUIDv7 أيضاً معرّف على أساس أنّ 74 بتّاً خارج الطابع الزمني تُستخدم عشوائيّة أو عدّاداً للتفرّد. في المقابل، UUIDv8 منصوص صراحة على أنّه «معتمد على التنفيذ، ولا يجوز افتراض التفرّد».123 وتشرح مكتبة Python القياسيّة أيضاً أنّ uuid4() يُولَّد بطريقة آمنة تشفيريّاً، فما دمت «تستخدم تنفيذاً سليماً استخداماً عادياً» على الأقلّ، ففرضيّة جانب UUID قويّة جدّاً.4

كيف تُرى حوادث تكرار UUIDمخطّط يبيّن أنّ أكثر تكرار UUID في العمل ليس مشكلة معيار UUID نفسه، بل حالات يكسر فيها التنفيذ أو التشغيل شروط التوليد التي يفترضها المعيار، وأنّ الفرضيّة في جانب UUID قويّة جدّاً ما دام التنفيذ السليم يُستخدم استخداماً عادياً.معيار UUID نفسهالفرضيّة قويّة جدّاً (122 بتّاً عشوائيّة وغيرها)التنفيذ والتشغيليكسران شروط التوليد التي يفترضها المعيارأكثر حوادث التكرار في العمل من هنا

الشكل 1: ما يُشتبه فيه ليس رياضيّات UUID، بل التنفيذ والتشغيل اللذان يكسران الفرضيّة.

ترتّب هذه المقالة الأنماط النموذجيّة التي يتصادم فيها UUID بسبب تشغيل أو تنفيذ خاطئ، مع تدابير منع التكرار. المحتوى يستند إلى RFC 9562 ووثائق Python الرسميّة ووثائق PostgreSQL الرسميّة كما يمكن تأكيدها في مارس 2026.546

مصطلحات هذه المقالة

قبل الفصول التالية نترجم في سطر واحد المصطلحات التي ستظهر بالإنجليزيّة.

المصطلح المعنى في سطر
PRNG pseudo-random number generator، مولّد أعداد شبه عشوائيّة. يصنع سلسلة ثابتة من seed، فالـ seed نفسه يعيد السلسلة نفسها
CSPRNG cryptographically secure PRNG، مولّد شبه عشوائيّ آمن تشفيريّاً. هدفه التصميمي يشمل صعوبة توقّع القيمة التالية
generator state حالة المولّد. الحالة الداخليّة للعشوائيّة وclock sequence والعدّاد وغيرها من موادّ تحديد UUID التالي
carefully seeded counter عدّاد مهيَّأ بعناية. في UUIDv7 يشير إلى المنطقة المستخدمة لصنع أرقام متتابعة داخل الملّي ثانية نفسها
clock rollback تراجع الساعة. عودة وقت النظام إلى قيمة سابقة بسبب تصحيح NTP أو تغيير يدويّ
counter rollover تجاوز العدّاد. عودة العدّاد إلى 0 بعد تجاوز القيمة القصوى
monotonicity الرتابة. خاصّيّة أنّ UUID المصنوع لاحقاً يكون دائماً أكبر قيمة
namespace فضاء الأسماء. في UUIDv3 / v5، UUID الذي يحدّد سياق تفسير الاسم
canonicalization التوحيد القياسي. معالجة تسوية السلاسل التي تشير إلى الهدف نفسه في شكل واحد، بما في ذلك حالة الأحرف والشرطة المائلة في النهاية

1. الخلاصة أوّلاً

باختصار مقدّماً، الخطر في هذه الأنماط.

النمط ماذا يحدث التدبير الأوّل
توليد قيم تشبه UUIDv4 بـ seed ثابت أو PRNG ضعيف تُعاد السلسلة نفسها في عمليّة أخرى أو عقدة أخرى استخدام واجهة UUID القياسيّة في نظام التشغيل أو بيئة التشغيل
توريث حالة التوليد كما هي بعد fork أو VM snapshot أو استنساخ الحاوية ترجع حالة العشوائيّة أو العدّاد فيظهر التكرار إعادة seed بعد fork، وإعادة التهيئة بعد clone، ومراجعة التعامل مع الحالة الدائمة
استخدام UUIDv3 / v5 بظنّ أنّه «معرّف جديد في كلّ مرّة» يُعاد توليد UUID نفسه من namespace نفسه والاسم نفسه فهمه بوصفه معرّفاً حتميّاً وحصر الاستخدام
تنفيذ UUIDv1 / v6 / v7 / v8 ذاتيّاً ومعالجة متساهلة لـ clock rollback وnode/counter يسهل التكرار عند التوليد الكثيف أو العقد المتعدّدة استخدام مكتبة قائمة وتقليل المولّدات الخاصّة
اقتطاع UUID في الأثناء أو سحقه في شكل آخر تتخلّى بنفسك عن تفرّد 128 بتّاً الأصليّ الحفظ والمقارنة بالطول الكامل
عدم وضع UNIQUE / PRIMARY KEY في قاعدة البيانات يدخل التكرار بهدوء ويتأخّر التحقيق وجود قيد تفرّد في طبقة التخزين

باختصار، الأمر أقرب إلى أنّ التصميم في الأثناء يحذف التفرّد الذي كنت تتوقّعه من UUID منه إلى أنّ UUID تصادم.

أين يُحذف التفرّدمخطّط يبيّن أنّ الأمر أقرب إلى حذف التفرّد المتوقَّع من UUID في تصميم الأثناء عبر التوليد أو النسخ والرجوع أو إساءة الاستخدام أو الاقتطاع أو غياب القيد، منه إلى أنّ UUID تصادم.التفرّد المتوقَّع من UUIDيُحذف بطريقة التوليد (ذاتيّ / عشوائيّة ضعيفة)يُحذف بالتشغيل (snapshot وfork)يُحذف بالحفظ (اقتطاع وغياب القيد)النتيجة ظهور التكرار

الشكل 2: التصادم في الغالب لا «يحدث»، بل «يُصنَع» في تصميم الأثناء.

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 25، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. ما يُشتبه فيه أوّلاً ليس «رياضيّات UUID» بل «التوليد والتشغيل»

يتعقّد حديث UUID لأنّ الخصائص تختلف حسب الإصدار.

  • UUIDv4 قائم على العشوائيّة. في RFC 9562 تُملأ 122 بتّاً خارج version / variant بعشوائيّة (Section 5.4).1
  • UUIDv7 بنية سهلة الفرز زمنيّاً، تُركَّب من طابع Unix بالملّي ثانية مع عشوائيّة أو carefully seeded counter للباقي (Section 5.7).2
  • UUIDv3 / v5 قائمان على الاسم. إن كان namespace نفسه وcanonical name نفسه، فظهور UUID نفسه هو السلوك الصحيح (Section 6.5).7
  • UUIDv8 للتجريب أو للاستخدام الخاصّ بالمورّد، والتفرّد معتمد على التنفيذ. تقول RFC «لا يجوز افتراض التفرّد» (Section 5.8).3
الخصائص تختلف حسب الإصدارمخطّط يبيّن أنّ UUIDv4 قائم على العشوائيّة، وUUIDv7 بنية زمنيّة تجمع الطابع الزمني مع عشوائيّة أو عدّاد، وUUIDv3 وv5 قائمان على الاسم بحيث يخرج القيمة نفسها من الدخل نفسه، وUUIDv8 تفرّده معتمد على التنفيذ، فالخصائص تختلف تماماً حسب الإصدار.إصدار UUIDv4: 122 بتّاً عشوائيّةv7: وقت + عشوائيّة وعدّادv3 / v5: الدخل نفسه يعطي القيمة نفسها (قائم على الاسم)v8: التفرّد معتمد على التنفيذ

الشكل 3: «نستخدم UUID» وحده لا يحسم الأمر؛ الخصائص تختلف تماماً حسب الإصدار.

أي إنّ قول «نستخدم UUID» لا يكفي، فالمضمون:

  • أهو uuid4() من المكتبة القياسيّة
  • أم timestamp + random ذاتيّ
  • أم uuid5(namespace, name)
  • أم تنسيق خاصّ يبدو كـ UUIDv8 فقط

يغيّر الحديث تماماً.

رصد تكرار UUIDأين صارت القيمة نفسها حقاًالمولّد ضعيفالحالة رجعتإساءة استخدام UUID القائم على الاسماقتطاع عند الحفظلا قيد تفرّد في قاعدة البياناتخطأ تنفيذ

الشكل 4: سبب التكرار ينحصر في خمس سلاسل: المولّد، والحالة، وإساءة الاستخدام، والاقتطاع، وغياب القيد.

في العمل، النظر من يمين هذا المخطّط أسرع.

وإذا كُتب كإجراء، فالعمليّ هو الإغلاق بالترتيب من المواضع الأقلّ كلفة في التحقّق. بالترتيب التالي تتزايد الأدوات اللازمة تدريجيّاً: «تشغيل SQL واحد»، ثم «grep على الشيفرة»، ثم «تأكيد إجراء التشغيل»، فيمكن التوقّف حين يظهر السبب.

لانعميُقتطَعيُحفَظ كاملاًتنفيذ ذاتيواجهة قياسيّةوُرثتلا مشكلةرصد duplicate key أو بيانات مكرّرة1. هل ثمّة UNIQUE / PRIMARY KEY في قاعدة البيانات (الفصل 8)دخل بهدوء بلا قيد. ضع القيد أوّلاً وأوقف مدخل التكرار2. هل يُحفَظ ويُقارَن بكامل 128 بتّاً (الفصل 7)اشتبه في مقارنة البادئة والسحق إلى 64 بتّاً وقطع النهاية لنقص طول العمود3. هل التوليد عبر واجهة قياسيّة (الفصلان 3 و5)اشتبه في التوليد الذاتي بـ PRNG ضعيف أو إساءة استخدام UUID القائم على الاسم لإصدار المعرّفات4. هل وُرثت حالة المولّد بعد fork / snapshot / clone (الفصل 4)حالة المولّد راجعةالمتبقي تنفيذ زمنيّ خاصّ أو تصميم UUIDv8 نفسه (الفصل 6)

الشكل 5: مسار تشخيص يغلق بالترتيب من الأقلّ كلفة: SQL واحد ثم grep ثم تأكيد إجراء التشغيل.

3. النمط 1: تسمية UUIDv4 مع استخدام PRNG ضعيف فعلاً

هذا الأكثر شيوعاً.

  • صنع 128 بتّاً بـ PRNG عامّ يعادل Math.random()
  • وضع seed عند الإقلاع بـ time() أو PID
  • تجميع «32 خانة hex تشبه شكل UUID» ذاتيّاً

حتّى إن بدا الناتج UUID، إن كان مصدر العشوائيّة ضعيفاً أُعيدت السلسلة نفسها في عمليّة أخرى أو عقدة أخرى.

يتّضح في الشيفرة. التالي مثال لا ينبغي فعله، ويعمل بمكتبة Python 3 القياسيّة وحدها.

# 悪い例: 一般用途の PRNG を seed 固定で回し、UUID 形式の文字列を自前で組み立てる
import random


def make_pseudo_uuid(seed: int) -> str:
    rng = random.Random(seed)          # 同じ seed なら毎回まったく同じ系列になる
    value = rng.getrandbits(128)
    hex_digits = f"{value:032x}"
    return "-".join([
        hex_digits[0:8],
        hex_digits[8:12],
        hex_digits[12:16],
        hex_digits[16:20],
        hex_digits[20:32],
    ])


first = make_pseudo_uuid(12345)
second = make_pseudo_uuid(12345)
print(first == second)   # True: 別プロセスでも別ノードでも、seed が同じなら同じ値が出る

في هذا المثال مشكلتان في الواقع.

  1. random.Random مولّد PRNG عامّ، فإن كان seed نفسه أُعيدت السلسلة كما هي. وحتّى إن استخدمت وقت الإقلاع أو PID بوصفه seed، فقد يتصادم عند الإقلاع المتزامن أو clone.
  2. لم تُضبَط بتّات version / variant أصلاً، فليس هذا UUIDv4 وفق RFC 9562. الدقيق أنّه عدد من 128 بتّاً بشكل UUID لا أكثر.
مشكلتا UUID الشبيه المصنوع ذاتيّاًمخطّط يبيّن أنّ تجميع سلسلة بشكل UUID عبر تشغيل PRNG عامّ بـ seed ثابت يعيد السلسلة نفسها إن كان seed نفسه فتخرج القيمة نفسها في عمليّة أخرى أو عقدة أخرى، وأنّ بتّات version وvariant لا تُضبَط فلا يكون UUIDv4 وفق RFC 9562 بل عدداً من 128 بتّاً بشكل UUID.توليد ذاتي بـ PRNG عامّ + seed ثابتالـ seed نفسه يعيد السلسلة نفسهابتّات version / variant غير مضبوطةالقيمة نفسها تخرج في عمليّة أخرى أو عقدة أخرىعدد من 128 بتّاً بشكل UUID لا أكثر

الشكل 6: مشكلتا المثال السيّئ قابليّة الإعادة وضبط البتّات، وهو أصلاً ليس UUIDv4.

في المقابل يصير المثال الجيّد قصيراً إلى حدّ المخادعة.

# 良い例: 標準 API をそのまま使う。seed も版数も自分で触らない
import uuid

new_id = uuid.uuid4()
print(new_id.version)    # 4: version ビットは API 側が正しく埋めてくれる
print(uuid.uuid4() != uuid.uuid4())   # True: 呼ぶたびに別の値になる

تقول RFC 9562 إنّه ينبغي استخدام CSPRNG من أجل تفرّد UUID وصعوبة توقّعه معاً (Section 6.9 Unguessability). هذا متطلّب موصى به (SHOULD)، وقد يوجد تصميم استثنائي حسب الاستخدام، لكن إن صنعت UUID بـ PRNG عامّ فينبغي أن تكون قادراً على شرح السبب. وتكتب أيضاً أنّه ينبغي إعادة seed حالة CSPRNG بشكل مناسب عند تغيّر حالة مثل process fork.8 ويشرح uuid.uuid4() في Python أيضاً أنّه يولّد UUID عشوائيّاً بطريقة آمنة تشفيريّاً.4

الخلاصة العمليّة هنا بسيطة.

  • لا تصنع UUID بنفسك
  • لا تعبث بـ seed العشوائيّة يدوياً
  • استخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدام كما هو

الإبقاء على مولّد خاصّ لأنّه «خفيف» أو «مستخدم منذ القديم» يكلّف أغلى ثمن لاحقاً.

الخلاصة العمليّة حول التوليدمخطّط يبيّن أنّ RFC 9562 تقول إنّه ينبغي استخدام CSPRNG للتفرّد وصعوبة التوقّع وتتطرّق إلى إعادة seed عند fork، وأنّ الخلاصة العمليّة ثلاث نقاط: لا تصنع UUID بنفسك، ولا تعبث بـ seed العشوائيّة يدوياً، واستخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدام كما هو.لا تصنع UUID بنفسكلا تعبث بـ seed العشوائيّة يدوياًاستخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدامCSPRNG وإعادة seed بعد fork توصية RFC

الشكل 7: خلاصة التوليد بسيطة: لا تصنع بنفسك ومِل نحو الواجهة القياسيّة.

4. النمط 2: إرجاع حالة التوليد بـ fork وsnapshot وclone

ثاني الأخطر هو تشغيل تُنسَخ فيه حالة المولّد أو تُرجَع.

توصي RFC 9562 صراحة بإعادة seed بعد fork (Section 6.9)، وتشرح أنّ التنفيذ بلا stable storage يزيد تواتر توليد clock sequence والعدّاد والبيانات العشوائيّة فترتفع احتماليّة التكرار (Section 6.3 UUID Generator States).89

من هنا يخرج استدلال عمليّ طبيعيّ.

  • استعادة الصورة نفسها عدّة مرّات بعد أخذ VM snapshot
  • قيام مولّد خاصّ من الحالة الابتدائيّة نفسها عند إقلاع صورة الحاوية
  • مشاركة حالة PRNG أو العدّاد بعد fork للعامل

في تشغيل كهذا قد تُعاد سلسلة توليد UUID دون قصد. هذا ليس نصّ RFC الحرفيّ «snapshot خطر»، لكنّه تنبيه عمليّ جدّاً يُستنتَج من إعادة seed بعد fork والتعامل مع generator state.89

كيف يصنع نسخ الحالة ورجوعها التكرارمخطّط يبيّن أنّ استعادة VM snapshot أو استنساخ صورة الحاوية أو fork العامل إن ورث حالة المولّد كما هي أرجع حالة العشوائيّة أو العدّاد فأعاد سلسلة التوليد دون قصد وظهر التكرار، وأنّ التدبير إعادة التهيئة فوراً بعد fork وclone وrestore والميل إلى عشوائيّة من نظام التشغيل.استعادة snapshot واستنساخ الحاوية وfork العاملتوريث حالة المولّد كما هيرجوع حالة العشوائيّة أو العدّادإعادة سلسلة التوليد وظهور التكرارالتدبير: إعادة تهيئة فوريّة والميل إلى عشوائيّة من نظام التشغيل

الشكل 8: تشغيل النسخ والرجوع ينسخ سلسلة التوليد معها.

التدابير في هذا النطاق.

  • لا تُبقِ حالة توليد UUID خاصّة طويلاً
  • أعد التهيئة فور fork / clone / restore
  • إن أمكن فمل نحو تنفيذ يستخدم عشوائيّة من نظام التشغيل في كلّ مرّة
  • إن كان مولّداً عالي التواتر فثبّت مواصفة إدارة الحالة وإعادة seed كتابة

5. النمط 3: فهم UUIDv3 / v5 على أنّه «معرّف جديد في كلّ مرّة»

UUIDv3 / v5 ليسا معرّفاً عشوائيّاً صعب التصادم. إنّهما معرّف حتميّ يعيد توليد المعرّف نفسه من الاسم نفسه.

تنصّ RFC 9562 على أنّ UUID المولَّد من same name بصيغة canonical نفسها في same namespace يجب أن يتساوى (Section 6.5 Name-Based UUID Generation).7 لذلك فالاستخدام التالي يجعل التكرار سلوكاً مطابقاً للمواصفة لا حادثاً.

الفهم الصحيح لـ UUID القائم على الاسممخطّط يبيّن أنّ UUIDv3 وv5 ليسا معرّفاً عشوائيّاً صعب التصادم بل معرّفاً حتميّاً يعيد توليد UUID نفسه من namespace نفسه وcanonical name نفسه، وأنّ التكرار عند استخدامه لإصدار معرّفات جديدة سلوك مطابق للمواصفة لا حادث.namespace نفسه + canonical name نفسهUUID نفسه حتماً (MUST في المواصفة)إن استُخدم لإصدار جديد فالتكرار مطابق للمواصفةمعرّف حتميّ لا عشوائيّ

الشكل 9: تكرار v3 / v5 ليس حادثاً، بل المواصفة نفسها بوصفه معرّفاً حتميّاً.

  • استخدام uuid5(NAMESPACE_URL, "https://example.com/users/42") في كلّ مرّة بوصفه «إصداراً جديداً»
  • الإصدار بـ namespace مشترك لكلّ العملاء + البريد دون إدخال المستأجر في namespace
  • الظنّ أنّ إعادة الإصدار عند كلّ retry للاسم المنطقيّ نفسه ستعطي معرّفاً آخر

يمكن تأكيد هذا أيضاً بشيفرة قصيرة تعمل بمكتبة Python 3 القياسيّة وحدها.

# 誤用例: uuid5 を「新規採番」だと思って使う
import uuid

url = "https://example.com/users/42"

first = uuid.uuid5(uuid.NAMESPACE_URL, url)
second = uuid.uuid5(uuid.NAMESPACE_URL, url)
print(first == second)   # True: 何回呼んでも同じ。これは仕様どおりの動き

# しかも、name の正規化がぶれると今度は逆に別 ID になる
with_slash = uuid.uuid5(uuid.NAMESPACE_URL, url + "/")
print(first == with_slash)   # False: 末尾スラッシュ 1 個で別 UUID

# 新規採番がしたいなら、そもそも版が違う
print(uuid.uuid4() != uuid.uuid4())   # True

إن استخدمت uuid5 فالأأمن أن تقرّر كيف توحّد name قبل تمريره أوّلاً، وتجمع دالة التوحيد في موضع واحد.

# 良い例: canonicalization を関数に切り出し、必ずそれを通してから uuid5 に渡す
import uuid

# tenant ごとに namespace を分ける。この値は仕様として固定し、あとから変えない
TENANT_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_DNS, "tenant-a.example.com")


def canonical_user_url(user_id: int) -> str:
    # スキーム、ホスト、末尾スラッシュの有無まで含めて 1 つの形に決める
    return f"https://example.com/users/{user_id}"


def user_uuid(user_id: int) -> uuid.UUID:
    return uuid.uuid5(TENANT_NAMESPACE, canonical_user_url(user_id))


print(user_uuid(42) == user_uuid(42))   # True: 同じ対象なら必ず同じ ID

وبالعكس، إن تذبذب canonicalization للاسم صار للهدف نفسه UUID مختلف. وتؤكّد RFC أيضاً مراراً التعامل مع التمثيل القياسي (Section 5.5، Section 6.5).710

المهمّ في هذا السلسلة ثلاث نقاط:

  • UUIDv3 / v5 ليسا «إصداراً بلا تكرار» بل «الدخل نفسه يعطي المعرّف نفسه»
  • لا تترك تصميم namespace غامضاً
  • ثبّت canonicalization للاسم كمواصفة
ماذا يجلب تذبذب canonicalizationمخطّط يبيّن أنّ تذبذب توحيد الاسم يجعل للهدف نفسه UUID مختلفاً، وأنّ الدخل نفسه يعطي المعرّف نفسه حتماً، لذلك فالأأمن تثبيت تصميم namespace كمواصفة وجمع دالة التوحيد في موضع واحد وتمريرها حتماً قبل uuid5.جمع دالة التوحيد في موضع واحد وتمريرها حتماًتذبذب مثل الشرطة المائلة في النهايةتوحيد الاسمهل هو غير متذبذبالهدف نفسه يعطي المعرّف نفسه حتماًالهدف نفسه UUID مختلفتثبيت تصميم namespace أيضاً كمواصفة

الشكل 10: إن لم تُثبَّت مواصفة namespace والتوحيد، وقع الحادث العكسي أيضاً (معرّف مختلف للهدف نفسه).

6. النمط 4: تنفيذ UUID زمنيّ أو UUIDv8 ذاتيّاً

UUIDv1 / v6 / v7 / v8 خطرة إن قُلِّد الشكل وحده.

6.1 معالجة متساهلة لـ node أو clock sequence في UUIDv1 / v6

في RFC 9562، UUIDv6 إعادة ترتيب لـ UUIDv1 لتحسين locality قاعدة البيانات، ويتعامل مع clock sequence وnode (Section 5.6). ثمّ ثمّة تنبيهات عدّة حول مقاومة تصادم العقدة في البيئة الموزّعة (Section 6.4) والاحتفاظ بالحالة (Section 6.3).11912

بل تكتب RFC أنّ ظهور الآلات الافتراضيّة والحاويات جعل تفرّد عنوان MAC غير مضمون بعد.5

لذلك فتصميم مثل:

  • الجزم بأنّ عنوان MAC يعني التفرّد
  • استنساخ node ID بحرقه في الصورة
  • إعادة clock sequence إلى قيمة ثابتة عند كلّ إعادة تشغيل

خطر.

جزميات خطرة في UUIDv1 / v6مخطّط يبيّن أنّ RFC تكتب أنّ ظهور الآلات الافتراضيّة والحاويات جعل تفرّد عنوان MAC غير مضمون بعد، لذلك فالجزم بأنّ عنوان MAC يعني التفرّد، واستنساخ node ID بحرقه في الصورة، وإعادة clock sequence إلى قيمة ثابتة عند كلّ إعادة تشغيل، تصاميم خطرة.الجزم بأنّ MAC يعني التفرّدتصميم خطراستنساخ node ID بحرقه في الصورةإعادة clock sequence إلى قيمة ثابتةفي بيئة الافتراض لا يُضمَن تفرّد MAC

الشكل 11: تفرّد MAC الذي كان فرضيّة v1 / v6 لا يقوم في عصر الافتراض.

6.2 صنع UUIDv7 ذاتيّاً وترك counter rollover وclock rollback

UUIDv7 عمليّ جدّاً، لكن RFC تكتب بعناية الرتابة ومعالجة العدّاد عند التوليد الكثيف (Section 6.2 Monotonicity and Counters). ومنصوص أيضاً أنّه لا يجوز إعادة تكرار معروف بسبب clock rollback أو counter rollover.213

إذن فتنفيذ مثل:

  • إصدار كثيف داخل الملّي ثانية نفسها بلا تصميم عدّاد
  • مواصلة التوليد دون فعل شيء حين يرجع الوقت
  • تهيئة العمليّات المتعدّدة العدّاد الداخليّ نفسه كلٌّ على حدة

خطر.

نقاط خطر إن تُركت في صنع UUIDv7 ذاتيّاًمخطّط يبيّن أنّ الإصدار الكثيف بلا تصميم عدّاد داخل الملّي ثانية نفسها، ومواصلة التوليد دون فعل شيء حين يرجع الوقت، وتهيئة العمليّات المتعدّدة العدّاد الداخليّ نفسه كلٌّ على حدة، تنفيذ خطر، وأنّ RFC تقول إنّه لا يجوز إعادة قيمة مع العلم أنّها مكرّرة بسبب clock rollback أو counter rollover.إصدار كثيف بلا تصميم عدّادv7 ذاتي سهل التكرارترك clock rollbackتهيئة العدّاد نفسه كلٌّ على حدةإعادة قيمة مع العلم أنّها مكرّرة محظورة

الشكل 12: في صنع v7 ذاتيّاً، معالجة العدّاد وتراجع الوقت خطّ الحياة.

من أيّ تواتر إصدار ينبغي الاهتمام

للحكم على «هل ينطبق نظامي»، أسرع طريق النظر إلى تخصيص البتّات.

UUIDv7 في RFC 9562 بنية يتبع فيها طابع الملّي ثانية من 48 بتّاً rand_a (12 بتّاً)، ثم بعد variant يأتي rand_b (62 بتّاً).2 وفي Section 6.2 تُعرض للحفاظ على الرتابة طريقة تجعل 12 بتّاً من rand_a عدّاداً مخصّصاً (Method 1)، وطريقة تستخدم جانب rand_b بوصفه «عدّاداً مهيَّأ عشوائيّاً» (Method 2)، وطريقة تستبدل حتى 12 بتّاً من rand_a بدقّة زمنيّة أدقّ من الملّي ثانية (Method 3).13

من هنا تُقرأ المؤشّرات هكذا.

لا تقرأ هنا ببساطة أنّ 12 بتّاً = حتى 4096 عنصراً. تطلب RFC 9562 تهيئة العدّاد بقيمة عشوائيّة في كلّ tick حتّى يصعب توقّعه.13 إن كانت القيمة الابتدائيّة 4000، لم يبق في ذلك tick سوى 96 عنصراً. العدد المتاح داخل tick واحد هو «4096 − القيمة الابتدائيّة»، لا 4096.

وتذكر RFC تدبيراً هو ترك البتّات العليا من العدّاد 0 وتهيئة الجانب الأدنى فقط.13 مثلاً إن ثُبِّتت البتّة العليا على 0 وعشوائيّة البتّات الـ 11 الدنيا فقط، فالقيمة الابتدائيّة 2047 كحدّ أقصى، فيُضمَن 2048 عنصراً في كلّ tick. العدد الذي يمكن ضمانه لا يُحدَّد بعرض العدّاد بل بنطاق التهيئة المسموح.

على هذا الأساس تصير المؤشّرات كالتالي.

عدد الإصدار لكلّ مولّد في الملّي ثانية كيف يُنظَر إليه
بضعة إلى عشرات بتهيئة تحجز البتّات العليا لا يُستهلك tick واحد
مئات بحسب طريقة التهيئة قد تدور الدورة هنا. هذا مستوى تحدّد فيه سقف القيمة الابتدائيّة وتكتب سلوك rollover (تقديم الطابع الزمني / الانتظار) كمواصفة
1000 فأكثر حتّى مع حجز البتّات العليا يصير الهامش ضيّقاً. افترض Method 2 الذي يستخدم جانب rand_b أيضاً، أو تصميماً يقدّم الطابع الزمني

لا يمكن القول «لا نخرج ملايين العناصر في الثانية فـ rollover غير ذي صلة». السقف يُحدَّد بتصميم التهيئة، فحتّى مع تواتر إصدار متوسّط، إن أُخذت القيمة الابتدائيّة من كامل 12 بتّاً فقد تدور الدورة. إن شُحن المنتج دون تقرير ماذا يفعل عند rollover، ظهر التكرار هناك. صمّم بهذا الترتيب: قرّر أوّلاً نطاق التهيئة المسموح، ثم سلوك rollover.

كيف يُحدَّد العدد الذي يضمنه العدّادمخطّط يبيّن أنّ RFC 9562 تطلب تهيئة العدّاد بقيمة عشوائيّة في كلّ tick لذلك فالعدد المتاح داخل tick واحد هو 4096 ناقص القيمة الابتدائيّة، وأنّ حجز البتّات العليا على 0 وعشوائيّة الجانب الأدنى يحدّد حدّاً أدنى مضموناً، لذلك يُصمَّم بترتيب تقرير نطاق التهيئة المسموح ثم سلوك rollover.تهيئة بقيمة عشوائيّة في كلّ tickالمتاح 4096 ناقص القيمة الابتدائيّةحجز البتّات العليا يضمن حدّاً أدنى1. تقرير نطاق التهيئة المسموح2. تقرير سلوك rollover

الشكل 13: العدد الذي يمكن ضمانه لا يُحدَّد بعرض العدّاد، بل بنطاق التهيئة المسموح.

غير أنّ ثمّة نقطتين لا ينبغي الاطمئنان عندهما هنا.

  • clock rollback يحدث بمعزل عن تواتر الإصدار. الوقت يرجع عادة بتصحيح NTP وعودة الآلة الافتراضيّة من التعليق وتغيير الوقت يدوياً. حتّى نظام يصنع 10 عناصر في الثانية يحتاج تدبيراً.
  • الأمر «لكلّ مولّد»، فيلزم ضرب عدد العمليّات. إن حملت 100 عمليّة عدّاداً مستقلاً وبدأت من القيمة الابتدائيّة نفسها، تداخلت السلاسل ولو قلّ إصدار العمليّة الواحدة.

6.3 استخدام UUIDv8 بخفّة كأنّه «معيار UUID جديد»

يبدو UUIDv8 مريحاً، لكن RFC 9562 واضحة جدّاً: تفرّد UUIDv8 معتمد على التنفيذ ولا يجوز افتراضه (Section 5.8).3

لذلك فـ «UUID خاصّ بالشركة» من نوع:

  • تضمين timestamp
  • تضمين shard id
  • تضمين معنى أعمال ما
  • ملء الباقي بعشوائيّة كيفما اتّفق

يعني أنّ وثيقة تصميمك هي نفسها مواصفة تفرّد UUID. إدخاله بلا مراجعة أخطر من أن يُقبل.

معنى استخدام UUIDv8مخطّط يبيّن أنّ RFC 9562 تقول إنّ تفرّد UUIDv8 معتمد على التنفيذ ولا يجوز افتراضه، لذلك في UUID خاصّ بالشركة يضمّن timestamp أو shard id أو معنى أعمال تصير وثيقة التصميم نفسها مواصفة التفرّد، وإدخاله بلا مراجعة أخطر من أن يُقبل.تركيب تنسيق خاصّ بـ UUIDv8التفرّد معتمد على التنفيذ والمعيار لا يضمنهوثيقة تصميم الشركة تصير مواصفة التفرّد نفسهاالإدخال بلا مراجعة أخطر من أن يُقبل

الشكل 14: اختيار v8 يعني تحمّل مسؤوليّة التفرّد بأنفسكم.

7. النمط 5: تقصير UUID في الأثناء

قد يكون التوليد صحيحاً حتى تلك النقطة، ثم يُكسَر في مرحلة الحفظ أو المقارنة.

أمثلة نموذجيّة.

  • استخدام أول 8 أحرف بدل مفتاح خارجيّ
  • سحق UUID من 128 بتّاً إلى عدد صحيح من 64 بتّاً
  • نقص طول عمود النصّ فيُقطع الذيل
  • معاملة التمثيل المختصر في السجلّ أو الشاشة كمفتاح تفرّد كما هو

المهمّ هنا أنّ تغيير التمثيل نفسه ليس سيّئاً.

تحويلات لا تُسقط 128 بتّاً مثل:

  • إزالة الشرطات
  • توحيد الأحرف الصغيرة / الكبيرة
  • الحفظ بـ 16 بايت ثنائيّة

لا مشكلة فيها. الخطر في تحويل يحذف مادّة التفرّد نفسها.

وخصوصاً تصميم صنع «معرّف مختصر يسهل على الإنسان قراءته» على حدة ثم صار دون انتباه مقدَّماً على UUID الأصليّ سهل الحوادث.

الفرق بين تحويل التمثيل وتحويل يحذف التفرّدمخطّط يبيّن أنّ تحويلات لا تُسقط 128 بتّاً مثل إزالة الشرطات وتوحيد حالة الأحرف والحفظ بـ 16 بايت ثنائيّة لا مشكلة فيها، وأنّ التحويلات التي تحذف مادّة التفرّد نفسها مثل استخدام أول 8 أحرف والسحق إلى 64 بتّاً وقطع الذيل لنقص طول العمود خطرة.يحافظيحذفهل يحافظ ذلك التحويل على 128 بتّاًإزالة الشرطات وتوحيد حالة الأحرف والتحويل الثنائيّأول 8 أحرف والتحويل إلى 64 بتّاً وقطع الذيلتحويل لا مشكلة فيهتحويل خطر تتخلّى فيه بنفسك عن التفرّد

الشكل 15: السيّئ ليس تغيير التمثيل، بل حذف مادّة التفرّد.

8. النمط 6: لا قيد تفرّد في قاعدة البيانات

وهذا مهمّ خصوصاً.

حتّى إن صعب تصادم UUID بما يكفي، إن كان التكرار غير مقبول حقّاً فينبغي وجود قيد تفرّد في جهة الحفظ أيضاً.

تشرح وثائق PostgreSQL الرسميّة أنّ unique constraint يضمن تفرّد قيم العمود أو مجموعة الأعمدة في الجدول كلّه، وأنّ primary key يصير معرّف صف فريد وغير فارغ.6

وتقول RFC 9562 أيضاً إنّ UUID يستطيع تقديم تفرّد كافٍ في التنفيذ، لكنّه لا يستطيع ضمان تفرّد عالميّ مطلق. وتقول أيضاً إنّ الاستخدامات ذات أثر التصادم العالي ينبغي أن تتّخذ تدابير أقوى (Section 6.7 Collision Resistance، Section 6.8 Global and Local Uniqueness).14

في العمل يصير هذا الجمع أساساً.

  • استخدام UUID بوصفه معرّفاً صعب التصادم
  • قاعدة البيانات تملك خطّ دفاع أخيراً بـ UNIQUE / PRIMARY KEY
  • تصميم retry / idempotency / incident logging عند التكرار

استخدام UUID وعدم وضع قيد تفرّد ليسا مترادفين.

قيد التفرّد في قاعدة البيانات بوصفه خطّ الدفاع الأخيرمخطّط يبيّن أنّ الجمع الأساس هو استخدام UUID بوصفه معرّفاً صعب التصادم، ومع ذلك، بما أنّ RFC نفسها تقول إنّها لا تستطيع ضمان تفرّد عالميّ مطلق، فإن كان التكرار غير مقبول حقّاً فاجعل UNIQUE أو PRIMARY KEY في قاعدة البيانات خطّ دفاع أخيراً، وصمّم أيضاً retry وidempotency وincident logging عند التكرار.استخدام UUID بوصفه معرّفاً صعب التصادمومع ذلك لا ضمان مطلق (منصوص في RFC)جعل UNIQUE / PRIMARY KEY في قاعدة البيانات خطّ الدفاع الأخيرتصميم retry وidempotency والسجلّ عند التكرار

الشكل 16: استخدام UUID وعدم وضع قيد تفرّد ليسا مترادفين.

9. قائمة تحقّق عمليّة

أخيراً نلخّص بشكل يسهل استخدامه كما هو في الإدخال أو التدقيق. جدول خلاصة الفصل 1 كان قائمة «ما هو خطر»، أمّا هذا فقائمة كيف تتأكّد من نظامك.

# ما يُتحقَّق منه طريقة التحقّق خطّ القبول
1 هل يُولَّد UUID ذاتيّاً grep في المستودع كلّه على آثار تجميع ذاتي مثل getrandbits وMath.random وnew Random( و%032x توليد UUID عبر الواجهة القياسيّة فقط مثل uuid4() / uuid7()
2 هل حُدِّد إصدار UUID كمواصفة عدّ الإصدار من البيانات المحفوظة. في PostgreSQL خانة الإصدار هي substring(id::text from 15 for 1) الإصدار المسموح موثَّق والبيانات الفعليّة تطابقه
3 هل جُرد التعامل مع seed وgenerator state قراءة وصف «إعادة التهيئة» في سكربت الإقلاع وDockerfile وإجراءات snapshot / clone. grep على مواضع fork للعامل إجراء إعادة صنع المولّد فور fork وإعادة تشغيل العامل وsnapshot وclone منصوص
4 هل يُحفَظ بالطول الكامل تأكيد تعريف العمود. إضافة grep في الشيفرة على اقتطاع مثل [:8] وsubstring( وLeft( وToString("N").Substring الحفظ والمقارنة يبقيان 128 بتّاً. التمثيل المختصر مقصور على العرض
5 هل ثمّة UNIQUE / PRIMARY KEY في قاعدة البيانات بالـ SQL أدناه، حدّد اسم العمود الذي يوضع فيه UUID، وأدرج القيود والفهارس الفريدة معاً (العدّ على مستوى الجدول يحسب المفتاح الرئيس التسلسليّ) صفّ واحد على الأقلّ فيه single_col يساوي true لذلك العمود
6 هل يمكن رصد التكرار grep على مواضع تبتلع استثناء يعادل duplicate key. انظر في السجلّ الفعليّ هل يظهر generator / node / deployment عند التكرار يُسجَّل الاستثناء ويمكن تتبع مصدره

تحقّق البند 5 يكفي بواحدة في PostgreSQL.

-- public.orders の uuid 列に一意性が張られているかを一覧する。
-- テーブル名と列名は対象に合わせて置き換える
WITH target AS (
    SELECT attrelid, attnum
    FROM   pg_attribute
    WHERE  attrelid = 'public.orders'::regclass
      AND  attname  = 'uuid'                    -- ← 調べたい列
      AND  NOT attisdropped
)
SELECT c.conname                     AS name,
       c.contype::text               AS kind,      -- p = 主キー / u = 一意制約
       array_length(c.conkey, 1) = 1 AS prevents_dup, -- その列だけで重複を防げるか
       pg_get_constraintdef(c.oid)   AS definition
FROM   pg_constraint AS c JOIN target AS t ON c.conrelid = t.attrelid
WHERE  c.contype IN ('p', 'u')
  AND  t.attnum = ANY (c.conkey)                -- その列を鍵に含むものだけ
UNION ALL
SELECT i.relname                     AS name,
       'i'                           AS kind,      -- i = 制約を伴わない一意インデックス
       -- 部分インデックス(indpred が非NULL)は条件に合う行の中でしか一意を保証しない
       x.indnkeyatts = 1 AND x.indpred IS NULL AS prevents_dup,
       pg_get_indexdef(x.indexrelid) AS definition
FROM   pg_index AS x
JOIN   pg_class AS i ON i.oid = x.indexrelid
JOIN   target AS t ON x.indrelid = t.attrelid
WHERE  x.indisunique
  AND  EXISTS (                                 -- INCLUDE 列は鍵ではないので数えない。
         SELECT 1                               -- indkey の先頭 indnkeyatts 個だけを見る
         FROM   generate_series(0, x.indnkeyatts - 1) AS k(i)  -- indkey は 0 始まり
         WHERE  x.indkey[k.i] = t.attnum)
  AND  NOT EXISTS (                             -- 制約に紐づく索引は上で出している
         SELECT 1 FROM pg_constraint AS c2 WHERE c2.conindid = x.indexrelid);

القراءة على مرحلتين.

  • kind هو p للمفتاح الرئيس، وu لقيد التفرّد، وi لفهرس فريد بلا قيد وُضع بـ CREATE UNIQUE INDEX.15
  • إن لم يوجد أيّ صفّ فيه prevents_dup يساوي true، فتكرار ذلك العمود غير ممنوع.
مسارا SQL تأكيد التفرّديبيّن أنّ تأكيد التفرّد يسحب جانبي pg_constraint حيث يظهر المفتاح الرئيس وقيد التفرّد وpg_index حيث يظهر CREATE UNIQUE INDEX بلا قيد، ويحكم في الشكلين بـ prevents_dup على تفرّد ذلك العمود وحده.تأكيد تفرّد عمود UUIDpg_constraint (p وu)pg_index (فهرس فريد)الحكم بـ prevents_dup هل التفرّد فردي

الشكل 17: القيد والفهرس الفريد يظهران في فهرسين مختلفين، لذلك يُسحبان معاً ويُحكَم بـ prevents_dup.

لا تحكم بـ «هل للجدول قيد تفرّد». تصميم فيه المفتاح الرئيس على id تسلسليّ وعمود UUID بلا قيد شائع جدّاً. العدّ على مستوى الجدول يبلّغ خطأ «خطّ دفاع أخير موجود» عن تلك الحالة. وللسبب نفسه، وجود مفتاح مركّب يشمل UUID يمرّر تكرار UUID وحده، لذلك يلزم النظر إلى prevents_dup.

ثمّ لا تنظر إلى pg_constraint وحده. تصميم يضع التفرّد بـ CREATE UNIQUE INDEX شائع أيضاً، وذلك يظهر في pg_index فقط. إن عدّت القيود وحدها وبلّغت «لا خطّ دفاع أخير»، أغفلت فهرساً قائماً ومضى الحديث إلى تغيير مخطّط غير لازم. الواحدة أعلاه تلتقط الشكلين (indnkeyatts من PostgreSQL 11 فصاعداً).

حكم جانب الفهرس إن كُتب ببساطة «هل العمود داخل indkey» أخرج نوعين من الكشف الخاطئ. كلاهما ينحاز إلى جهة «ممنوع»، فتضغط ختم القبول كما هو.

  • عدّ أعمدة INCLUDE مفاتيح. في CREATE UNIQUE INDEX ... ON orders(id) INCLUDE (uuid) يصطفّ في indkey أيضاً uuid الذي ليس مفتاحاً.15 في المقابل indnkeyatts عدد المفاتيح (1 في هذا المثال)، فيقوم معاً indnkeyatts = 1 وتضمين uuid، فيصير الفهرس الموضوع على id «يمنع تكرار uuid». المفاتيح هي أول indnkeyatts عناصر من indkey فقط، لذلك تطابق هناك حصراً (فهرس indkey يبدأ من 015)
  • عدّ الفهرس الجزئي ضماناً للكلّ. CREATE UNIQUE INDEX ... ON orders(uuid) WHERE active لا يضمن التفرّد إلّا داخل الصفوف التي تطابق الشرط. بين الصفوف غير active، أو بين صفّ على الفهرس وصفّ ليس عليه، يمكن أن يتعايش UUID نفسه عادة. إن كان indpred غير NULL فهو فهرس جزئي،15 فلا يُعدّ منعاً للتكرار الكلّي (الفهرس نفسه يبقى في القائمة، فيمكن من جملة WHERE في definition معاملته خطّ دفاع مشروط)
فخّان يسهّلان الكشف الخاطئ في تدقيق التفرّدمخطّط يبيّن أنّ العدّ على مستوى الجدول في التدقيق يبلّغ خطأ المفتاح الرئيس التسلسليّ بوصفه خطّ دفاع أخيراً، وأنّ النظر إلى pg_constraint وحده يغفل الفهرس الفريد الموضوع بـ CREATE UNIQUE INDEX، وأنّ خطأ عدّ عمود INCLUDE مفتاحاً وخطأ عدّ الفهرس الجزئي ضماناً للكلّ ينحازان كلاهما إلى جهة ممنوع، لذلك يلزم تحديد عمود UUID والنظر إلى prevents_dup.الفحص بتحديد عمود UUIDالتقاط القيود والفهارس معاًالنظر إلى prevents_dupالعدّ على مستوى الجدول يبلّغ خطأكشف خاطئ لعمود INCLUDE والفهرس الجزئي

الشكل 18: التدقيق يكشف خطأ إن لم ينظر إلى «تحديد العمود وهل هو فريد بذلك العمود وحده».

10. خلاصة

حوادث تصادم UUID تبدأ في الغالب لا من أنّ UUID ضعيف، بل من كسر فرضيّة UUID في التنفيذ أو التشغيل.

  • التوليد الذاتي بعشوائيّة ضعيفة
  • إرجاع الحالة بعد fork أو snapshot
  • استخدام UUID القائم على الاسم لإصدار المعرّفات
  • تنفيذ v7 أو v8 ذاتيّاً بخفّة
  • التقصير في الأثناء والتخلّي عن التفرّد
  • إسقاط قيد التفرّد في قاعدة البيانات

إن فُعل هذا، فالأمر لا يختلف كثيراً عن الذهاب بأنفسكم إلى وضع يسهّل التصادم.

إذا وجدت تكراراً، فما يُشتبه فيه أوّلاً قبل رياضيّات UUID هو المولّد، وإدارة الحالة، وشكل الحفظ، وتصميم القيد. بهذا الترتيب ينحصر السبب في الغالب كثيراً.

ترتيب الاشتباه عند العثور على تكرارمخطّط يبيّن أنّه إذا وُجد تكرار فالاشتباه بترتيب المولّد ثم إدارة الحالة ثم شكل الحفظ ثم تصميم القيد، قبل رياضيّات UUID، ينحصر السبب في الغالب كثيراً.عُثر على تكرار1. اشتبه في المولّد2. اشتبه في إدارة الحالة3. اشتبه في شكل الحفظ4. اشتبه في تصميم القيدرياضيّات UUID يجوز أن تكون الأخيرة

الشكل 19: بهذا الترتيب ينحصر سبب تكرار UUID تقريباً.

11. مقالات ذات صلة

12. روابط مرجعية

  1. IETF RFC 9562, Section 5.4 UUID Version 4. عن منطقة العشوائيّة من 122 بتّاً في UUIDv4. ↩ ↩2

  2. IETF RFC 9562, Section 5.7 UUID Version 7. عن تفكير timestamp وrandom bits والعدّاد في UUIDv7. ↩ ↩2 ↩3 ↩4

  3. IETF RFC 9562, Section 5.8 UUID Version 8. عن أنّ تفرّد UUIDv8 معتمد على التنفيذ ولا يجوز افتراضه. ↩ ↩2 ↩3

  4. Python 3.14 documentation, uuid module. عن التوليد الآمن تشفيريّاً في uuid4()، والسلوك الحتميّ في uuid5()، وخصائص uuid7() / uuid8(). ↩ ↩2 ↩3

  5. IETF RFC 9562, Universally Unique IDentifiers (UUIDs). وثيقة الأساس لشكل UUID وكلّ إصدار وأفضل الممارسات ككلّ. ↩ ↩2

  6. PostgreSQL documentation, Constraints. عن ضمان التفرّد بقيد UNIQUE وPRIMARY KEY. ↩ ↩2

  7. IETF RFC 9562, Section 6.5 Name-Based UUID Generation. عن أنّ same namespace + same name يعطيان UUID نفسه، وأهمّيّة canonicalization. ↩ ↩2 ↩3

  8. IETF RFC 9562, Section 6.9 Unguessability. عن استخدام CSPRNG وإعادة seed بعد fork. ↩ ↩2 ↩3

  9. IETF RFC 9562, Section 6.3 UUID Generator States. عن التعامل مع stable storage وgenerator state. ↩ ↩2 ↩3

  10. IETF RFC 9562, Section 5.5 UUID Version 5. عن مواصفة UUID القائم على الاسم استناداً إلى namespace + canonical name. ↩

  11. IETF RFC 9562, Section 5.6 UUID Version 6. عن node / clock sequence / DB locality في UUIDv6. ↩

  12. IETF RFC 9562, Section 6.4 Distributed UUID Generation. عن مقاومة تصادم العقدة في البيئة الموزّعة. ↩

  13. IETF RFC 9562, Section 6.2 Monotonicity and Counters. عن تنبيهات clock rollback وcounter rollover والتوليد الدفعيّ. ↩ ↩2 ↩3 ↩4

  14. IETF RFC 9562, Sections 6.7 and 6.8. عن تفكير collision resistance وglobal uniqueness. ↩

  15. PostgreSQL documentation, pg_constraint. عن أنّ contype بقيمة p يعني primary key وu يعني unique constraint، وأنّ conrelid يشير إلى جدول القيد، وأنّ conindid يشير إلى الفهرس الذي يسند القيد. الفهرس الفريد بلا قيد يظهر فقط في جانب pg_index (indrelid الجدول المستهدف، وindisunique هل هو فريد). ↩ ↩2 ↩3 ↩4

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

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

أسئلة شائعة حول موضوع هذه المقالة.

هل UUID لا يتصادم؟
إن استُخدم استخداماً عادياً فهو صعب التصادم بما يكفي. في RFC 9562 يملك UUIDv4 منطقة عشوائيّة من 122 بتّاً، وUUIDv7 أيضاً معرّف على أساس أنّ 74 بتّاً خارج الطابع الزمني تُستخدم عشوائيّة أو عدّاداً للتفرّد. ما دمت تستخدم تنفيذاً سليماً استخداماً عادياً، مثل uuid4() في Python، فالفرضيّة قويّة جدّاً. غير أنّ RFC 9562 نفسها تقول إنّ UUID لا يستطيع ضمان تفرّد عالميّ مطلق، وفي الاستخدامات ذات أثر التصادم الكبير ينبغي اتّخاذ تدابير أقوى. لهذا يكون قيد التفرّد في قاعدة البيانات خطّ الدفاع الأخير.
لماذا يحدث تكرار UUID؟
أكثر تكرار UUID في العمل ليس مشكلة المعيار نفسه، بل حالات يكسر فيها التنفيذ أو التشغيل شروط التوليد التي يفترضها المعيار. الأنماط النموذجيّة ستّة: توليد UUID ذاتيّاً بـ seed ثابت أو PRNG ضعيف، وإرجاع حالة المولّد بعد fork أو VM snapshot أو استنساخ الحاوية، واستخدام UUIDv3 / v5 بظنّ أنّه «معرّف جديد في كلّ مرّة»، وتنفيذ UUID زمنيّ أو UUIDv8 ذاتيّاً مع معالجة متساهلة لـ clock rollback والعدّاد، واقتطاع UUID في الأثناء والتخلّي عن التفرّد، وترك قيد التفرّد في قاعدة البيانات فيدخل التكرار بهدوء.
هل استخدام UUIDv3 أو UUIDv5 يسبّب التكرار؟
UUIDv3 / v5 ليسا معرّفاً عشوائيّاً صعب التصادم، بل معرّفاً حتميّاً يعيد توليد المعرّف نفسه من الاسم نفسه. تنصّ RFC 9562 على أنّ UUID المولَّد من namespace نفسه وcanonical name نفسه يجب أن يتساوى، لذلك ظهور UUID نفسه من الدخل نفسه ليس حادثاً بل سلوكاً مطابقاً للمواصفة. إذن استخدامه لإصدار معرّفات جديدة إساءة استخدام. وبالعكس، إن تذبذب canonicalization للاسم صار للهدف نفسه UUID مختلف. المهمّ تثبيت تصميم namespace وقواعد توحيد الاسم كمواصفة.
ماذا نفعل لمنع تكرار UUID؟
أوّلاً لا تولّد UUID بنفسك، ومِل نحو واجهة قياسيّة مثل uuid4() / uuid7() أو تنفيذ شائع. حدّد إصدار UUID المستخدم كمواصفة، ولا تورّث حالة المولّد بعد fork أو إعادة تشغيل العامل أو snapshot أو clone. احفظ وقارن بكامل 128 بتّاً، ولا تستخدم مقارنة البادئة أو العرض المختصر كمفتاح أصليّ. ثمّ إن كان التكرار غير مقبول حقّاً فضع قيد UNIQUE / PRIMARY KEY في قاعدة البيانات، ولا تبتلع duplicate key، وابقَ قادراً على تتبع أيّ مولّد وأيّ عقدة أخرجته.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة