اتساق البيانات والنسخ

نمط السَّاغَا

18 دقيقة الدرس 9 من 10

نمط السَّاغَا

في الدرس السابق تعلمت أن Commit ذو المرحلتين (2PC) يوفر تناسقاً قوياً عبر الخدمات الموزعة — لكن بتكلفة باهظة: يجب أن يُقفَل جميع المشاركين ويظلوا متاحين طوال مدة المعاملة. في الواقع العملي، إقفال سجل طلب وسجل مخزون وسجل دفع عبر ثلاث خدمات مستقلة لمدة 200 مللي ثانية فحسب يخلق تنافساً شديداً على الموارد عند التوسع، ويجعل النظام هشاً كلما كانت أي خدمة بطيئة أو غير متاحة مؤقتاً.

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

فكرة جوهرية: تقايض الساغا الذرية (كل شيء أو لا شيء في لحظة واحدة) بـالاتساق التدريجي (تتقارب جميع الخدمات نحو الحالة الصحيحة بعد عدد محدود من الخطوات، بما فيها التعويض). هذه مقايضة صريحة ومتعمدة وهي دائماً الخيار الصحيح في بنيات الخدمات المصغرة.

مثال ملموس: تسجيل طلب في التجارة الإلكترونية

تأمل عملية تسجيل طلب تمتد عبر أربع خدمات مستقلة: خدمة الطلبات، وخدمة المخزون، وخدمة الدفع، وخدمة الإشعارات. تتطلب مسار النجاح نجاح الخطوات الأربع جميعها؛ وأي فشل في خطوة ما يستوجب التراجع عن العمل المُنجز السابق.

الخطوات الأمامية هي:

  1. خدمة الطلبات — إنشاء سجل طلب بحالة PENDING.
  2. خدمة المخزون — حجز المخزون المطلوب (طرحه من الكمية المتاحة).
  3. خدمة الدفع — تحصيل المبلغ من وسيلة الدفع.
  4. خدمة الإشعارات — إرسال رسالة تأكيد وتحديث حالة الطلب إلى CONFIRMED.

المعاملات التعويضية المقابلة (تنفّذ بالترتيب العكسي عند الفشل):

  1. تحديث حالة الطلب إلى CANCELLED.
  2. تحرير المخزون المحجوز (إعادة الكمية).
  3. إصدار استرداد للمبلغ.
  4. إرسال رسالة إلغاء.
Saga happy path and compensation flow for order placement Happy Path (forward steps) Failure: Payment fails → compensate 1. Order Service Create PENDING order 2. Inventory Service Reserve stock 3. Payment Service Charge customer 4. Notification Email + CONFIRMED success event success event success event 1. Order Service Create PENDING order ✓ 2. Inventory Service Reserve stock ✓ 3. Payment Service FAILED (card declined) Compensate #2 Release inventory Compensate #1 Mark CANCELLED trigger compensate then
يسار: مسار النجاح — كل خطوة تنشر حدث نجاح لتشغيل التالية. يمين: عند فشل خدمة الدفع، تنفَّذ المعاملات التعويضية بالترتيب العكسي لتحرير المخزون وإلغاء الطلب.

التنسيق بالكوريوغرافي مقابل التنسيق بالأوركسترا

هناك طريقتان مختلفتان جذرياً لتنسيق خطوات الساغا:

الكوريوغرافي (مدفوع بالأحداث): كل خدمة تستمع للأحداث على ناقل رسائل مشترك وتتفاعل بشكل مستقل. تنشر خدمة الطلبات OrderCreated؛ تستهلكه خدمة المخزون وتنشر StockReserved؛ تستهلكه خدمة الدفع وهكذا. لا يوجد منسق مركزي — الساغا تنبثق من التفاعلات.

الأوركسترا (مدفوع بالأوامر): يقوم منسق الساغا المخصص (غالباً ما يُنفَّذ كآلة حالة) بإصدار أوامر صريحة لكل مشارك بالتسلسل، وينتظر ردوده، ثم يقرر الخطوة التالية — بما في ذلك إصدار التعويضات عند فشل أي خطوة. المنسق هو المصدر الوحيد للحقيقة بشأن الحالة الحالية للساغا.

Choreography vs Orchestration saga coordination Choreography Order Svc Inventory Svc Notification Payment Svc Message Bus / Event Bus publish publish subscribe subscribe No central coordinator Services react to events Orchestration Saga Orchestrator (state machine) Order Svc Inventory Svc Notification Payment Svc Orchestrator commands each step Single source of saga state
الكوريوغرافي: تُصدر الخدمات أحداثاً وتتفاعل معها على ناقل مشترك دون منسق مركزي. الأوركسترا: آلة حالة مخصصة تصدر الأوامر لكل مشارك وتدير التعويض عند الفشل.

الكوريوغرافي: المقايضات

  • المزايا: اقتران خفيف — الخدمات لا تعرف بعضها؛ إضافة خطوات جديدة سهلة بالاشتراك في الأحداث الحالية؛ لا نقطة فشل واحدة للتنسيق.
  • العيوب: يصعب فهم التدفق الكامل — ضمني وموزع على خدمات عديدة؛ صعوبة التصحيح عند الفشل في منتصف الساغا؛ التعويضات يجب أن تكون مدفوعة بالأحداث أيضاً مما يزيد تعقيد الشبكة.

يعمل الكوريوغرافي جيداً مع الساغات البسيطة الخطية ذات 2–4 خطوات حيث يكون عقد الأحداث محدداً مسبقاً.

الأوركسترا: المقايضات

  • المزايا: تدفق الساغا صريح ومركزي — سهل القراءة والمراقبة والتصحيح؛ منطق الإلغاء متجاور مع المنطق الأمامي؛ آلة حالة المنسق هي سجل تدقيق طبيعي.
  • العيوب: تُدخل مكوناً جديداً (المنسق) يجب أن يكون متاحاً بدرجة عالية؛ خطر التحول إلى "خدمة إله" إذا كان نطاقها سيئاً؛ قد يخلق اقتراناً أوثق عبر قنوات الأوامر المباشرة.

الأوركسترا هي الخيار السائد في أنظمة الخدمات المصغرة الإنتاجية ذات التدفقات متعددة الخطوات والطويلة. أطر مثل Apache Camel وConductor (Netflix) وTemporal وAWS Step Functions تنفذه كبدائي أساسي.

أفضل الممارسات: اجعل حالة الساغا دائمة. احفظ حالة المنسق في قاعدة بيانات قبل إصدار كل أمر. إذا انهار المنسق وأعاد التشغيل، يمكنه الاستئناف من الخطوة الأخيرة المعروفة تماماً — دون فقدان معاملات أو تشغيل تعويضات خاطئة. Temporal.io مثلاً يخزن كل حدث سير عمل في سجل أحداث يُلحَق به فقط ويصمد أمام إعادة تشغيل العمليات.

تصميم المعاملات التعويضية

المعاملات التعويضية ليست تراجعات بالمعنى القاعدي — المعاملة الأصلية نُفِّذت فعلاً. التعويض هو معاملة جديدة متحركة للأمام تلغي الأثر التجاري دلالياً.

ثلاث خصائص يجب أن تتوفر في كل تعويض:

  1. الاستهلاك المتعدد الآمن (Idempotency) — يمكن تنفيذ التعويض مرات متعددة بأمان. إذا أعادت الشبكة إرسال أمر التعويض، يجب أن يكون التنفيذ الثاني بلا أثر (مثل "إذا كان المخزون قد حُرِّر مسبقاً، لا تفعل شيئاً").
  2. التبادلية حيثما أمكن — يجب ألا تعتمد التعويضات على ترتيب محدد للأحداث المتزامنة، لأن ترتيب تسليم الرسائل غير مضمون في الأنظمة الموزعة.
  3. الاكتمال — كل خطوة أمامية تُغيِّر الحالة يجب أن يكون لها تعويض محدد جيداً. إذا لم تستطع تعريف تعويض، فهذه الخطوة لا يمكن أن تكون جزءاً من ساغا (فكر في استخدام 2PC أو استدعاء متزامن بدلاً من ذلك).
مأزق — "المعاملات المحورية": بعض الخطوات لا يمكن تعويضها بعد تنفيذها. إرسال بريد إلكتروني أو تشغيل تحويل بنكي هي معاملات محورية — بمجرد تنفيذها لا يمكن التراجع عنها. ضع المعاملات المحورية كـآخر خطوة في ساغتك حتى تظل التعويضات لجميع الخطوات السابقة ممكنة إذا فشلت المعاملة المحورية نفسها قبل اكتمالها.

العزل ومشكلة "القراءة القذرة"

خلافاً لمعاملات ACID، لا توفر الساغات أي عزل بين حالات الساغا المتزامنة. بينما تحجز الساغا أ المخزون وتُحصِّل الدفع، يمكن للساغا ب قراءة الحالة الوسيطة (مثل المخزون الذي يظهر محجوزاً لكن الدفع لم يُؤكَّد بعد). هذه هي مشكلة "القراءة القذرة" في الساغات الموزعة.

استراتيجيات التخفيف:

  • القفل الدلالي — وسّم الموارد بعلامة "قيد التنفيذ" (مثل status = PENDING_PAYMENT) تتحقق منها الساغات الأخرى قبل المتابعة.
  • التحديثات التبادلية — صمّم التحديثات بحيث لا يهم الترتيب (مثل زيادة/تخفيض العدادات بدلاً من تعيين قيم مطلقة).
  • إعادة قراءة القيم — قبل الخطوة الأخيرة، أعد قراءة الحقول الحرجة وألغِ الساغا إذا تغيرت بشكل غير متوقع منذ بدئها.

متى تستخدم نمط الساغا

  • أي عملية تمتد عبر خدمتين مصغرتين أو أكثر بقواعد بيانات مستقلة ومتطلب تجاري للاتساق.
  • سير العمل طويلة الأمد (حجز رحلة: طيران + فندق + سيارة؛ معالجة قرض: فحص ائتماني + تقييم + موافقة + صرف) حيث لا يمكن قبول إقفال موزع طوال المدة.
  • عندما تكون الخطوات الفردية مستهلَكة بشكل آمن أو يمكن جعلها كذلك.

الساغات هي آلية الاتساق المعيارية في الصناعة لبنيات الخدمات المصغرة. كل منصة تجارة إلكترونية كبرى، ونظام تقنية مالية، وخدمة مشاركة في الركوب تستخدم نوعاً من هذا النمط. إتقانه — مع فهم مقايضاته في العزل والانضباط المطلوب للتعويضات الصحيحة — هو أحد أكثر المهارات قيمةً عملياً في تصميم الأنظمة الموزعة.