FlowingDev

شرح مُعرّفات UUID: المعرّف الفريد اللي ما يجي منه اثنين

الـ UUID هو رقم مكون من 128 بت يُستخدم لتعريف المعلومات بشكل فريد في أنظمة الكمبيوتر، مع ضمان شبه مؤكد بعدم تكراره أبدًا.

جرّب الأداة: مولد UUID

في جملة واحدة

الـ UUID هو رقم مكون من 128 بت، يعمل كرقم تسلسلي فريد لأي شي يخطر على بالك في عالم البرمجيات، مع احتمال شبه مستحيل إنه يتكرر مرتين.

المشكلة اللي يحلها

في بدايات عالم الكمبيوتر، كان تتبع الأشياء بسيط. أول مستخدم عندك ياخذ ID رقم 1، والثاني 2، وهكذا. نظام "الترقيم التلقائي المتزايد" هذا كان شغال تمام... طالما عندك قاعدة بيانات واحدة وسيرفر واحد بس هو اللي ينشئ كل السجلات.

بعدين جا الإنترنت. والأنظمة الموزعة (distributed systems). والـ microservices. والتطبيقات اللي تشتغل بدون انترنت (offline-first).

فجأة، صار عندك أجهزة كمبيوتر متعددة وكلها محتاجة تنشئ أشياء جديدة (مستخدمين، منشورات، منتجات، سجلات logs) في نفس الوقت، بدون ما يكلمون بعض. لو سيرفر في دبلن وسيرفر في طوكيو حاولوا ينشئون السجل "التالي"، كلهم راح ينشئون السجل رقم #5830. ولما قواعد بياناتهم تتزامن لاحقًا، راح يصير عندك تعارض (collision). أي سجل هو السجل الحقيقي رقم #5830؟ هنا تبدأ الفوضى.

هذه هي المشكلة الأساسية اللي تحلها الـ UUIDs (اختصار لـ Universally Unique Identifiers أو المعرّفات الفريدة عالميًا): إنشاء معرّفات فريدة بشكل لامركزي وغير منسق. مطور على لابتوبه في كوفي شوب يقدر ينشئ ID لعنصر جديد في قائمة مهامه، ويكون متأكد إحصائيًا إنه ما فيه أي شخص ثاني، على أي كمبيوتر ثاني، في تاريخ ومستقبل الكون كله، راح ينشئ نفس الـ ID بالضبط. هذا يسمح للأنظمة بإنشاء معرّفات فريدة بشكل مستقل، ويمهد الطريق للبرمجيات القوية والموزعة اللي نعتمد عليها اليوم.

كيف يشتغل من الداخل

في جوهره، الـ UUID هو مجرد رقم كبير: طوله 128 بت. هذا يعني 2¹²⁸ احتمال ممكن، واللي هو تقريبًا 340 undecillion (رقم 3 وبعده 37 صفر). عشان تتخيل ضخامة الرقم، لو أنشأت مليار UUID كل ثانية، راح تحتاج حوالي 10 مليار سنة عشان تستهلك كل الاحتمالات. فرصة تعارض اثنين UUIDs تم إنشاؤهم عشوائيًا هي فرصة ضئيلة بشكل فلكي.

تشريح الـ UUID

مع إنه رقم صحيح من 128 بت، لكننا ما نشوفه بهالشكل أبدًا. دايمًا يتم تمثيله كنص سداسي عشري (hexadecimal) من 32 حرف، مقسم لخمس مجموعات تفصل بينها شرطات.

الـ UUID النموذجي (إصدار 4) يبدو كذا: 123e4567-e89b-42d3-a456-426614174000

خلنا نفصّل هالتنسيق:

  • الهيكل: 8-4-4-4-12 (يمثل 32 حرف سداسي عشري، ليصبح المجموع 36 حرف مع الشرطات).
  • البيانات: كل حرف hex يمثل 4 بت (تسمى "nibble"). 32 حرف * 4 بت/حرف = 128 بت.
  • الأرقام السحرية: شايف رقم 4 في بداية المجموعة الثالثة (42d3)؟ هذا الرقم 4 مو عشوائي. هو يحدد إصدار الـ UUID (في هالحالة، الإصدار 4). الحرف الأول من المجموعة الرابعة (a456) له معنى خاص بعد؛ يحدد المتغير (variant)، عشان يضمن إنه يتبع الهيكل القياسي. لأغلب الـ UUIDs اللي بتشوفها، بيكون واحد من هالأحرف: 8، 9، A، أو B.

جولة على الإصدارات

الأداة هذي مخصصة للإصدار 4 (v4)، وهو النوع الأكثر شيوعًا. لكن فيه عدة إصدارات، وكل واحد له استراتيجية إنشاء مختلفة.

الإصدار طريقة الإنشاء حالة الاستخدام
v1 الطابع الزمني (Timestamp) + عنوان MAC لجهاز الكمبيوتر المنشئ. عندما تحتاج لترتيب زمني. (نادر الاستخدام الآن بسبب مخاوف الخصوصية المتعلقة بكشف عنوان الـ MAC).
v2 مثل v1، لكن مع إضافة معلومات POSIX UID/GID. نادر جدًا. هو مجرد إضفاء طابع رسمي على v1.
v3 هاش MD5 لـ "namespace" و "name". حتمي (Deterministic). إذا أعطيته نفس الـ namespace والـ name، ستحصل دائمًا على نفس الـ UUID. (أقل شيوعًا، لأن MD5 فيه نقاط ضعف).
v4 عشوائية بحتة. الخيار الافتراضي. عندما تحتاج فقط إلى ID فريد ولا تهتم بأي شيء آخر.
v5 هاش SHA-1 لـ "namespace" و "name". الخيار الحتمي الحديث. نفس فكرة v3، لكن مع دالة هاش أقوى.

إنشاء UUID من الإصدار 4

إنشاء UUID من الإصدار v4 بسيط من ناحية المفهوم:

  1. أنشئ 128 بت من البيانات العشوائية القوية من الناحية التشفيرية (cryptographically strong).
  2. عدّل على بضعة بتات محددة لضبط حقول "الإصدار" (version) و "المتغير" (variant)، كما هو مطلوب في المعيار.
  3. نسّق الـ 128 بت الناتجة كنص سداسي عشري مع شرطات.

هذا هو الكود الزائف لخطوة "التعديل":

// Assuming `bits` is an array of 128 random bits (0s and 1s)

// Set the version to 4 (0100)
bits[48] = 0;
bits[49] = 1;
bits[50] = 0;
bits[51] = 0;

// Set the variant to '10x'
bits[64] = 1;
bits[65] = 0;

في الواقع، أغلب لغات البرمجة توفر دالة من سطر واحد مثل crypto.randomUUID() عشان تسوي كل هذا لك، وتضمن إنها تتم بشكل صحيح وآمن. الزبدة هي أن الـ UUID من الإصدار v4 هو مجرد 122 بت من العشوائية البحتة، مغلفة بـ 6 بت من البيانات الوصفية (metadata).

قصص من الواقع

كابوس دمج قواعد البيانات

شركتين ناشئتين، "Acme" و "WidgetCorp"، قرروا يندمجون. كل وحدة عندها منتجات ناجحة، ولكل منها قاعدة بياناتها الخاصة للمستخدمين، والمنتجات، والطلبات. في أول اجتماع للتكامل، سأل مطور مبتدئ: "كيف راح ندمج جداول المستخدمين؟ المستخدم اللي عندي بالـ ID رقم 101 هو 'Alice'، لكن المستخدم اللي عندهم بنفس الـ ID 101 هو 'Bob'." ساد الصمت في الغرفة. كل جدول في كلتا قاعدتي البيانات كان يستخدم أرقام ID صحيحة بسيطة ومتزايدة تلقائيًا. دمجها راح يكون مهمة ضخمة تتطلب إعادة كتابة كل المفاتيح الأجنبية (foreign keys)، ومطابقة كل سجل، والدعاء بأنه ما يفوتهم شيء. هذا الشيء أخّر عملية الدمج شهور.

الدرس: لو كانوا استخدموا UUIDs من البداية، كان الدمج بيكون تافه. المستخدم f47ac10b-58cc-4372-a567-0e02b2c3d479 من شركة Acme كان ممكن يتعايش بسلام مع المستخدم 9c68a520-2a83-43a3-b45d-4c86518a28cc من شركة WidgetCorp. لا تعارضات، لا كوابيس. الـ UUIDs أساسية للأنظمة اللي ممكن تحتاج في يوم من الأيام تتفاعل أو تندمج.

سلة التسوق السريعة

كان فيه مطور يبني ميزة "إضافة سريعة" لمتجر إلكتروني. لما المستخدم يضغط "أضف للسلة" على قائمة المنتجات، كان يظهر مؤشر تحميل (spinner) لمدة 1-2 ثانية بينما التطبيق ينتظر السيرفر ينشئ عنصر السلة ويعيد الـ ID الجديد حقه. الإحساس كان بطيء. جات المطور فكرة عبقرية: ماذا لو التطبيق ما انتظر؟ غير الكود بحيث لما المستخدم يضغط، المتصفح فورًا ينشئ UUID v4 لعنصر السلة الجديد، ويضيفه للحالة المحلية (local state)، ويحدّث واجهة المستخدم على طول. التطبيق صار يحسسك إنه صاروخ. في الخلفية، كان يرسل الطلب للسيرفر، ويقوله "لو سمحت، أنشئ عنصر سلة بهذا الـ UUID المحدد." لو فشل الاتصال بالشبكة، التطبيق يقدر ببساطة يحاول مرة ثانية لاحقًا، باستخدام نفس الـ UUID عشان يتجنب إنشاء عناصر مكررة.

الدرس: إنشاء الـ UUID من طرف العميل (client-side) يمكّن ما يسمى بـ "الواجهات المتفائلة" (Optimistic UI)، حيث الواجهة تتحدث فورًا، بافتراض أن العملية ستنجح. هذا يخلق تجربة مستخدم أسرع وأكثر استجابة بكثير ويجعل التعامل مع سيناريوهات عدم الاتصال بالإنترنت أسهل بكثير.

قصة المحقق في عالم الـ Microservices

عميل بلّغ عن خطأ: طلبه فشل، لكن تم خصم المبلغ من بطاقته. النظام كان عبارة عن شبكة معقدة من الـ microservices: Auth، Gateway، Orders، Payments، Shipping. الطلب الواحد كان ممكن يتنقل بين خمس أو ست من هذي الخدمات. العثور على نقطة الفشل بالضبط كان زي البحث عن إبرة في كومة قش من مليون سجل log في الدقيقة. المهندس الرئيسي أصدر قرار تغيير: كل طلب وارد إلى الـ Gateway راح يتم إعطاؤه UUID، يسمى "Correlation ID" (معرّف الارتباط). هذا الـ ID راح يتم تمريره لكل microservice يتعامل مع الطلب، وكل رسالة log راح تحتويه. المرة الجاية اللي حصل فيها خطأ، فريق الدعم ببساطة بحث في نظام الـ logging عن هذا الـ UUID الوحيد. على الفور، حصلوا على قصة كاملة ومرتبة زمنيًا لرحلة الطلب عبر النظام بأكمله، وحددوا بالضبط الخدمة اللي فشلت.

الدرس: الـ UUIDs لا تقدر بثمن كـ correlation IDs لتتبع الطلبات وتصحيح الأخطاء في الأنظمة الموزعة والمعتمدة على الـ microservices.

أخطاء وفخاخ شائعة

  • استخدام UUIDs كمفاتيح أساسية (primary keys) لقواعد البيانات... بإهمال. مع إنها ممتازة للفرادة، لكن الـ UUIDs كبيرة الحجم (16 بايت مقابل 4 أو 8 للرقم الصحيح) وعشوائية. العشوائية ممكن تكون سيئة جدًا لأداء فهارس قواعد البيانات (database index)، وتؤدي إلى تجزئة (fragmentation) وكتابة أبطأ لأن قاعدة البيانات تعاني لإدراج صفوف جديدة في منتصف شجرة الفهرس B-tree. قواعد البيانات الحديثة وإصدارات UUID الأحدث (مثل v7 المقترح، وهو مرتب زمنيًا) يمكن أن تخفف من هذا، لكنها مقايضة حرجة يجب أن تكون على دراية بها.
  • افتراض أن كل الـ UUIDs عشوائية. قد يرى مطور UUID في نظام قديم ويبني منطقًا برمجيًا بافتراض أنه غير قابل للتنبؤ. قد لا يدرك أنه UUID من الإصدار v1، والذي يحتوي على طابع زمني وعنوان MAC للجهاز الذي أنشأه، مما قد يؤدي إلى تسريب معلومات حساسة.
  • التعامل معه كأي نص عادي (string). بعض المطورين قد يعتقدون أن أي نص فريد هو "UUID". قد يستخدمون "product-123" أو ينشئون ID باستخدام مولد أرقام عشوائية ضعيف. الـ UUIDs الحقيقية تلتزم بتنسيق صارم، وبالنسبة للإصدار v4، يجب أن يتم إنشاؤها بمصدر عشوائية آمن من الناحية التشفيرية لضمان الفرادة.
  • استخدام الإصدار الخاطئ للمهمة الخاطئة. خطأ شائع هو استخدام UUID v4 (عشوائي) عندما تحتاج إلى واحد حتمي (deterministic). على سبيل المثال، إذا كنت بحاجة إلى إنشاء ID فريد لملف بناءً على محتواه، يجب عليك استخدام UUID v5 مع هاش (hash) الملف كـ "name". هذا يضمن أنك إذا واجهت نفس الملف مرة أخرى، فستنشئ نفس الـ UUID بالضبط، مما يسهل عملية إزالة التكرارات.

ليش لازم يكون على رادارك

يجب أن تفكر في استخدام مولد UUID كلما كنت في موقف حيث:

  • تحتاج إلى إنشاء معرّف فريد، لكن لا يمكنك الاعتماد على سلطة مركزية (مثل تسلسل قاعدة بيانات واحد).
  • أنت تبني نظامًا موزعًا، أو microservice، أو أي تطبيق حيث تحتاج مثيلات متعددة إلى إنشاء بيانات بشكل مستقل.
  • تريد إنشاء IDs فريدة من طرف العميل (في متصفح أو تطبيق جوال) لتحديثات واجهة المستخدم المتفائلة أو للقدرات التي تعمل بدون اتصال بالإنترنت.
  • تحتاج إلى إنشاء correlation IDs لتتبع الطلبات أثناء تدفقها عبر أنظمة متعددة.
  • أنت تختار مفتاحًا أساسيًا (primary key) لجدول قاعدة بيانات وتعطي الأولوية للفرادة العالمية على أداء الإدراج الخام (وقد فكرت في المقايضات).

في تطوير البرمجيات الحديثة، هذه السيناريوهات هي القاعدة وليست الاستثناء. معرفة متى وكيف تستخدم الـ UUIDs هي مهارة أساسية.

تعمق أكثر

  • RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace - المواصفات التقنية الأصلية التي تحدد UUIDs، بما في ذلك الإصدارات من 1 إلى 5.
  • Wikipedia: Universally unique identifier - نظرة عامة ممتازة وشاملة على التاريخ والمعايير والهيكل والإصدارات المختلفة.
  • MDN Web Docs: Crypto.randomUUID() - توثيق واجهة برمجة التطبيقات (API) الحديثة في المتصفح لإنشاء UUIDs v4، وهو مثال عملي على استخدام UUIDs.
  • New UUID Formats (Draft RFC) - العمل الجاري لتحديد إصدارات UUID جديدة (مثل v6 و v7 و v8) التي تعالج بعض أوجه القصور في الإصدارات الأصلية، مثل توفير IDs مرتبة زمنيًا وقابلة للفرز.

انتهينا من النظرية. حان وقت التطبيق — 100% في متصفحك.

جرّب الأداة: مولد UUID