هل 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
flowchart TB
accTitle: كيف تُرى حوادث تكرار UUID
accDescr: مخطّط يبيّن أنّ أكثر تكرار UUID في العمل ليس مشكلة معيار UUID نفسه، بل حالات يكسر فيها التنفيذ أو التشغيل شروط التوليد التي يفترضها المعيار، وأنّ الفرضيّة في جانب UUID قويّة جدّاً ما دام التنفيذ السليم يُستخدم استخداماً عادياً.
u1["معيار UUID نفسه"] -.-> u2["الفرضيّة قويّة جدّاً (122 بتّاً عشوائيّة وغيرها)"]
u3["التنفيذ والتشغيل"] --> u4["يكسران شروط التوليد التي يفترضها المعيار"]
u4 --> u5["أكثر حوادث التكرار في العمل من هنا"]
الشكل 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 تصادم.
flowchart TB
accTitle: أين يُحذف التفرّد
accDescr: مخطّط يبيّن أنّ الأمر أقرب إلى حذف التفرّد المتوقَّع من UUID في تصميم الأثناء عبر التوليد أو النسخ والرجوع أو إساءة الاستخدام أو الاقتطاع أو غياب القيد، منه إلى أنّ UUID تصادم.
s0["التفرّد المتوقَّع من UUID"] --> s1["يُحذف بطريقة التوليد (ذاتيّ / عشوائيّة ضعيفة)"]
s0 --> s2["يُحذف بالتشغيل (snapshot وfork)"]
s1 --> s3["يُحذف بالحفظ (اقتطاع وغياب القيد)"]
s2 --> s3
s3 --> s4["النتيجة ظهور التكرار"]
الشكل 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
flowchart TB
accTitle: الخصائص تختلف حسب الإصدار
accDescr: مخطّط يبيّن أنّ UUIDv4 قائم على العشوائيّة، وUUIDv7 بنية زمنيّة تجمع الطابع الزمني مع عشوائيّة أو عدّاد، وUUIDv3 وv5 قائمان على الاسم بحيث يخرج القيمة نفسها من الدخل نفسه، وUUIDv8 تفرّده معتمد على التنفيذ، فالخصائص تختلف تماماً حسب الإصدار.
v0["إصدار UUID"] --> v1["v4: 122 بتّاً عشوائيّة"]
v0 --> v2["v7: وقت + عشوائيّة وعدّاد"]
v1 --> v3["v3 / v5: الدخل نفسه يعطي القيمة نفسها (قائم على الاسم)"]
v2 --> v4["v8: التفرّد معتمد على التنفيذ"]
الشكل 3: «نستخدم UUID» وحده لا يحسم الأمر؛ الخصائص تختلف تماماً حسب الإصدار.
أي إنّ قول «نستخدم UUID» لا يكفي، فالمضمون:
- أهو
uuid4()من المكتبة القياسيّة - أم
timestamp + randomذاتيّ - أم
uuid5(namespace, name) - أم تنسيق خاصّ يبدو كـ UUIDv8 فقط
يغيّر الحديث تماماً.
flowchart TD
A[رصد تكرار UUID] --> B{أين صارت القيمة نفسها حقاً}
B --> C[المولّد ضعيف]
B --> D[الحالة رجعت]
B --> E[إساءة استخدام UUID القائم على الاسم]
B --> F[اقتطاع عند الحفظ]
B --> G[لا قيد تفرّد في قاعدة البيانات]
C --> H[خطأ تنفيذ]
D --> H
E --> H
F --> H
G --> H
الشكل 4: سبب التكرار ينحصر في خمس سلاسل: المولّد، والحالة، وإساءة الاستخدام، والاقتطاع، وغياب القيد.
في العمل، النظر من يمين هذا المخطّط أسرع.
وإذا كُتب كإجراء، فالعمليّ هو الإغلاق بالترتيب من المواضع الأقلّ كلفة في التحقّق. بالترتيب التالي تتزايد الأدوات اللازمة تدريجيّاً: «تشغيل SQL واحد»، ثم «grep على الشيفرة»، ثم «تأكيد إجراء التشغيل»، فيمكن التوقّف حين يظهر السبب.
flowchart TD
S["رصد duplicate key أو بيانات مكرّرة"] --> Q1{"1. هل ثمّة UNIQUE / PRIMARY KEY في قاعدة البيانات (الفصل 8)"}
Q1 -- لا --> A1["دخل بهدوء بلا قيد. ضع القيد أوّلاً وأوقف مدخل التكرار"]
Q1 -- نعم --> Q2{"2. هل يُحفَظ ويُقارَن بكامل 128 بتّاً (الفصل 7)"}
Q2 -- يُقتطَع --> A2["اشتبه في مقارنة البادئة والسحق إلى 64 بتّاً وقطع النهاية لنقص طول العمود"]
Q2 -- يُحفَظ كاملاً --> Q3{"3. هل التوليد عبر واجهة قياسيّة (الفصلان 3 و5)"}
Q3 -- تنفيذ ذاتي --> A3["اشتبه في التوليد الذاتي بـ PRNG ضعيف أو إساءة استخدام UUID القائم على الاسم لإصدار المعرّفات"]
Q3 -- واجهة قياسيّة --> Q4{"4. هل وُرثت حالة المولّد بعد fork / snapshot / clone (الفصل 4)"}
Q4 -- وُرثت --> A4["حالة المولّد راجعة"]
Q4 -- لا مشكلة --> A5["المتبقي تنفيذ زمنيّ خاصّ أو تصميم 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 が同じなら同じ値が出る
في هذا المثال مشكلتان في الواقع.
random.Randomمولّد PRNG عامّ، فإن كان seed نفسه أُعيدت السلسلة كما هي. وحتّى إن استخدمت وقت الإقلاع أو PID بوصفه seed، فقد يتصادم عند الإقلاع المتزامن أو clone.- لم تُضبَط بتّات version / variant أصلاً، فليس هذا UUIDv4 وفق RFC 9562. الدقيق أنّه عدد من 128 بتّاً بشكل UUID لا أكثر.
flowchart TB
accTitle: مشكلتا UUID الشبيه المصنوع ذاتيّاً
accDescr: مخطّط يبيّن أنّ تجميع سلسلة بشكل UUID عبر تشغيل PRNG عامّ بـ seed ثابت يعيد السلسلة نفسها إن كان seed نفسه فتخرج القيمة نفسها في عمليّة أخرى أو عقدة أخرى، وأنّ بتّات version وvariant لا تُضبَط فلا يكون UUIDv4 وفق RFC 9562 بل عدداً من 128 بتّاً بشكل UUID.
b1["توليد ذاتي بـ PRNG عامّ + seed ثابت"] --> b2["الـ seed نفسه يعيد السلسلة نفسها"]
b1 --> b3["بتّات version / variant غير مضبوطة"]
b2 --> b4["القيمة نفسها تخرج في عمليّة أخرى أو عقدة أخرى"]
b3 --> b5["عدد من 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 العشوائيّة يدوياً
- استخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدام كما هو
الإبقاء على مولّد خاصّ لأنّه «خفيف» أو «مستخدم منذ القديم» يكلّف أغلى ثمن لاحقاً.
flowchart TB
accTitle: الخلاصة العمليّة حول التوليد
accDescr: مخطّط يبيّن أنّ RFC 9562 تقول إنّه ينبغي استخدام CSPRNG للتفرّد وصعوبة التوقّع وتتطرّق إلى إعادة seed عند fork، وأنّ الخلاصة العمليّة ثلاث نقاط: لا تصنع UUID بنفسك، ولا تعبث بـ seed العشوائيّة يدوياً، واستخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدام كما هو.
r1["لا تصنع UUID بنفسك"] --> r2["لا تعبث بـ seed العشوائيّة يدوياً"]
r2 --> r3["استخدم المكتبة القياسيّة أو تنفيذاً شائع الاستخدام"]
r3 -.-> r4["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
flowchart TB
accTitle: كيف يصنع نسخ الحالة ورجوعها التكرار
accDescr: مخطّط يبيّن أنّ استعادة VM snapshot أو استنساخ صورة الحاوية أو fork العامل إن ورث حالة المولّد كما هي أرجع حالة العشوائيّة أو العدّاد فأعاد سلسلة التوليد دون قصد وظهر التكرار، وأنّ التدبير إعادة التهيئة فوراً بعد fork وclone وrestore والميل إلى عشوائيّة من نظام التشغيل.
c1["استعادة snapshot واستنساخ الحاوية وfork العامل"] --> c2["توريث حالة المولّد كما هي"]
c2 --> c3["رجوع حالة العشوائيّة أو العدّاد"]
c3 --> c4["إعادة سلسلة التوليد وظهور التكرار"]
c2 -.-> c5["التدبير: إعادة تهيئة فوريّة والميل إلى عشوائيّة من نظام التشغيل"]
الشكل 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 لذلك فالاستخدام التالي يجعل التكرار سلوكاً مطابقاً للمواصفة لا حادثاً.
flowchart TB
accTitle: الفهم الصحيح لـ UUID القائم على الاسم
accDescr: مخطّط يبيّن أنّ UUIDv3 وv5 ليسا معرّفاً عشوائيّاً صعب التصادم بل معرّفاً حتميّاً يعيد توليد UUID نفسه من namespace نفسه وcanonical name نفسه، وأنّ التكرار عند استخدامه لإصدار معرّفات جديدة سلوك مطابق للمواصفة لا حادث.
n1["namespace نفسه + canonical name نفسه"] --> n2["UUID نفسه حتماً (MUST في المواصفة)"]
n2 --> n3["إن استُخدم لإصدار جديد فالتكرار مطابق للمواصفة"]
n1 -.-> n4["معرّف حتميّ لا عشوائيّ"]
الشكل 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 للاسم كمواصفة
flowchart TB
accTitle: ماذا يجلب تذبذب canonicalization
accDescr: مخطّط يبيّن أنّ تذبذب توحيد الاسم يجعل للهدف نفسه UUID مختلفاً، وأنّ الدخل نفسه يعطي المعرّف نفسه حتماً، لذلك فالأأمن تثبيت تصميم namespace كمواصفة وجمع دالة التوحيد في موضع واحد وتمريرها حتماً قبل uuid5.
m1["توحيد الاسم"] --> m2{"هل هو غير متذبذب"}
m2 -->|"جمع دالة التوحيد في موضع واحد وتمريرها حتماً"| m3["الهدف نفسه يعطي المعرّف نفسه حتماً"]
m2 -.->|"تذبذب مثل الشرطة المائلة في النهاية"| m4["الهدف نفسه UUID مختلف"]
m1 -.-> m5["تثبيت تصميم 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 إلى قيمة ثابتة عند كلّ إعادة تشغيل
خطر.
flowchart TB
accTitle: جزميات خطرة في UUIDv1 / v6
accDescr: مخطّط يبيّن أنّ RFC تكتب أنّ ظهور الآلات الافتراضيّة والحاويات جعل تفرّد عنوان MAC غير مضمون بعد، لذلك فالجزم بأنّ عنوان MAC يعني التفرّد، واستنساخ node ID بحرقه في الصورة، وإعادة clock sequence إلى قيمة ثابتة عند كلّ إعادة تشغيل، تصاميم خطرة.
d1["الجزم بأنّ MAC يعني التفرّد"] -.-> d4["تصميم خطر"]
d2["استنساخ node ID بحرقه في الصورة"] -.-> d4
d3["إعادة clock sequence إلى قيمة ثابتة"] -.-> d4
d4 --> d5["في بيئة الافتراض لا يُضمَن تفرّد 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
إذن فتنفيذ مثل:
- إصدار كثيف داخل الملّي ثانية نفسها بلا تصميم عدّاد
- مواصلة التوليد دون فعل شيء حين يرجع الوقت
- تهيئة العمليّات المتعدّدة العدّاد الداخليّ نفسه كلٌّ على حدة
خطر.
flowchart TB
accTitle: نقاط خطر إن تُركت في صنع UUIDv7 ذاتيّاً
accDescr: مخطّط يبيّن أنّ الإصدار الكثيف بلا تصميم عدّاد داخل الملّي ثانية نفسها، ومواصلة التوليد دون فعل شيء حين يرجع الوقت، وتهيئة العمليّات المتعدّدة العدّاد الداخليّ نفسه كلٌّ على حدة، تنفيذ خطر، وأنّ RFC تقول إنّه لا يجوز إعادة قيمة مع العلم أنّها مكرّرة بسبب clock rollback أو counter rollover.
e1["إصدار كثيف بلا تصميم عدّاد"] -.-> e4["v7 ذاتي سهل التكرار"]
e2["ترك clock rollback"] -.-> e4
e3["تهيئة العدّاد نفسه كلٌّ على حدة"] -.-> e4
e4 --> e5["إعادة قيمة مع العلم أنّها مكرّرة محظورة"]
الشكل 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.
flowchart TB
accTitle: كيف يُحدَّد العدد الذي يضمنه العدّاد
accDescr: مخطّط يبيّن أنّ RFC 9562 تطلب تهيئة العدّاد بقيمة عشوائيّة في كلّ tick لذلك فالعدد المتاح داخل tick واحد هو 4096 ناقص القيمة الابتدائيّة، وأنّ حجز البتّات العليا على 0 وعشوائيّة الجانب الأدنى يحدّد حدّاً أدنى مضموناً، لذلك يُصمَّم بترتيب تقرير نطاق التهيئة المسموح ثم سلوك rollover.
f1["تهيئة بقيمة عشوائيّة في كلّ tick"] --> f2["المتاح 4096 ناقص القيمة الابتدائيّة"]
f2 --> f3["حجز البتّات العليا يضمن حدّاً أدنى"]
f3 --> f4["1. تقرير نطاق التهيئة المسموح"]
f4 --> f5["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. إدخاله بلا مراجعة أخطر من أن يُقبل.
flowchart TB
accTitle: معنى استخدام UUIDv8
accDescr: مخطّط يبيّن أنّ RFC 9562 تقول إنّ تفرّد UUIDv8 معتمد على التنفيذ ولا يجوز افتراضه، لذلك في UUID خاصّ بالشركة يضمّن timestamp أو shard id أو معنى أعمال تصير وثيقة التصميم نفسها مواصفة التفرّد، وإدخاله بلا مراجعة أخطر من أن يُقبل.
g1["تركيب تنسيق خاصّ بـ UUIDv8"] --> g2["التفرّد معتمد على التنفيذ والمعيار لا يضمنه"]
g2 --> g3["وثيقة تصميم الشركة تصير مواصفة التفرّد نفسها"]
g3 --> g4["الإدخال بلا مراجعة أخطر من أن يُقبل"]
الشكل 14: اختيار v8 يعني تحمّل مسؤوليّة التفرّد بأنفسكم.
7. النمط 5: تقصير UUID في الأثناء
قد يكون التوليد صحيحاً حتى تلك النقطة، ثم يُكسَر في مرحلة الحفظ أو المقارنة.
أمثلة نموذجيّة.
- استخدام أول 8 أحرف بدل مفتاح خارجيّ
- سحق UUID من 128 بتّاً إلى عدد صحيح من 64 بتّاً
- نقص طول عمود النصّ فيُقطع الذيل
- معاملة التمثيل المختصر في السجلّ أو الشاشة كمفتاح تفرّد كما هو
المهمّ هنا أنّ تغيير التمثيل نفسه ليس سيّئاً.
تحويلات لا تُسقط 128 بتّاً مثل:
- إزالة الشرطات
- توحيد الأحرف الصغيرة / الكبيرة
- الحفظ بـ 16 بايت ثنائيّة
لا مشكلة فيها. الخطر في تحويل يحذف مادّة التفرّد نفسها.
وخصوصاً تصميم صنع «معرّف مختصر يسهل على الإنسان قراءته» على حدة ثم صار دون انتباه مقدَّماً على UUID الأصليّ سهل الحوادث.
flowchart TB
accTitle: الفرق بين تحويل التمثيل وتحويل يحذف التفرّد
accDescr: مخطّط يبيّن أنّ تحويلات لا تُسقط 128 بتّاً مثل إزالة الشرطات وتوحيد حالة الأحرف والحفظ بـ 16 بايت ثنائيّة لا مشكلة فيها، وأنّ التحويلات التي تحذف مادّة التفرّد نفسها مثل استخدام أول 8 أحرف والسحق إلى 64 بتّاً وقطع الذيل لنقص طول العمود خطرة.
h0{"هل يحافظ ذلك التحويل على 128 بتّاً"}
h0 -->|"يحافظ"| h1["إزالة الشرطات وتوحيد حالة الأحرف والتحويل الثنائيّ"]
h0 -.->|"يحذف"| h2["أول 8 أحرف والتحويل إلى 64 بتّاً وقطع الذيل"]
h1 --> h3["تحويل لا مشكلة فيه"]
h2 --> h4["تحويل خطر تتخلّى فيه بنفسك عن التفرّد"]
الشكل 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 وعدم وضع قيد تفرّد ليسا مترادفين.
flowchart TB
accTitle: قيد التفرّد في قاعدة البيانات بوصفه خطّ الدفاع الأخير
accDescr: مخطّط يبيّن أنّ الجمع الأساس هو استخدام UUID بوصفه معرّفاً صعب التصادم، ومع ذلك، بما أنّ RFC نفسها تقول إنّها لا تستطيع ضمان تفرّد عالميّ مطلق، فإن كان التكرار غير مقبول حقّاً فاجعل UNIQUE أو PRIMARY KEY في قاعدة البيانات خطّ دفاع أخيراً، وصمّم أيضاً retry وidempotency وincident logging عند التكرار.
i1["استخدام UUID بوصفه معرّفاً صعب التصادم"] --> i2["ومع ذلك لا ضمان مطلق (منصوص في RFC)"]
i2 --> i3["جعل UNIQUE / PRIMARY KEY في قاعدة البيانات خطّ الدفاع الأخير"]
i3 --> i4["تصميم 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، فتكرار ذلك العمود غير ممنوع.
flowchart TB
accTitle: مسارا SQL تأكيد التفرّد
accDescr: يبيّن أنّ تأكيد التفرّد يسحب جانبي pg_constraint حيث يظهر المفتاح الرئيس وقيد التفرّد وpg_index حيث يظهر CREATE UNIQUE INDEX بلا قيد، ويحكم في الشكلين بـ prevents_dup على تفرّد ذلك العمود وحده.
chk["تأكيد تفرّد عمود UUID"] --> con["pg_constraint (p وu)"]
chk --> idx["pg_index (فهرس فريد)"]
con --> pd["الحكم بـ prevents_dup هل التفرّد فردي"]
idx --> pd
الشكل 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معاملته خطّ دفاع مشروط)
flowchart TB
accTitle: فخّان يسهّلان الكشف الخاطئ في تدقيق التفرّد
accDescr: مخطّط يبيّن أنّ العدّ على مستوى الجدول في التدقيق يبلّغ خطأ المفتاح الرئيس التسلسليّ بوصفه خطّ دفاع أخيراً، وأنّ النظر إلى pg_constraint وحده يغفل الفهرس الفريد الموضوع بـ CREATE UNIQUE INDEX، وأنّ خطأ عدّ عمود INCLUDE مفتاحاً وخطأ عدّ الفهرس الجزئي ضماناً للكلّ ينحازان كلاهما إلى جهة ممنوع، لذلك يلزم تحديد عمود UUID والنظر إلى prevents_dup.
j1["الفحص بتحديد عمود UUID"] --> j2["التقاط القيود والفهارس معاً"]
j2 --> j3["النظر إلى prevents_dup"]
j1 -.-> j4["العدّ على مستوى الجدول يبلّغ خطأ"]
j2 -.-> j5["كشف خاطئ لعمود INCLUDE والفهرس الجزئي"]
الشكل 18: التدقيق يكشف خطأ إن لم ينظر إلى «تحديد العمود وهل هو فريد بذلك العمود وحده».
10. خلاصة
حوادث تصادم UUID تبدأ في الغالب لا من أنّ UUID ضعيف، بل من كسر فرضيّة UUID في التنفيذ أو التشغيل.
- التوليد الذاتي بعشوائيّة ضعيفة
- إرجاع الحالة بعد fork أو snapshot
- استخدام UUID القائم على الاسم لإصدار المعرّفات
- تنفيذ v7 أو v8 ذاتيّاً بخفّة
- التقصير في الأثناء والتخلّي عن التفرّد
- إسقاط قيد التفرّد في قاعدة البيانات
إن فُعل هذا، فالأمر لا يختلف كثيراً عن الذهاب بأنفسكم إلى وضع يسهّل التصادم.
إذا وجدت تكراراً، فما يُشتبه فيه أوّلاً قبل رياضيّات UUID هو المولّد، وإدارة الحالة، وشكل الحفظ، وتصميم القيد. بهذا الترتيب ينحصر السبب في الغالب كثيراً.
flowchart TB
accTitle: ترتيب الاشتباه عند العثور على تكرار
accDescr: مخطّط يبيّن أنّه إذا وُجد تكرار فالاشتباه بترتيب المولّد ثم إدارة الحالة ثم شكل الحفظ ثم تصميم القيد، قبل رياضيّات UUID، ينحصر السبب في الغالب كثيراً.
k0["عُثر على تكرار"] --> k1["1. اشتبه في المولّد"]
k1 --> k2["2. اشتبه في إدارة الحالة"]
k2 --> k3["3. اشتبه في شكل الحفظ"]
k3 --> k4["4. اشتبه في تصميم القيد"]
k0 -.-> k5["رياضيّات UUID يجوز أن تكون الأخيرة"]
الشكل 19: بهذا الترتيب ينحصر سبب تكرار UUID تقريباً.
11. مقالات ذات صلة
- دليل FileSystemWatcher في العمل - معالجة الأحداث المفقودة والتكرار
- أساسيات التحكّم الحصري في تكامل الملفّات - أفضل الممارسات لقفل الملفّ والمطالبة الذرّيّة
12. روابط مرجعية
-
IETF RFC 9562, Section 5.4 UUID Version 4. عن منطقة العشوائيّة من 122 بتّاً في UUIDv4. ↩ ↩2
-
IETF RFC 9562, Section 5.7 UUID Version 7. عن تفكير timestamp وrandom bits والعدّاد في UUIDv7. ↩ ↩2 ↩3 ↩4
-
IETF RFC 9562, Section 5.8 UUID Version 8. عن أنّ تفرّد UUIDv8 معتمد على التنفيذ ولا يجوز افتراضه. ↩ ↩2 ↩3
-
Python 3.14 documentation,
uuidmodule. عن التوليد الآمن تشفيريّاً فيuuid4()، والسلوك الحتميّ فيuuid5()، وخصائصuuid7()/uuid8(). ↩ ↩2 ↩3 -
IETF RFC 9562, Universally Unique IDentifiers (UUIDs). وثيقة الأساس لشكل UUID وكلّ إصدار وأفضل الممارسات ككلّ. ↩ ↩2
-
PostgreSQL documentation, Constraints. عن ضمان التفرّد بقيد UNIQUE وPRIMARY KEY. ↩ ↩2
-
IETF RFC 9562, Section 6.5 Name-Based UUID Generation. عن أنّ same namespace + same name يعطيان UUID نفسه، وأهمّيّة canonicalization. ↩ ↩2 ↩3
-
IETF RFC 9562, Section 6.9 Unguessability. عن استخدام CSPRNG وإعادة seed بعد fork. ↩ ↩2 ↩3
-
IETF RFC 9562, Section 6.3 UUID Generator States. عن التعامل مع stable storage وgenerator state. ↩ ↩2 ↩3
-
IETF RFC 9562, Section 5.5 UUID Version 5. عن مواصفة UUID القائم على الاسم استناداً إلى namespace + canonical name. ↩
-
IETF RFC 9562, Section 5.6 UUID Version 6. عن node / clock sequence / DB locality في UUIDv6. ↩
-
IETF RFC 9562, Section 6.4 Distributed UUID Generation. عن مقاومة تصادم العقدة في البيئة الموزّعة. ↩
-
IETF RFC 9562, Section 6.2 Monotonicity and Counters. عن تنبيهات clock rollback وcounter rollover والتوليد الدفعيّ. ↩ ↩2 ↩3 ↩4
-
IETF RFC 9562, Sections 6.7 and 6.8. عن تفكير collision resistance وglobal uniqueness. ↩
-
PostgreSQL documentation, pg_constraint. عن أنّ
contypeبقيمةpيعني primary key وuيعني unique constraint، وأنّconrelidيشير إلى جدول القيد، وأنّconindidيشير إلى الفهرس الذي يسند القيد. الفهرس الفريد بلا قيد يظهر فقط في جانب pg_index (indrelidالجدول المستهدف، وindisuniqueهل هو فريد). ↩ ↩2 ↩3 ↩4
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
لماذا يعمل مجلّد مشترك في Windows أحياناً ويفشل أحياناً ── فصل Kerberos وNTLM وبيانات الاعتماد
شخّص انقطاع الوصول المتقطّع إلى مجلّد مشترك في Windows من الأعراض والسجلّات. راجع الأسماء مقابل عناوين IP، والفشل في التطبيق وحده، وكلمات...
القرص عند 100٪: ما الذي ينبغي إيقافه فعليّاً؟ — التمييز بين SysMain وWindows Search وDefender
اعزل استخدام قرص Windows عند 100٪ عبر معدّل النقل وزمن الاستجابة والملفّات. أوقف SysMain مؤقّتاً بأمان، وضيّق نطاق Windows Search، وحلّل ...
الحجم نفسه 1 غيغابايت، لكن مجلّد الصور يُنسَخ أبطأ من فيديو واحد — لماذا؟
لماذا تختلف سرعة النسخ على Windows عند الحجم نفسه: عدد الملفّات، وزمن انتظار SSD وNAS، والتجميع في ZIP، ومقارنة الإنشاء والنقل والاستخراج...
ما جدولة GPU المسرَّعة عتاديّاً في Windows؟ هل تفعيلها يجعل الحاسوب أسرع؟
دليل موضَّح لغير المختصّين عن جدولة GPU المسرَّعة عتاديّاً (HAGS) في Windows: كيف تعمل، ومتى تُفعَّل أو تُوقَف، ولماذا قد يغيب الإعداد، و...
ترتيب تحليل الأسماء على Windows ── hosts والذاكرة المؤقّتة لـ DNS وLLMNR/mDNS وDoH
أيّ من hosts أو الذاكرة المؤقّتة لـ DNS أو خادم DNS أو LLMNR/mDNS أجاب يقرّر لماذا تفشل بعض الحواسيب. تعلّم ترتيب تحليل الأسماء على Windo...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
الاستشارات التقنية ومراجعة التصميم
موضوع تصادم UUID لا يقتصر على فهم المواصفة، بل يمتدّ إلى مصدر الأرقام العشوائيّة وتشغيل الـ snapshot وقيود قاعدة البيانات وidempotency، لذا يستحقّ الترتيب ضمن مراجعة التصميم أو الاستشارة التقنيّة.
التحقيق في الأخطاء وتحليل السبب الجذري
في حوادث التكرار الفعليّة يلزم التمييز بين أن تكون المشكلة في UUID نفسه وأن تكون في التنفيذ أو التشغيل، ولذلك يهمّ ترتيب زوايا التحقيق وتصميم إجراءات منع التكرار.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- هل 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، وابقَ قادراً على تتبع أيّ مولّد وأيّ عقدة أخرجته.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.