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

مشروع: اختيار استراتيجية الاتساق

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

مشروع: اختيار استراتيجية الاتساق

كل مفهوم تناولناه في هذا البرنامج التعليمي — نظرية CAP، نماذج الاتساق، النصاب، طبولوجيات التكرار، Raft، بروتوكول 2PC، نمط Sagas — يصبّ في قرار هندسي واحد: ما مستوى ضمان الاتساق الذي يحتاجه نظامي فعلاً، وكيف أُنفّذه دون مبالغة في الهندسة؟ يعالج هذا الدرس الختامي هذا القرار من بدايته إلى نهايته، مستخدماً منصة تجارة إلكترونية واقعية كحالة دراسية متكاملة. الهدف هو امتلاك إطار عمل قابل للتكرار يمكنك تطبيقه على أي نظام تصمّمه.

النظام النموذجي: ShopFleet

ShopFleet منصة تجارة إلكترونية متوسطة الحجم: مليونا مستخدم نشط يومياً، 50,000 طلب يومياً، مُنشرة عبر ثلاث مناطق AWS (us-east-1، eu-west-1، ap-southeast-1). تتألف من ستة خدمات أساسية:

  • كتالوج المنتجات — بيانات SKU والصور والأوصاف
  • المخزون — أعداد المخزون الفعلية لكل SKU لكل مستودع
  • سلة التسوق — سلات التسوق المرتبطة بالجلسة
  • الطلبات — دورة حياة الطلب (تم الإنشاء ← الدفع معلق ← مدفوع ← يُستوفى ← مشحون)
  • المدفوعات — التكامل مع Stripe / القنوات المصرفية
  • ملفات المستخدمين — إعدادات الحساب والعناوين المحفوظة والتفضيلات

لكل خدمة قاعدة بيانات مستقلة. لا يوجد مخزن بيانات مشترك. هذا هو النسيج الكلاسيكي للخدمات المصغّرة — ومشكلة الاتساق تعيش عند نقاط التقاطع بين هذه الخدمات.

الخطوة الأولى — تصنيف كل خدمة وفق ملف القراءة/الكتابة وتحمّل القِدَم

قبل اختيار نموذج اتساق، يجب أن تفهم محورين لكل خدمة: إلى أي حد يمكن أن تكون القراءات قديمة بأمان؟ وماذا يحدث إذا فُقدت كتابة أو تكررت؟

Consistency requirement matrix for ShopFleet services Staleness Tolerance (low → high) Write Criticality (low → high) Strong Consistency Required Relax Writes, Tighten Reads Eventual Consistency OK Best-Effort / Async Payments Inventory Orders Cart User Profiles Product Catalog
تصنيف خدمات ShopFleet على محورَي تحمّل القِدَم وحرجية الكتابة. الخدمات في الربع الأعلى الأيسر تتطلب اتساقاً قوياً؛ تلك في الربع الأسفل الأيمن تقبل الاتساق النهائي.

الخطوة الثانية — تخصيص نموذج اتساق لكل خدمة

بعد تحديد الموضع على المصفوفة، خصّص نموذجاً لكل خدمة مع تبرير بالعواقب الفعلية:

المدفوعات — خطي + 2PC

يجب أن تكون عملية الخصم والإيداع ذرية. إذا خصم نظام المدفوعات من العميل ولم تتلقَّ خدمة الطلبات تأكيداً (أو العكس)، فالشركة أمام مشكلة احتيال أو إيرادات. الأداة الصحيحة هنا هي 2PC عبر XA بين قاعدة بيانات المدفوعات وقاعدة بيانات الطلبات — كلتاهما في المنطقة نفسها، زمن الرحلة أقل من 2 مللي ثانية، والمعاملة تكتمل في أقل من 50 مللي ثانية. خطر التعليق في 2PC مقبول لأننا نشغّل منسّقاً عالي التوافر (Atomikos مع سجل منسّق مُكرَّر على PostgreSQL). داخلياً، تستخدم قاعدة بيانات المدفوعات تكراراً متزامناً بقائد واحد مع كتابة نصابية (w = الأغلبية) قبل الاستجابة للمنسّق.

لا تستخدم Sagas في إتمام الدفع. المعاملة التعويضية التي "تُعيد" رسوماً بعد الحدث ليست كعدم إجراء الخصم أصلاً: سيرى العميل ظهور رسوم ثم اختفاءها، مع احتمال تجميد مصرفي. لحركة الأموال، الذرية لحظة المعاملة غير قابلة للتفاوض.

المخزون — قراءة ما كتبته + قراءات نصابية

المخزون أكثر تعقيداً مما يبدو. الخطر الحقيقي هو البيع الزائد: تدفقان متزامنان لإتمام الطلب يقرآن مخزون = 1 وكلاهما يضع طلباً بنجاح، ليبقى المخزون عند −1. الحل هو كتابة شرطية (إقفال متفائل عبر عداد إصدار) على قاعدة بيانات المخزون، لا تنسيق موزع:

UPDATE inventory SET quantity = quantity - 1, version = version + 1 WHERE sku_id = 'SKU-9921' AND version = 47 AND quantity >= 1;

إذا أثّر UPDATE على صفر صفوف، يُعيد تدفق إتمام الطلب المحاولة أو يعرض خطأ "نفاد المخزون". هذه معاملة ACID على عقدة واحدة — لا حاجة لـ2PC. لقراءات عبر المناطق (عرض المخزون على صفحات المنتجات)، تُستخدم قراءة نصابية (r = 2 من 3 نسخ) مع تحمّل نحو 50 مللي ثانية من القِدَم في العرض. الخصم الفعلي يجري دائماً على القائد.

الطلبات — قراءات رتيبة + تنسيق Saga

يمر الطلب بحالات: CREATED → PAYMENT_PENDING → PAID → FULFILLMENT_PENDING → SHIPPED → DELIVERED. كل انتقال يلمس خدمة مختلفة (المدفوعات، المستودع، الشحن). هذا هو حالة الاستخدام المثالية لـSaga منظّم: خدمة الطلبات هي المُنظِّم؛ تُصدر أوامر وتنتظر ردوداً وتُشغّل معاملات تعويضية عند الفشل.

للقراءات، لا يجب أن يرى العميل الذي يطّلع على سجل طلباته طلباً "يتراجع" (مثل ظهوره كـSHIPPED ثم العودة إلى PAID). يستوجب هذا اتساق القراءة الرتيبة — يتحقق بتوجيه جميع قراءات جلسة مستخدم معين إلى النسخة نفسها (توجيه ثابت مُرتكز على هوية المستخدم إلى مجموعة نسخ).

سلة التسوق — اتساق الجلسة + نهائي

السلة مرتبطة بالجلسة بطبيعتها. لا أحد غيرك يقرأ سلتك؛ يهمّك فقط أن كتاباتك مرئية في قراءاتك اللاحقة. هذا هو نموذج اقرأ ما كتبته (يُسمى أيضاً اتساق الجلسة). التنفيذ: تكتب خدمة السلة إلى قائد Redis؛ تُخدَّم القراءات من القائد نفسه (أو من نسخة أكّدت الكتابة فعلاً، باستخدام فحص إزاحة التكرار). بيانات السلة مؤقتة بطبيعة التصميم — مهلة 24 ساعة تعني أن الكتابة المفقودة إزعاج UX طفيف لا كارثة عمل. الاتساق النهائي مع قراءة ما كتبته هو الاختيار الصحيح هنا.

كتالوج المنتجات — اتساق نهائي + تخزين CDN

تتغير أوصاف المنتجات والصور والأسعار نادراً (بضعة آلاف كتابة يومياً عبر مليوني SKU). القراءات تفوق الكتابات بكثير. سعر قديم بـ30 ثانية على صفحة منتج مقبول — خطوة إتمام الطلب تُجري قراءة حديثة موثوقة قبل تأكيد السعر. تستخدم خدمة الكتالوج تكراراً متعدد القادة عبر مناطق AWS الثلاث (كل منطقة لها قائد) مع حل تعارض آخر كتابة تفوز (LWW) على طابع updated_at. الأصول الثابتة مُدفوعة إلى CloudFront بمهلة 5 دقائق. تُعظّم هذه الطبولوجيا إنتاجية القراءة عالمياً مع تحمّل تباين قصير بين المناطق.

ملفات المستخدمين — اتساق نهائي

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

ShopFleet consistency strategy summary per service Service Consistency Model Replication / Coordination Staleness SLA Payments Linearizable (2PC + XA) Sync single-leader, quorum write w=majority, 2PC coordinator replicated Zero (atomic) Inventory Read-Your-Writes + Quorum Reads Optimistic lock (versioned CAS write) r=2/3 replicas for display reads < 50 ms (display) Orders Monotonic Reads + Saga Orchestration Orchestrated Saga, session-affinity sticky routing to replica 0 ms (monotonic) Cart Session Consistency (Read-Your-Writes) Redis primary write, read from primary or replication-offset-checked replica Seconds (ephemeral) User Profiles Eventual Consistency Async single-leader replication cross-region < 5 s (cross-region) Product Catalog Eventual Consistency (multi-leader + CDN) Multi-leader LWW, CDN 5-min TTL authoritative price check at checkout < 30 s (display)
استراتيجية الاتساق الكاملة لمنصة ShopFleet: لكل خدمة نموذج وتطبيق واتفاقية قِدَم مرتبطة بمتطلباتها التجارية الفعلية.

الخطوة الثالثة — إطار القرار (عام)

استخدم قائمة التحقق الخماسية هذه على أي خدمة في أي نظام:

  1. ما تكلفة القراءة القديمة؟ إذا كانت "خسارة مالية" أو "احتيال"، اختر الاتساق القوي. إذا كانت "خلل UX طفيف"، اختر النهائي.
  2. ما تكلفة الكتابة المفقودة أو المكررة؟ إذا كانت لا رجعة فيها (نقل أموال، خصم مخزون)، استخدم كتابات ذرية مع إقفال متفائل أو 2PC. إذا كانت قابلة للاسترداد، يكفي الكتابة مرة واحدة على الأقل مع الـidempotency.
  3. هل المشاركون في موضع مشترك وقليلون؟ 2PC مجدٍ لـ2–3 قواعد بيانات في مركز بيانات واحد. لأي شيء يمتد عبر مناطق أو يتجاوز ~5 مشاركين، استخدم Sagas.
  4. هل العملية طويلة الأمد (أكثر من 100 مللي ثانية)؟ لا تستخدم 2PC أبداً. استخدم Saga مع معاملات تعويضية. وقت حيازة القفل يتناسب مع زمن كمون خطوة الـsaga — هذه هي الرؤية المحورية.
  5. ما تأخّر التكرار الذي يمتصّه اتفاقية مستوى الخدمة؟ ترجم SLA العمل ("يرى المستخدمون المخزون المحدَّث خلال ثانية") إلى ميزانية تكرار. صمّم مسارات القراءة (نصابية، متزامنة، غير متزامنة) لتحقيق تلك الميزانية.
الخطأ الأكثر شيوعاً هو تطبيق أقوى اتساق متاح في كل مكان — الخطي عبر جميع الخدمات — لأنه يبدو "آمناً". إنه ليس آمناً؛ بل هو هشّ. يُقرن توافرك بأضعف حلقة في سلسلة التنسيق. تخصيص الاتساق لكل خدمة ليس تنازلاً؛ بل انضباط هندسي.
نفّذ دائماً "فحص السعر عند إتمام الطلب". حتى عند استخدام الاتساق النهائي لعرض المنتجات، أجرِ قراءة حديثة موثوقة للسعر والمخزون على القائد مباشرةً قبل تأكيد الطلب. هذا النهج الثنائي — نهائي للتصفّح، قوي للالتزام — هو النمط القياسي في الصناعة الذي يستخدمه Amazon وShopify وكل منصة تجارة إلكترونية كبرى تقريباً.

الخطوة الرابعة — التحقق من التصميم

استراتيجية الاتساق لا تساوي شيئاً دون تحليل أوضاع الفشل. لكل خدمة، اسأل: ماذا يحدث إذا فشل القائد الرئيسي الآن؟ وماذا يحدث إذا استمر انقسام شبكي بين المناطق 30 ثانية؟ في ShopFleet:

  • فشل قائد المدفوعات: تُرقَّى النسخة المتزامنة (RDS Multi-AZ، ~30 ثانية). معاملات 2PC المعلّقة تُلغى بانتهاء مهلة المنسّق. لا فقدان بيانات.
  • فشل قائد المخزون: تفشل الكتابات الشرطية؛ تعرض صفحة إتمام الطلب "غير متاح مؤقتاً". تراجع قراءات العرض إلى النسخ النصابية. التعافي في ~30 ثانية.
  • انقسام 30 ثانية بين us-east وeu-west: يتباعد كتالوج المنتجات — قد يرى مستخدمو الاتحاد الأوروبي أسعاراً مختلفة قليلاً. القراءة الموثوقة عند إتمام الطلب تُشغَّل على قائد منطقة المستخدم، لا طلب خاطئ يُوضع. كتابات السلة في المنطقة الأوروبية تُخزَّن محلياً وتُدفع عند استعادة الاتصال.

تشغيل هذه السيناريوهات ذهنياً — أو بشكل أفضل، بأداة chaos-engineering مثل Gremlin أو AWS Fault Injection Simulator — يحوّل التصميم الورقي إلى معمارية مُتحقَّق منها.

الملخص

استراتيجية الاتساق الصحيحة ليست إعداداً واحداً؛ بل قرار لكل مكوّن مدفوع بالأثر التجاري وطبولوجيا المشاركين وميزانيات الكمون. تستخدم ShopFleet أربعة نماذج متمايزة عبر ستة خدمات: خطي + 2PC للأموال، إقفال متفائل + نصاب للمخزون، قراءات رتيبة + Sagas لسير عمل الطلبات، واتساق نهائي لكل ما يتصفّحه المستخدم. هذه هي الحالة الطبيعية والصحيحة لنظام موزع مُصمَّم جيداً — ليست فشلاً في تحقيق التوحيد.

اكتمل الدرس!

تهانينا! لقد أكملت جميع الدروس في هذا البرنامج التعليمي.