مفاهيم الفشل الثلاثة التي تستحق المعرفة
مرحباً
تنهار الأنظمة الموزعة وفق أنماط محددة. بمجرد أن تتعلم هذه الأنماط، يصبح كل تقرير ما بعد الحادثة تمريناً على التعرف على النمط بدلاً من أن يكون لغزاً.
تغطي ثلاثة مفاهيم معظم ما يهم في تحليل الأعطال في بيئة الإنتاج:
نقطة فشل مفردة (SPOF): مكوّن يؤدي فشله إلى انهيار نظام أكبر. غالباً ما تكون مخفية: خادم DNS الذي يعتمد عليه الجميع؛ الشهادة (Certificate) التي تتجدد ضدها جميع الخدمات؛ خادم قاعدة البيانات الرئيسي الوحيد.
الفشل المتسلسل: فشل مكوّن واحد يُطلق فشل مكوّن آخر، والذي يُطلق بدوره فشل مكوّن ثالث. قاعدة بيانات بطيئة تسبب انتهاء المهلة الزمنية (Timeouts) في طبقة واجهة برمجة التطبيقات (API)، مما يؤدي إلى إعادة المحاولة، مما يُحمّل قاعدة البيانات بشكل أكبر، مما يسبب مزيداً من انتهاء المهلة الزمنية. ينتشر الانفجار.
نطاق الانفجار: مقدار الجزء الذي ينهار من النظام عندما يفشل جزء واحد. الاختيارات المعمارية إما تحدّ من هذا النطاق أو تتركه غير محدود. نقطة الفشل المفردة (SPOF) لها نطاق انفجار غير محدود. الخدمة المعزولة (Bulkheaded) لها نطاق محدود.
بحلول نهاية هذا الدرس، ستكون قادراً على:
- تحديد نقاط الفشل المفردة (SPOFs) في البنية التحتية من خلال الفحص البصري
- التعرف على أنماط الفشل المتسلسل: القطيع الرعدي (Thundering herd)، عاصفة إعادة المحاولة (Retry storm)، طابور الموت (Queue of death)
- قراءة خط زمني حقيقي وفصل المحفّز (Trigger) عن العيب الكامن (Latent defect) الذي كشفه المحفّز
- كتابة بنود إجراء خالية من اللوم (Blameless) تستهدف الأنظمة بدلاً من الأشخاص، وتغطي الجوانب التالية: الوقاية / الكشف / التعافي
- الاستدلال حول العزل (Bulkheads) وقواطع الدائرة (Circuit breakers) كأدوات لتحديد نطاق الانفجار
اكتشف نقطة الفشل المفردة
فحص البنية المعمارية متعددة الطبقات
فكّر في بنية ويب صغيرة:
- نظام أسماء النطاقات (DNS): api.example.com -> عنوان IP لمُخدِّم أسماء واحد 203.0.113.10 مستضاف لدى مزود DNS واحد
- شبكة توصيل المحتوى (CDN): مزود CDN واحد أمام api.example.com
- نقطة الدخول (Ingress): خادمان عكسيان (Reverse Proxy) خلف مُوزِّع الأحمال
- الخلفية (Backend): ستة نسخ من واجهة برمجة التطبيقات (API) في منطقتين للتوفر (ثلاثة في كل منطقة)
- قاعدة البيانات: قاعدة بيانات أساسية واحدة + نسخة قراءة واحدة، في نفس منطقة التوفر
- ذاكرة مؤقتة (Cache): عناقيد Redis، موزعة على ثلاثة عقد عبر نفس منطقتي التوفر (Availability Zones)
السؤال: ما هي المكونات التي تمثل نقاط فشل مفردة (SPOFs)؟ تلميح: نقاط الفشل المفردة ليست دائماً من نوع "آلة واحدة" الواضح. عناقيد تتكون من ثلاث آلات تقع جميعها في منطقة توفر واحدة تُعد نقطة فشل مفردة لفشل تلك المنطقة.
ثلاثة أنماط كلاسيكية للفشل المتسلسل
تنتقل الأعطال عبر الاعتماديات
النمط 1: القطيع الرعدي (Thundering herd). يفشل مورد مشترك (ذاكرة مؤقتة، قفل، قاعدة بيانات) أو يعيد تشغيل نفسه. يعيد كل عميل كان يعتمد عليه المحاولة في نفس الوقت. يغرق هذا الطوفان ما يعاد تشغيله؛ تتراكم عمليات إعادة المحاولة أسرع مما تستطيع عملية الاستيعاب أثناء التعافي؛ ولا يكتمل التعافي أبدًا.
النمط 2: عاصفة إعادة المحاولة (Retry storm). يتباطأ خدمة سفلية (downstream). بدلاً من الفشل، تعيد الخدمات العلوية (upstream) المحاولة. تضاعف عمليات إعادة المحاولة الحمل الأصلي. يتباطأ الخدمة البطيء أكثر، مما يحفز المزيد من عمليات إعادة المحاولة. في النهاية، يتجاوز الحمل حتى نسخة صحية من الخدمة.
النمط 3: طابور الموت (Queue of death). يتلقى طابور معالجة لا يملك ضغطًا عكسيًا (backpressure) أسرع مما يعالجه. ينمو الطابور بلا حدود. تستنفد الذاكرة؛ ينهار المستهلك (consumer)؛ يعيد التشغيل؛ يجد طابورًا أكبر لا يزال؛ وينهار مرة أخرى.
الخيط المشترك: اضطراب أولي صغير يحفز حلقة تغذية راجعة موجبة. يستجيب النظام نفسه بتضخيم العطل بدلاً من تخميدِه.
آليات التخميد
التراجع الأسي مع التذبذب (Jitter). تنتظر العملاء الذين يعيدون المحاولة فترة أطول في كل مرة، مع إزاحة عشوائية. يمنع موجات إعادة المحاولة المتزامنة.
قاطع الدائرة (Circuit Breaker). يتتبع المستدعي (Caller) معدل الفشل في الخدمات السفلية (Downstream). عند تجاوز حد معين، يتوقف المستدعي عن الاستدعاء لفترة تبريد، ويفشل طلباته الخاصة فوراً بدلاً من ذلك. يمنع العمل الضائع، ويسمح للخدمة السفلية بالتعافي.
الجدار العازل (Bulkhead). عزل الموارد لكل اعتمادية. حوض اتصالات (Connection Pool) A لقاعدة البيانات، وحوض اتصالات منفصل B للذاكرة المؤقتة (Cache). لا يمكن لقاعدة بيانات بطيئة أن تستنفد جميع الاتصالات؛ فتستمر استدعاءات الذاكرة المؤقتة.
تخفيف الحمل (Load Shedding). عند حدوث الحمل الزائد، يتم إسقاط الطلبات عند الحافة بدلاً من قبولها والفشل ببطء. استجابة 429 خلال 1 مللي ثانية أفضل من استجابة 500 خلال 30 ثانية.
الضغط الخلفي (Backpressure). إبطاء المنتجين عندما لا يستطيع المستهلكون مواكبة الإيقاع. تصبح الطوابير محدودة؛ ويحظر المرسلون؛ ويشعر المصدر الأصلي للعمل بالاحتكاك.
تشخيص السلسلة المتسلسلة
تنهار طبقة واجهة برمجة التطبيقات (API) لفريق أثناء تبديل قاعدة البيانات الروتيني. الجدول الزمني:
- 14:00:00 — يقوم المشغّل بترقية قاعدة البيانات الاحتياطية. مدة التوقف المتوقعة: ~10 ثوانٍ.
- 14:00:08 — قاعدة البيانات الأساسية غير متاحة. تبدأ طلبات طبقة واجهة برمجة التطبيقات (API) بالفشل مع أخطاء في اتصال قاعدة البيانات.
- 14:00:08 — تعيد طبقة واجهة برمجة التطبيقات (API) المحاولة (الإعداد الافتراضي: 5 محاولات، بدون تأخير تصاعدي، بفارق 100 مللي ثانية).
- 14:00:11 — تم ترقية قاعدة البيانات الاحتياطية، وبدأت في قبول الاتصالات الجديدة.
- 14:00:11 — تفتح طبقة واجهة برمجة التطبيقات (API) آلاف اتصالات قاعدة البيانات الجديدة في نفس الوقت (كل نسخة × كل طلب متزامن × كل محاولة إعادة).
- 14:00:13 — استنفدت مجموعة اتصالات قاعدة البيانات الأساسية الجديدة؛ تم رفض الاتصالات الجديدة.
- 14:00:13-14:05:00 — تستنفد نسخ طبقة واجهة برمجة التطبيقات (API) مجموعات الاتصالات، وتطرح استثناءات، وتنهار، وتعيد التشغيل، وتتكرر العملية.
- 14:05:00 — يوقف المشغّل يدويًا حركة مرور طبقة واجهة برمجة التطبيقات (API)؛ تستقر قاعدة البيانات.
- 14:10:00 — اكتمل استعادة الحركة المرورية تدريجياً. إجمالي فترة الانقطاع: ~10 دقائق (مقابل ~10 ثوانٍ متوقعة).
DNS SERVFAIL: عيبان مركّبان
تحليل حادثة على شكل واقعي
ما يلي هو نسخة معقّمة من حادثة حقيقية. تم تغيير أسماء الموردين، وتم إخفاء عناوين IP؛ لكن الشكل، والجدول الزمني، والدروس المستفادة حقيقية.
الملخص
أعاد الموقع example.com استجابة SERVFAIL من جميع محللات DNS العامة لمدة تقارب 3-4 ساعات. لم تتأثر أي من المناطق الـ46 الأخرى على نفس خادم DNS الرئيسي. السبب الجذري: عيبان مركّبان.
1. أضاف المورّد أ (مزوّد DNS ثانوي) عنوان IP مزامنة داخلي جديدًا لم يكن مدرجًا في قائمة السماح allow-axfr-ips الخاصة بالخادم الرئيسي.
2. كانت منطقة example.com تحتوي على تعارض CNAME قديم منذ سنوات يخالف معايير RFC (حيث كانت demo.example.com تحتوي على سجلات CNAME و MX/TXT في نفس التسمية)، مما أدى إلى رفض المورّد أ للمنطقة عند تنفيذ AXFR جديد.
الجدول الزمني (بتوقيت UTC)
- حوالي 15:00 — يضيف المورّد A عنوان IP المزامنة الجديد 198.51.100.42 إلى بنيته التحتية
- 15:02 — يظهر أول AXFR-out denied لـ 198.51.100.42 في سجلات DNS الأساسية (لا يوجد تنبيه لهذا الإشارة)
- حوالي 18:00 — تم الوصول إلى نافذة انتهاء صلاحية SOA؛ يزيل المورّد A منطقة example.com من ذاكرة التخزين المؤقت
- حوالي 18:30 — تم رصد SERVFAIL من الخارج
- حوالي 19:45 — تم تحديد السبب الجذري
- 20:00 — تمت إضافة 198.51.100.42 إلى allow-axfr-ips؛ تم إعادة تشغيل الخادم الأساسي
- 20:05 — تم إرسال NOTIFY؛ تم بدء AXFR؛ المنطقة لا تزال SERVFAIL (تعارض CNAME)
- 20:07 — يكشف check-zone عن خطأ واحد: تعارض CNAME على demo.example.com
- 20:09 — تم استبدال سجل CNAME بسجل A؛ فحص المنطقة نظيف (0 أخطاء)
- 20:10 — تم إرسال إشعار NOTIFY؛ اكتمل نقل AXFR؛ يبدأ المورّد A في تقديم المنطقة
- 20:11 — يعيد أمر dig @8.8.8.8 example.com A عنوان IP الصحيح — تم الحل
لماذا example.com فقط؟
تشارك جميع المناطق الـ 47 نفس الخادم الأساسي لـ DNS. أثّر حظر عنوان IP الخاص بـ AXFR على جميع المناطق. لكن example.com فقط كان لديها تعارض CNAME، و example.com فقط هي التي احتاجت إلى نقل AXFR جديد في اللحظة التي فُرض فيها الحظر. كانت المناطق الأخرى قد حدّثت بياناتها بالفعل قبل الحظر أو لم تكن بحاجة إلى التحديث بعد.
عيب كامن
كان تعارض CNAME في demo.example.com موجوداً منذ سنوات. كان يعمل لأن الخادم الأساسي كان يقدم المنطقة من قاعدته البيانات (متسامحاً مع انتهاكات RFC) وكان المورّد A يقدم من بيانات مخزّنة قديمة من قبل إدخال الانتهاك. عندما تخلّى المورّد A عن ذاكرته المؤقتة واحتاج إلى بيانات جديدة، ظهر الانتهاك.
المحفّز
أضاف المورّد أ بشكلٍ صامت عنوان IP جديدًا للمزامنة. لم يكن هذا العنوان مدرجًا في قائمة السماح (Allowlist) الخاصة بالخادم الرئيسي. تم رفض طلب AXFR. بعد ثلاث ساعات (عند انتهاء صلاحية SOA)، قام المورّد أ بإسقاط المنطقة (Zone). ظهر الخلل الكامن عندما حاول النظام التعافي.
كتابة عناصر إجراء خالية من اللوم
خالية من اللوم: استهداف الأنظمة، وليس الأشخاص
يُسمّي عنصر الإجراء الخالي من اللوم شيئًا يجب أن تفعله المنظومة بشكل مختلف، وليس شيئًا يجب أن يفعله شخص ما بشكل مختلف. عبارة "درّب المشغّل" هي عبارة ملوّمة. أما عبارة "أضف فحصًا آليًا يلتقط هذا الخطأ قبل النشر" فهي خالية من اللوم.
تتجمع عناصر الإجراء الخالية من اللوم الجيدة في ثلاثة أبعاد:
- الوقاية: جعل الأمر السيئ أصعب أو مستحيل الحدوث
- الاكتشاف: ملاحظة الأمر في وقت أبكر إذا حدث
- التعافي: الحد من حجم الضرر عند حدوث الأمر
يجب أن يحدد كل عنصر (1) التغيير المحدد في النظام، و (2) الفريق المسؤول، و (3) البُعد الذي يخدمه.
أقسام تغرق دون غرق السفينة
مستعارة من الهندسة البحرية
تحمل السفن جدراناً فاصلة مقاومة للماء: جدران عمودية تقسم هيكل السفينة إلى أقسام. يمكن لغرفة واحدة أن تغرق دون أن تغرق السفينة؛ ويمكن لغرفة أخرى أن تفشل دون التأثير على البقية.
تستعير الأنظمة الموزعة نفس الكلمة ونفس الفكرة.
نمط الجدران الفاصلة: عزل الموارد لكل اعتمادية. خدمة تستدعي ثلاث واجهات برمجية (APIs) هابئة تستخدم ثلاث مجموعات اتصالات منفصلة، وثلاث مجموعات خيوط منفصلة، وثلاثة ميزانيات إعادة محاولة منفصلة. لا يمكن لواجهة برمجية هابئة بطيئة أو معطلة أن تستهلك الموارد المخصصة للاعتماديتين الأخريين.
بدون جدران فاصلة: تستنفد اعتمادية بطيئة واحدة مجموعة الخيوط المشتركة؛ تتوقف استدعاءات الاعتماديات الأخرى بانتظار الخيوط؛ تصبح الخدمة بأكملها غير مستجيبة.
مع جدران فاصلة: تستنفد اعتمادية بطيئة واحدة مجموعتها الخاصة؛ تفشل استدعاءاتها بسرعة؛ تستمر استدعاءات الاعتماديات الأخرى بشكل طبيعي؛ يبقى نطاق التأثير محصوراً بالاعتمادية المعطلة.
قواطع الدائرة
نمط قاطع الدائرة (Circuit breaker pattern): غلاف ذو حالة (stateful) حول اعتمادية سفلية (downstream dependency) يتتبع معدل الفشل. ثلاث حالات:
- مغلق (طبيعي): تمر الاستدعاءات عبره. يتم عدّ حالات الفشل.
- مفتوح (مُفعّل): عند تجاوز عتبة الفشل (على سبيل المثال، 50% من الفشل في آخر 30 ثانية)، تُفتح الدائرة. تفشل الاستدعاءات فوراً دون محاولة الوصول إلى الاعتمادية. هذا يوفر على المُستدعي (caller) إهداء الجهد، ويحمي الاعتمادية من استقبال الأحمال بينما هي غير صحية.
- نصف مفتوح (اختباري): بعد فترة تبريد، يُسمح لقاطع الدائرة بتمرير نسبة صغيرة من الاستدعاءات. إذا نجحت، يُغلق الدائرة ويعود إلى الوضع الطبيعي. إذا فشلت، يُعاد فتحها لفترة تبريد أخرى.
الدور الرئيسي: يمنع قاطع الدائرة إهداء الجهد خلال الفترات المعروفة بعدم الصحة، & يمنح الخدمة السفلية فرصة للتعافي دون استمرار الأحمال عليها.
تُحدد الجدران العازلة (Bulkheads) نطاق الانفجار. تمنع قواطع الدائرة (Circuit breakers) استمرار الانفجار بنفسه.
تحديد نطاق الانفجار
تستدعي خدمة الواجهة البرمجية (API) لديك أربع خدمات سفلية: خدمة المستخدم، خدمة التوصيات، خدمة الإشعارات، & واجهة برمجة تطبيقات الدفع من طرف ثالث. سمع الفريق أن 'خدمة التوصيات كانت غير مستقرة قليلاً' & يريد التأكد من أن النظام البقيّة يبقى صحياً عندما تفشل هذه الخدمة.
اليوم، يستخدم الخادم مجموعة خيوط (Thread Pool) مشتركة واحدة مكوّنة من 200 خيطًا ومجموعة اتصالات HTTP مشتركة واحدة. تتنافس جميع الخدمات الفرعية الأربع على هذه الموارد. ولا توجد قواطع دارة (Circuit Breakers).
تصميم مراجعة أوضاع الفشل
التوليف
لقد تعلمت كيفية اكتشاف نقاط الفشل المفردة (SPOFs) عن طريق الفحص، والتعرف على أنماط الفشل المتسلسل، والتمييز بين المحفّز والخلل الكامن عند قراءة تقرير ما بعد الحادثة، وكتابة عناصر إجراء خالية من اللوم عبر الوقاية / الكشف / التعافي، وتحديد نطاق الانفجار باستخدام الجدران العازلة + قواطع الدائرة + التدهور السليم.
طبّق جميع العناصر الخمسة.
تقوم فريقك بإطلاق خدمة جديدة search.example.com تعتمد على ثلاث خدمات فرعية: فهرس بحث أساسي (index.example.com)، وخدمة تحليلات (analytics.example.com)، وخدمة توصيات (recs.example.com). يريد الفريق منك أن تقود "مراجعة أوضاع الفشل" قبل الإطلاق.
إلى أين يتجه هذا الدرس مستقبلاً
إلى أين يتجه هذا الدرس مستقبلاً
يمكنك الآن تحديد SPOF، والتعرف على التسلسل، وقراءة تقرير ما بعد الحادثة بشكل منتج، وكتابة عناصر إجراء خالية من اللوم، وتحديد نطاق الانفجار بالتصميم.
يدرس الدرس الأخير في هذا المساق (cs_distsys_observability_and_capacity) ما يجب قياسه حتى تكتشف أن مشكلة تحدث قبل أن يلاحظها المستخدمون. فحوصات الصحة، نقاط النهاية للإصدارات، الإشارات الذهبية الأربعة على مستوى الوكيل، وكيفية ارتباط قرارات سعة الذروة بالبيانات المرصودة.
درس مرافق: geometry_of_failure_modes_and_blast_radius يستنتج مركزية بينية (أي عقدة في الرسم البياني هي عنق الزجاجة) وقطع الحد الأدنى (الحد الأقصى لنطاق الانفجار).
أحسنت. إلى الأمام.