مولّد UUID
ولّد معرّفات UUID — عشوائية من الإصدار 4 أو مرتَّبة زمنيًا من الإصدار 7 — فرادى أو دفعات.
122 بتًا عشوائيًا. ولا يكشف شيئًا عن وقت إنشائه.
جارٍ التوليد…
ما هو UUID ولماذا وُجد
الـ UUID — اختصار Universally Unique Identifier، ويُسمّى GUID في وثائق مايكروسوفت — قيمة من 128 بتًا تُكتب في صورة 32 رقمًا ست عشريًا ضمن التجميع المألوف 8-4-4-4-12. وغايته كلها أن يتيح لأنظمة منفصلة أن تسكّ معرّفات باستقلال تام، بلا أي تنسيق بينها، ومع ذلك تطمئن إلى أن النتائج لن تتصادم. وهذا ما يميّزه عن عمود ذاتي التزايد في قاعدة البيانات: فخادمان، وجهازان محمولان بلا اتصال في طائرة، ومهمة تعمل في الخلفية — كلها تستطيع إنشاء سجلات في اللحظة نفسها دون أن تستأذن أحدًا.
والتفرّد هنا احتمالي لا مضمون. فالإصدار 4 يترك 122 بتًا للعشوائية، وهو فضاء كبير بما يكفي لأن يبقى احتمال التكرار — حتى بعد توليد مليارات القيم — أدنى بكثير من احتمال أن يُفسد القرص البيانات في صمت. وعمليًا يمكنك أن تعامله كأنه فريد؛ فالرياضيات ليست الحلقة الأضعف.
الإصدار 4 في مقابل الإصدار 7 — القرار الذي يهمّ
الإصدار 4 عشوائي من أوله إلى آخره. لا يحمل أي معلومة: لا متى أُنشئ، ولا مَن أنشأه، ولا بأي ترتيب. وهذه إما أعظم مزاياه أو عيبه الجوهري، بحسب الموضع الذي تضعه فيه.
أما الإصدار 7، الذي قُنّن في RFC 9562 عام 2024، فيستبدل بالبتّات الـ48 الأولى طابعًا زمنيًا من Unix بالميلي ثانية، ويملأ الباقي بالعشوائية. ولأن الزمن يأتي أولًا وتُقرأ القيمة من اليسار إلى اليمين، فإن فرز معرّفات v7 كنصٍّ عادي يفرزها زمنيًا كذلك.
وليس هذا فرقًا شكليًا. فقواعد البيانات تحفظ المفاتيح الأساسية في فهرس B-tree مرتَّب. أدخِل مفاتيح v7 فيحطّ كل صف جديد عند الحافة اليمنى للشجرة، بجوار سابقه — فتبقى الصفحات التي تكتب فيها في الذاكرة وينمو الفهرس منظَّمًا. وأدخِل مفاتيح v4 فتحطّ كل كتابة في موضع عشوائي، فتلمس قاعدة البيانات صفحة مختلفة في كل مرة، وتهبط معدلات الإصابة في الذاكرة المؤقتة، ويتشظّى الفهرس. وفي جدول كبير مزدحم يكون الفارق في إنتاجية الإدخال كبيرًا، ولهذا اعتُمد v7 بهذه السرعة للمفاتيح الأساسية.
- اختر v7 للمفاتيح الأساسية في قواعد البيانات، ولمعرّفات الأحداث والسجلات، ولكل ما ستفرزه أو تستعلم عنه بمدى زمني لوقت الإنشاء.
- اختر v4 حين يظهر المعرّف في موضع غير موثوق ولا يجوز أن يتسرّب منه شيء — بما في ذلك أن سجلًا أُنشئ قبل آخر بلحظة.
- وكلاهما مناسب لمعرّف ربط أو مفتاح عدم تكرار أو اسم ملف، حيث لا يهمّ الترتيب.
والمقابل هو ذلك التسريب بعينه: فمعرّف v7 يخبر كل من يراه، بدقة المِلّي ثانية، متى أُنشئ. وهذا في معرّف صفٍّ غير ضارّ غالبًا بل مفيد كثيرًا. أما في رمز إعادة تعيين كلمة مرور أو رابط مشاركة عام فهو معلومة لم تقصد نشرها — وتلك أصلًا ينبغي ألّا تكون UUID، بل رموزًا عشوائية من مولّد أسرار مخصّص.
الترتيب داخل المِلّي ثانية الواحدة
دقيقة تُوقِع كثيرًا من تنفيذات v7: العتاد الحديث يولّد أكثر بكثير من معرّف واحد في المِلّي ثانية. فإذا تشارك قيمتان الطابع الزمني نفسه، تحدّد ترتيبَهما البتّاتُ العشوائية التي تليه — أي أنه يتحدّد بالمصادفة. ولّد خمسين في حلقة متلاحقة فستحصل على خمسين قيمة مرتَّبة تقريبًا فقط، وتفقد بالضبط الخاصية التي اخترت v7 من أجلها.
ويعالج RFC 9562 ذلك بعدّاد رتيب، وهذه الأداة تنفّذه: فالبتّات الاثنتا عشرة التالية مباشرةً للطابع الزمني تتصاعد داخل المِلّي ثانية الواحدة، فتكون الدفعة متزايدة تمامًا لا تقريبًا. وإن امتلأ العدّاد — أكثر من 4096 قيمة في المِلّي ثانية نفسها — استلف المولّد المِلّي ثانية التالية بدل أن يلتفّ ويُخرج قيمة تُرتَّب قبل سابقتها. ويعالج بالطريقة نفسها رجوع الساعة إلى الوراء، وهو ما يحدث مع تصحيحات NTP.
وبقراءة قيمة v7 من اليسار إلى اليمين، وليكن المثال 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b:
- 0190a1b2-c3d4 — الطابع الزمني لـ Unix بـ48 بتًا بالميلي ثانية. ولأنه يأتي أولًا، فترتيب النص هو ترتيب الزمن.
- 7 — رقم الإصدار، وهو ما يجعل هذه القيمة v7 لا v4.
- e5f — العدّاد الرتيب بـ12 بتًا، يتصاعد داخل المِلّي ثانية الواحدة.
- 8 — بتّات المتغيّر (variant)، ثبّتها RFC 9562 لكل UUID حديث (دائمًا 8 أو 9 أو a أو b).
- a9b-0c1d2e3f4a5b — البتّات الـ62 الباقية، عشوائية خالصة.
تخزين المعرّفات واستعمالها
الصورة القياسية هي الحروف الصغيرة مع الشُّرَط، وينصّ RFC 9562 على أن تُخرج المولّدات هذه الصورة بالضبط — ولهذا لا تقدّم هذه الأداة خيارات تنسيق. ومع ذلك ستصادف كتابات أخرى: بحروف كبيرة وبين قوسين معقوفين في أدوات مايكروسوفت، وبلا شُرَط حيث أراد أحدهم عمودًا أقصر. وكلها البتّات الـ128 نفسها، وينبغي أن تتجاهل المقارنات حالة الحروف.
وموضع الأهمية الحقيقي هو التخزين. فالـ UUID يشغل 16 بايت، لكن صورته النصية 36 حرفًا — فتخزينه نصًّا يضاعف المساحة في الصف بأكثر من الضعف، والأهم في كل فهرس يشمله. استعمل نوعًا أصليًا حيث يوجد:
uuid -- PostgreSQL: نوع أصلي من 16 بايت BINARY(16) -- MySQL: مضغوط؛ وCHAR(36) يهدر 20 بايت لكل صف uniqueidentifier -- SQL Server crypto.randomUUID() // جافاسكربت: v4 فقط، ويتطلب سياقًا آمنًا uuid.uuid4() / uuid7() # بايثون: v4 في المكتبة القياسية؛ وv7 عبر مكتبة
وتحذير أخير: الـ UUID يعرّف ولا يخوّل. ولأنه لا يمكن تخمينه، يُغري ذلك بمعاملة رابط غير معلن يحتوي عليه وكأنه خاص. لكن المعرّفات تتسرّب — عبر السجلات وتاريخ المتصفح وترويسات الإحالة ولقطات الشاشة — فكل ما يحتاج حمايةً فعلية يظل بحاجة إلى تحقّق حقيقي من الصلاحيات خلفه.
الأسئلة الشائعة
- أستعمل v4 أم v7؟
- استعمل v7 للمفاتيح الأساسية ولكل ما ستفرزه بوقت الإنشاء: فالطابع الزمني في المقدمة يُبقي الإدخالات متجمّعة في نهاية الفهرس بدل تبعثرها فيه. واستعمل v4 حين يجب ألّا يكشف المعرّف شيئًا البتة، ولا حتى وقت إنشائه.
- هل يتطابق معرّفان يومًا؟
- ممكن لكنه ضئيل إلى حدّ التلاشي. فللإصدار 4 مئة واثنان وعشرون بتًا عشوائيًا، حتى إن احتمال التصادم بعد توليد مليارات القيم يبقى أدنى بكثير من احتمال أن يُفسدها التخزين في صمت.
- وماذا عن الإصدارات 1 و3 و5؟
- الإصدار 1 يُرمّز طابعًا زمنيًا وعنوان MAC للجهاز، فيكشف هوية العتاد ولا يمكن إنتاجه في متصفح أصلًا. أما الإصداران 3 و5 فيشتقّان معرّفًا اشتقاقًا حتميًا من فضاء أسماء واسمٍ باستعمال MD5 أو SHA-1 — وهو مفيد حين يجب أن يعطي المدخل نفسه المعرّف نفسه دائمًا، لكنه عمل مختلف عن توليد معرّف جديد.
- هل الـ UUID آمن بما يكفي ليكون رمزًا سريًا؟
- معرّف v4 لا يمكن تخمينه، لكن معرّف v7 يُرمّز وقت إنشائه علانية، وليس أيٌّ منهما مصمَّمًا ليكون بيانات اعتماد. ولإعادة تعيين كلمات المرور ورموز الجلسات وروابط المشاركة، ولّد سرًّا عشوائيًا مخصّصًا وتحقّق من الصلاحيات في الخادم بدل الاتكال على صعوبة تخمين المعرّف.
- لماذا لا تخرج دفعة v7 لديّ مرتَّبة تمامًا في أدوات أخرى؟
- لأن كثيرًا من التنفيذات تتخطّى العدّاد الرتيب. فحين تتشارك عدة قيم المِلّي ثانية نفسها، يُترك ترتيبها للبتّات العشوائية التي تليها. وهذه الأداة تنفّذ عدّاد RFC 9562، فالدفعة المولَّدة هنا متزايدة تمامًا.
- كيف ينبغي أن أخزّن UUID في قاعدة بيانات؟
- في نوع أصلي من 16 بايت حيث يوجد — uuid في PostgreSQL، وuniqueidentifier في SQL Server، وBINARY(16) في MySQL. أما تخزين الصورة النصية ذات الـ36 حرفًا فيضاعف بأكثر من الضعف المساحة المستعملة في الصف وفي كل فهرس يشمل العمود.
- هل تُولَّد على خوادمكم؟
- لا. تُولَّد في متصفحك عبر crypto.getRandomValues، مصدر العشوائية التعموي في المنصّة — المصدر نفسه الذي يستعمله crypto.randomUUID. ولا تُرسَل أي قيمة إلى أي جهة، وإعادة تحميل الصفحة تُنتج مجموعة جديدة تمامًا.