← All posts

آلية اكتشاف الخدمات لدينا التقطت فشلها بنفسها ثم أوقفت نفسها

Authagonal·July 1, 2026
authdistributed-systemsclusteringleader-electionazureaks

كانت لدينا مجموعة من ثلاث نسخ متماثلة ظلّت تختلف مع نفسها باستمرار. كانت المهام الخلفية تُنفَّذ مرتين وثلاث مرات على التوالي. لم يكن الجواب في السجلّات؛ بل كان في شيفرتنا نحن. احتوى روتين اكتشاف الأقران على كتلة catch، وكان التعليق بداخلها يقول، بمعناه تقريبًا: "فشل multicast، تم تعطيل الاكتشاف." كانت آلية اكتشاف الخدمات لدينا تلتقط فشلها بنفسها وتُطفئ نفسها بصمت، وكانت تفعل ذلك تمامًا في بيئة الإنتاج منذ اليوم الأول.

هذه قصة سبب كون بروتوكول التجميع الصحيح على خادم مثبَّت في رفّ هو الأداة الخاطئة لحظة وضعه على منصة مُدارة، وسبب كون الحلّ هو حذفه بدلًا من إصلاحه.

ما الغرض الفعلي من المجموعة

يعمل خادم المصادقة بأكثر من نسخة متماثلة من أجل التوافر. النسخ مستقلة في معظمها: يمكن لأيٍّ منها التحقق من token أو فحص كلمة مرور. لكن بعض المهام يجب أن تُنفَّذ مرة واحدة، لا مرة لكل نسخة. مسحة الاحتفاظ بالبيانات التي تحذف السجلات منتهية الصلاحية. التمريرة التي تُطلق webhooks العملاء. أي شيء يمتدّ إلى الخارج وله أثر جانبي. لأجل ذلك تحتاج إلى أمرين يجب أن تتفق عليهما المجموعة: من هم الأعضاء، وأيّهم القائد الذي ينفّذ العمل المفرد (singleton).

كان جوابنا الأصلي هو الجواب الكلاسيكي: gossip. يثرثر كل node مع أقرانه، والعضوية خاصية ناشئة عمّن يمكن الوصول إليه، وتنتخب المجموعة قائدًا من ضمن المجموعة المتفق عليها. إنه تصميم بديع. لكنه يفترض أيضًا أن العُقد قادرة على إيجاد بعضها بعضًا، وكانت الطريقة التي تجدها بها الشيفرة القديمة هي multicast: الصياح على مقطع الشبكة المحلية ورؤية من يردّ.

لماذا تعطّل، في صمت

لا تحمل Kubernetes المُدارة multicast. وكذلك لا تحمله تقريبًا أيّ شبكة سحابية لا تبنيها بنفسك. كانت الصيحة تخرج ولا يعود شيء، في كل مرة. فـ"فشل" الاكتشاف، واشتعلت كتلة catch، وخلصت كل عقدة إلى أنها وحيدة في العالم.

عند نسخة واحدة يكون هذا غير ضار. عند ثلاث يكون انقسامًا في الدماغ (split-brain) بحكم التركيب: ثلاث عُقد، كلٌّ منها متيقّنة أنها العضو الوحيد، وكلٌّ منها تنتخب نفسها قائدًا، وكلٌّ منها تنفّذ العمل المفرد. تنفّذت مسحة الاحتفاظ على الثلاث جميعًا. وأطلقت تمريرة الـwebhooks webhook كل عميل ثلاث مرات. لم يُطلِق أيٌّ من ذلك خطأً، لأن كل شيء كان على ما يُرام من وجهة نظر كل عقدة وحيدة. لم يكن العيب انهيارًا؛ بل كانت ثلاثة برامج يتصرّف كلٌّ منها بصواب تامّ في عالم فهمته على نحو خاطئ.

كان الحلّ أن نتوقف عن اكتشاف الأقران تمامًا

ها هي إعادة التأطير التي جعلت الأمر كلّه ينهار إلى بضعة أسطر: كنّا نحاول أن نعيد بناء، عبر بروتوكول ثرثار، حقيقةً تخزّنها لنا المنصة أصلًا باتساق قويّ. نحن نعمل على سحابة تمنحنا مخزنًا قويّ الاتساق. لا تحتاج عملية انتخاب القائد إلى توافق بين الأقران ما دام يوجد أصلًا مكان واحد يمكن للجميع الاتفاق عليه.

وهكذا صارت الزعامة عبارة عن blob lease. blob واحد، lease واحد. مَن يحمل الـlease فهو القائد. للـlease مهلة، فالقائد الذي يموت يتوقّف عن التجديد ويتحرّر الـlease من تلقاء نفسه لمن يأخذه تاليًا. لا يوجد اكتشاف للأقران، ولا حساب نصاب (quorum)، ولا multicast، ولا كتلة catch تتربّص لتعطيله. أما التنسيق والأحداث فيسريان عبر جدول صغير يعمل بمثابة ناقل (bus) بين العُقد.

أما الخاصية التي لم أتوقّع أن أعشقها: يمكنك فتح Storage Explorer ورؤية عقل المجموعة. مَن يحمل الـlease الآن. وما المصطفّ على الناقل. لم تعد العضوية شيئًا تستنتجه من سلوك بروتوكول، بل صارت صفًّا يمكنك قراءته. حين يكون الشيء الذي يقرّر مَن يشغّل مهامك المدمِّرة قيمةً يمكنك النظر إليها، يتوقّف تصحيح الأخطاء عن أن يكون تنقيبًا أثريًّا.

الجزء الذي حذفنا فيه شيئًا بارعًا عن قصد

وبما أننا كنّا منهمكين هناك، حذفنا محدِّد معدّل موزَّعًا مبنيًّا على conflict-free replicated data type، وهو CRDT يدمج عدّادات كل node في عدد عالميّ دون تنسيق. كان أنيقًا حقًّا. لكنه كان أيضًا يحلّ مشكلةً كنّا قد نقلناها إلى موضع آخر. حدّ المعدّل العالميّ مكانه الطرف (edge)، حيث تصل الطلبات، لا أن يُعاد بناؤه داخل المجموعة ببنية بيانات تحتاج فقرة كاملة لشرحها. فأخرجناه.

حذف شيفرة تعمل يبدو خسارة إلى أن تحصي ما تتوقّف عن صيانته. أزلنا طبقة gossip، وافتراض multicast، وكتلة catch المعطِّلة لذاتها، والـCRDT، واستبدلنا كل ذلك بـlease وجدول. تراجع عدد الأسطر، وتراجع معه عدد الحالات التي يمكن للنظام أن يكون فيها.

الدرس، مُجرَّدًا

عضوية gossip وmulticast هما الأداتان الصحيحتان على العتاد الصِّرف (bare metal) أو على شبكة مسطّحة تتحكّم بها. وهما الأداتان الخاطئتان على منصة لا تحمل شبكتها الآليةَ ذاتها التي تعتمدان عليها، ونمط الفشل ليس انهيارًا صاخبًا؛ بل هو احتياطيٌّ صامت يبدو وكأنه يعمل. قبل أن تنقل نمطًا من أنماط الأنظمة الموزَّعة إلى بنية تحتية مُدارة، اسأل عمّا يفترضه بشأن الشبكة، واسأل عمّا تمنحك إياه المنصة أصلًا مجانًا. منصتنا كانت تشغّل أصلًا مخزنًا قويّ الاتساق. كان وضع lease في ذلك المخزن انتخابًا للقائد أبسط وأصحّ من البروتوكول الذي كنّا نحمله، وهو انتخاب يمكننا مراقبته.

إن كنت تشغّل خدمة مصادقة، فإن الأجزاء التي تحفظ مفاتيحك وتقرّر مَن يشغّل مهامك المدمِّرة ينبغي أن تكون أكثر الأشياء التي تملكها مللًا وأقدرها على الفحص. لقد جعلنا أجزاءنا مملّة. كان ذلك ترقية.