منهجية التصميم
صمّم مولّد معرّفات فريدة
آلاف الأجهزة، كل منها يسكّ معرّفات بشكل مستقل، ولا يجوز أن يتصادم أي معرّفين — في أي مكان على الأرض، أبداً. لا تستطيع الاتصال بسلطة مركزية؛ لا وقت لذلك. ومع ذلك، هذه المشكلة محلولة، في بيئات الإنتاج، بمحاسبة دقيقة على 64 بت.
- فريد عالمياً: لا تصادمات بين الأجهزة، أبداً — ولا منسّق مركزي يفرض ذلك.
- قابل للترتيب الزمني تقريباً: المعرّفات الأحدث أكبر عددياً من الأقدم، لتبقى الفهارس والتصفّح رخيصة.
- مضغوط وسريع: عدد صحيح من 64 بت، وحوالي 10,000 معرّف في الثانية دون أي عناء.
الـUUID هو الجواب السهل: يُولَّد في أي مكان، بلا تنسيق، وانتهى. لكنه يكلّف 128 بت — ضعف حجم مفاتيحك وفهارس أثقل — والقيم العشوائية تبعثر الإدراجات عبر الـB-tree، فتجزّئ الصفحات وتقتل قابلية الترتيب. أما الـauto-increment في قاعدة بيانات واحدة فمضغوط ومرتّب تماماً، لكن كل إدراج يمر عبر كاتب واحد: عنق زجاجة، ونقطة فشل وحيدة. ويخفّف ذلك خادم تذاكر (ticket server) — قاعدة بيانات واحدة توزّع نطاقات على أجهزة كثيرة — لكن موزّع النطاقات يبقى صندوقاً واحداً موته يوقف العالم. كل خيار مركزي يشتري لك البساطة ويدفع ثمنها من التوافرية.
قسّم الـ64 بت إلى مقاطع، وكل مقطع يشتري لك خاصية واحدة. 41 بت للطابع الزمني — بالمللي ثانية منذ epoch مخصوص — تعطيك نحو 69 عاماً من العمر وتضع الزمن في البتات العليا، فتترتّب المعرّفات زمنياً مجاناً. 5 بت لمركز البيانات و5 للجهاز تعطيك 1,024 مولّداً مستقلاً بلا أي تنسيق. 12 بت للتسلسل — عدّاد لكل مللي ثانية على كل جهاز — تعطيك 4,096 معرّفاً في المللي ثانية لكل جهاز، أي نحو أربعة ملايين في الثانية، مقابل حاجتك لعشرة آلاف. الفرادة تكفّ عن كونها شيئاً تتحقق منه؛ لتصبح شيئاً تضمنه البنية نفسها.
قد يعيد NTP ساعة الجهاز إلى الوراء — ثانية كبيسة، أو تصحيح، أو إعادة تشغيل. الآن يرجع طابعك الزمني إلى أرض سبق أن أصدرت فيها معرّفات، فتتسرّب التكرارات. أمامك ثلاث حركات: انتظر حتى تلحق الساعة بآخر طابع استخدمته، أو ارفض توليد معرّفات حتى يتقدّم الزمن مجدداً، أو واصل استخدام آخر طابع زمني وتقدّم في التسلسل فقط. معظم أنظمة الإنتاج ترفض وتطلق تنبيهاً — تعطّل وجيز في مولّد واحد خير من تكرار صامت في مفتاحك الأساسي. قل أيهما تختار، ولماذا.
الفكرة العميقة: أنت لم تصنع الفرادة — بل قسّمتها. الزمن والمكان والتسلسل ينحتون فضاء البتات بإحكام لدرجة أن جهازين يستحيل فيزيائياً أن ينتجا الرقم نفسه. التنسيق استُبدل بالهندسة.
لماذا يحتل الطابع الزمني البتات العليا في معرّف snowflake — وما الذي يفسد بالضبط عندما ترجع ساعة جهاز إلى الوراء؟ (لأن مقارنة الأعداد الصحيحة تقرأ البتات العليا أولاً، فالطابع المتصدّر يجعل المعرّفات الأحدث تترتّب أكبر مجاناً؛ أما رجوع الساعة فيعيد إدخالك إلى قيم زمنية استُخدمت سابقاً بمعرّف الجهاز نفسه، فيتكرر عدّاد التسلسل وقد تصدر مفتاحاً أساسياً مكرراً — ومن هنا استراتيجيات الانتظار أو الرفض أو إعادة استخدام آخر طابع.)
اختر UUID حين تتفوق السهولة على الحجم والترتيب؛ وعدّاداً حين يكفيك كاتب واحد بصدق؛ وsnowflake حين تحتاج الخواص الثلاث معاً. وأياً كان ما تشحنه، قرّر سياسة رجوع الساعة قبل أن يقررها NTP عنك.
ابحث عن المكان الذي يولّد نظامك فيه المعرّفات اليوم — افتح الكود، لا تخمّن. إن كان UUID عشوائياً، فافحص ما إن كان فهرس مفتاحك الأساسي يتجزّأ تحت الإدراجات المبعثرة. ثم ارسم توزيع بتات snowflake على مقياسك: كم جهازاً، وكم معرّفاً في المللي ثانية، وكم سنة من الطابع الزمني تحتاج. عشر دقائق وصفحة واحدة، ولن تجيب عن سؤال المقابلة هذا بالطريقة القديمة مرة أخرى.
منهجية التصميم