مولّد UUID
ولّد معرّفات UUID فرادى أو خمسين دفعة واحدة: v4 عشوائي، أو v7 مرتَّب زمنيًا يحفظ ختمه الزمني ترتيب الدفعة ويصلح مفتاحًا أفضل لقاعدة بيانات.
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 يعرّف ولا يخوّل. ولأنه لا يمكن تخمينه، يُغري ذلك بمعاملة رابط غير معلن يحتوي عليه وكأنه خاص. لكن المعرّفات تتسرّب — عبر السجلات وتاريخ المتصفح وترويسات الإحالة ولقطات الشاشة — فكل ما يحتاج حمايةً فعلية يظل بحاجة إلى تحقّق حقيقي من الصلاحيات خلفه.
كيف تعرف أن المعرّف من الإصدار 4 أم 7
يصلك المعرّف عادةً بلا ملاحظة تقول من أين جاء، ولا تحتاج إلى واحدة: فالإصدار مكتوب داخل القيمة نفسها. وكلا الإصدارين 32 رقمًا ست عشريًا في التجميع نفسه 8-4-4-4-12، فالفرق ليس في الشكل — بل في رقمين مفردين في موضعين ثابتين، وكل ما عداهما يتبعهما.
- الرقم الأول من المجموعة الثالثة هو الإصدار. فـ 4 هناك تعني الإصدار 4، و7 تعني الإصدار 7، وليس لأي جزء آخر من القيمة رأي في ذلك.
- والرقم الأول من المجموعة الرابعة يحمل بتّات المتغيّر (variant)، وهو 8 أو 9 أو a أو b في الإصدارين معًا — فلا يخبرك أبدًا أيهما بين يديك. لكنه يخبرك أن القيمة تتبع RFC 9562 أصلًا؛ وأي شيء آخر في ذلك الموضع فهو بنية أقدم أو ليس UUID.
- وإن كان رقم الإصدار 7، فالأرقام الاثنا عشر الأولى هي وقت الإنشاء: عدٌّ بـ48 بتًا للمِلّي ثانية منذ مطلع 1970، مكتوب ست عشريًا.
- وإن كان 4، فليس بعده ما يُقرأ. فالإصدار 4 لا يرمّز زمنًا ولا جهازًا ولا ترتيبًا، وهذه هي الخاصية التي اخترته من أجلها.
خذ القيمة التي يفكّكها القسم أعلاه، 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b. تفتح مجموعتها الثالثة على 7، فهي إذن من الإصدار 7، وأرقامها الاثنا عشر الأولى هي 0190a1b2c3d4 — حوّل هذا العدد الست عشري إلى العشري، وسلّمه إلى محوّل طوابع زمن Unix، فتحصل على أصيل يوم في يوليو 2024. وإلى جانبها، يفتح 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d مجموعته الثالثة على 4، وأرقامه الاثنا عشر الأولى بيانات عشوائية لا تفكّ إلى شيء. ويتفق الاثنان على أمر واحد بالضبط: كلاهما يفتح مجموعته الرابعة على واحد من الأرقام الأربعة التي تتيحها بتّات المتغيّر.
هل الإصدار 7 مثل الإصدار 4 في مقاومة التصادم؟
سؤال في محلّه، فالإصدار 7 يحمل فعلًا عشوائية أقل. فالإصدار 4 ينفق 122 من بتّاته الـ128 على بيانات عشوائية. أما الإصدار 7 فينفق 48 على الطابع الزمني، و12 أخرى هنا على العدّاد الرتيب، فيبقى 62 بتًا عشوائيًا — أي ما يزيد قليلًا على النصف. وقراءةً كعدد مجرّد يبدو ذلك تراجعًا خطيرًا.
- داخل الدفعة الواحدة من هذه الأداة التكرار مستحيل لا مستبعد. فالقيم التي تتشارك المِلّي ثانية تأخذ قيم عدّاد مختلفة، والقيم التي لا تتشاركها تحمل طوابع زمنية مختلفة، فلا يمكن أن تتساوى قيمتان — وهذا حساب لا احتمال.
- وبين جهازين يولّدان في اللحظة نفسها، الاحتمال في أسوأ الأحوال هو مسألة أعياد الميلاد على 62 بتًا، وهي تتعادل عند الجذر التربيعي للفضاء تقريبًا: في حدود ملياري قيمة، كلها في المِلّي ثانية الواحدة نفسها.
- وبين مِلّي ثانية وأخرى لا يكون تصادم الإصدار 7 مستبعدًا فحسب بل مستحيلًا، لأن الأرقام الأولى نفسها تختلف.
فالمقارنة الصادقة إذن ليست 122 بتًا في مقابل 62. بل هي قرعة واحدة تُسحب مرارًا طوال عمر النظام في مقابل قرعة منفصلة أصغر بكثير تُقام داخل كل مِلّي ثانية وتُطرح حين تنتهي. والترتيب الثاني هو الأقوى، ويبقى سبب تفضيل الإصدار 4 هو ما يقوله قسم المقارنة — لا التصادم، بل أن الإصدار 7 يعلن متى أُنشئ.
نقل جدول قائم من الإصدار 4 إلى الإصدار 7
السؤال الذي يلي اختيار الإصدار 7 هو ما العمل بالصفوف الموجودة في الجدول أصلًا، والمريح أن العمود نفسه لا يحتاج إلى أي تغيير. فكلا الإصدارين هما البتّات الـ128 نفسها في الصورة النصية نفسها ذات الـ36 حرفًا، فعمود uuid في PostgreSQL أو BINARY(16) أو CHAR(36) يحمل الخليط دون أن يلحظ. تبدأ بتوليد الإصدار 7 للصفوف الجديدة وتقف عند ذلك؛ لا خطوة ترحيل ولا ملء رجعي.
- ما يصل فورًا: كل صفّ يُدرج من الآن يحمل طابعًا زمنيًا في المقدمة، فتحطّ المفاتيح الجديدة متجاورة عند طرف واحد من الفهرس بدل أن تتبعثر فيه. وهذه الفائدة تخصّ موضع الكتابات الجديدة، وهي حاضرة منذ أول إدراج.
- وما لا يصل أبدًا: الصفوف الموجودة تبقى بلا ترتيب إلى الأبد. فلا شيء يستطيع أن يضع وقت إنشاء في قيمة لم تُعطَه قط، وإعادة إصدار كل معرّف تعني إعادة كتابة كل مفتاح أجنبي يشير إليه — وهو عمل أكبر بكثير من تبديل مولّد، ونادرًا ما يستحقّ من أجل الفهرس وحده.
- وما ينبغي الانتباه له: فرز العمود يكفّ عن أن يعني شيئًا واحدًا. فعدُّ المِلّي ثانية عدد صغير في حقل من 48 بتًا، فتتجمّع مفاتيح الإصدار 7 في شريط ضيّق أسفل المدى بينما تنتشر مفاتيح الإصدار 4 على المدى كله، ويسقط أحدها بينها من حين لآخر.
وهذه النقطة الأخيرة هي التي تعضّ، لأن استعلامًا يفرز بالمفتاح يبدو صحيحًا على البيانات الجديدة ويخطئ في صمت على القديمة. فإن احتجت إلى ترتيب واحد للجدول كله، أضِف عمودًا لوقت الإنشاء وافرز به، ودع المعرّف يعود معرّفًا. والخليط على الأقل قابل للقراءة في أثناء ذلك: فرقم الإصدار جالس في كل قيمة، ويستطيع استعلام أن يفرّق بين الحقبتين بلا عمود ثانٍ حين يضطر.
الأسئلة الشائعة
- أستعمل 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. ولا تُرسَل أي قيمة إلى أي جهة، وإعادة تحميل الصفحة تُنتج مجموعة جديدة تمامًا.
- هل أعرف متى أُنشئ معرّف من الإصدار 7؟
- نعم، والقيمة وحدها تكفي. فأرقامها الست عشرية الاثنا عشر الأولى عدٌّ للمِلّي ثانية منذ مطلع 1970: حوّلها إلى العشري وسلّم الناتج إلى محوّل طوابع زمن Unix. أما الإصدار 4 فلا حقل كهذا فيه، فالأرقام الاثنا عشر نفسها هناك عشوائية ولا تفكّ إلى شيء.
- هل بتّات الإصدار 7 العشوائية أقل من بتّات الإصدار 4؟
- نعم — 62 هنا في مقابل 122 للإصدار 4، لأن الطابع الزمني والعدّاد يأخذان المساحة. لكن ذلك لا يجعل التصادم أرجح عمليًا: فقيمتان من الإصدار 7 لا تتصادمان إلا إن أُنشئتا في المِلّي ثانية نفسها، فهذه الـ62 بتًا تُنفق داخل مِلّي ثانية واحدة لا على امتداد عمر النظام كله، وداخل الدفعة الواحدة من هذه الأداة يجعل العدّاد التكرار مستحيلًا لا مستبعدًا فحسب.
- هل يجتمع الإصداران 4 و7 في العمود نفسه؟
- نعم. فهما البتّات الـ128 نفسها في الصورة النصية نفسها، فلا شيء في المخطّط يتغيّر ولا ترحيل — تولّد الإصدار 7 من الآن وتترك الصفوف القديمة كما هي. والشيء الوحيد الذي ينبغي الانتباه له هو الفرز: فالصفوف الجديدة مرتَّبة بينها بترتيب الإنشاء، لكن القديمة العشوائية مبعثرة حولها، ففرز الجدول بالمفتاح ليس ترتيبًا زمنيًا للجدول كله.
- ما الإصداران 6 و8 من UUID؟
- يعرّفهما RFC 9562 إلى جانب الإصدار 7. فالإصدار 6 هو الإصدار 1 وقد أُعيد ترتيب حقول طابعه الزمني ليُفرز زمنيًا، وهو موجّه للأنظمة الملتزمة بالإصدار 1 أصلًا — ويقول المعيار إن ما عداها ينبغي أن يستعمل الإصدار 7. أما الإصدار 8 فمساحة مفتوحة عمدًا لبنى مخصّصة، لا يثبت فيها إلا بتّات الإصدار والمتغيّر وتبقى الـ122 الباقية لك تحدّدها. وهذه الأداة لا تولّد أيًّا منهما.
أدوات ذات صلة
- مولّد الرموز العشوائية
ولّد مفاتيح وأسرارًا وعبارات مرور آمنة.
- مولّد بيانات وهمية
بيانات اختبار ببذرة، أرقام تحققها وحسابات IBAN فيها صحيحة فعلًا.
- مولّد رموز QR
كل قرار في الترميز ظاهر: الوضع والإصدار والمستوى والقناع.