الفكرة العامة
بدل ما كل أبلكيشن يحل مشاكل الفصل 8 لوحده، بنبني abstractions عامة بضمانات مفيدة (زي ما الـ transactions بتخبي الـ crashes والـ concurrency). أهمها: Consensus — إن كل النودز توافق على حاجة واحدة.
معظم الداتابيزات الـ replicated بتدي eventual consistency (الأدق نسميها convergence): لو الكتابات وقفت، في وقت مش معروف الـ replicas هتتساوى. ده ضمان ضعيف جدًا ومش بيقول إمتى. الأحسن نعرف ضمانات أقوى — بس ليها تمن (أداء و availability).
Linearizability
اسمها كمان atomic consistency / strong consistency / immediate consistency. الفكرة: السيستم يبان كأن فيه نسخة واحدة بس من الداتا، وكل عملية atomic. أول ما كتابة تخلص (أو قراءة ترجع القيمة الجديدة)، كل القراءات اللي بعدها لازم تشوف الجديد — مينفعش ترجع للقديم.
إمتى محتاجها؟
- Locking و Leader election: لازم كل النودز تتفق مين ماسك الـ lock (ZooKeeper, etcd). وإلا split brain.
- Constraints و Uniqueness: username unique، رصيد مش سالب، تذكرة مش متباعة مرتين. (لو constraints مرنة ينفع حلول أضعف.)
- Cross-channel timing: مثال: ترفع صورة → تتخزن في file storage → رسالة في queue لـ service تعمل thumbnail. لو الـ queue أسرع من الـ replication، الـ service هيقرا صورة قديمة أو مش موجودة. (قناتين: storage و queue.)
مين بيحققها؟
| الطريقة | Linearizable؟ |
|---|---|
| Single-leader | ⚠️ ممكن — لو قريت من الـ leader أو followers sync. بس ممكن يكسرها split brain أو failover بـ async. |
| Consensus algorithms (ZooKeeper, etcd) | ✅ — ده أساسًا هدفها. |
| Multi-leader | ❌ — كتابات concurrent في أماكن مختلفة. |
| Leaderless (Dynamo) | ❌ غالبًا — حتى quorums ممكن تكسرها (تأخير شبكة متفاوت)، و LWW بالساعات أكيد لأ. ممكن تبقى linearizable بـ read repair synchronous قبل ما ترجع (بتكلفة)، بس compare-and-set مش هيبقى ممكن. |
الـ CAP Theorem — من غير ما تتلخبط
لو الشبكة اتقطعت (partition) بين داتاسنترين:
الـ replicas اللي مش واصلة للـ leader لازم ترفض أو تستنى → unavailable.
كل replica تكمل تشتغل لوحدها → available بس مش linearizable.
والسبب الحقيقي للتخلي عن linearizability غالبًا هو الأداء مش الـ fault tolerance: حتى الرام في الـ multi-core CPU مش linearizable (كل core له cache)! وأُثبت إن response time للقراءة والكتابة في نظام linearizable لازم يكون متناسب مع عدم اليقين في تأخير الشبكة — والشبكة الحقيقية تأخيرها متغير جدًا.
الترتيب (Ordering) والسببية (Causality)
الترتيب موضوع متكرر في الكتاب كله: ترتيب الـ leader للـ replication log، ترتيب الـ serializability، والـ timestamps في الفصل 8. والسبب: الترتيب بيحافظ على السببية — السؤال قبل الجواب، الـ insert قبل الـ update، الـ snapshot لازم يكون causally consistent.
أي عمليتين تقدر تقول مين قبل مين. خط زمني واحد.
بعض العمليات مرتبة (لو سبب ونتيجة)، والباقي concurrent ومالوش ترتيب — زي الـ branching والـ merging في Git.
Linearizability ⇒ causality (أقوى منها). بس Causal consistency هي أقوى consistency ممكنة مش بتبطّأ بسبب تأخير الشبكة وبتفضل available وقت الـ partitions! وفي حالات كتير هي اللي محتاجها فعلًا. التحدي: إزاي تتبع الـ causal dependencies؟ (زي version vectors — بس الداتابيز لازم تعرف أنهي version الأبلكيشن قراه قبل ما يكتب.)
Sequence Numbers و Lamport Timestamps
تتبع كل الـ dependencies مكلف. البديل: أرقام تسلسلية (counter) متسقة مع السببية. في single-leader سهلة (الليدر يدي رقم لكل عملية). من غير leader؟ الطرق البسيطة (كل نود أرقام فردية/زوجية، timestamps، blocks) مش causally consistent.
Total Order Broadcast (Atomic Broadcast)
بروتوكول لتبادل الرسايل بين النودز بشرطين:
- Reliable delivery: لو رسالة وصلت لنود، توصل لكل النودز.
- Totally ordered: كل النودز تستلم الرسايل بنفس الترتيب.
ده بالظبط اللي محتاجينه للـ state machine replication: كل replica تنفذ نفس الكتابات بنفس الترتيب = نسخ متسقة. (وكمان serializable transactions لو كل رسالة stored procedure deterministic.) وده بيشبه log بالظبط (replication log، transaction log، WAL). ZooKeeper و etcd بيطبقوه. ومهم كمان للـ fencing tokens (رقم الرسالة = token متزايد — في ZooKeeper اسمه zxid).
Distributed Transactions و Consensus
Consensus = كذا نود يتفقوا على حاجة واحدة. أمثلة: leader election (ومنع split brain)، و atomic commit (transaction على كذا نود — يا الكل commit يا الكل abort).
Atomic Commit و Two-Phase Commit (2PC)
على نود واحدة سهلة: الـ commit record في الـ log هو اللحظة الحاسمة. بس على كذا نود؟ ماينفعش تبعت commit للكل وخلاص — ممكن واحدة تنجح وواحدة تفشل (constraint، crash، شبكة) — والـ commit مايترجعش (حد ممكن يكون قرأه بالفعل).
نقطتين اللاعودة: (1) الـ participant لما يقول “نعم” بيوعد إنه هيعمل commit مهما حصل (كتب كل حاجة على الديسك). (2) الـ coordinator لما يكتب القرار في الـ log (commit point) لازم ينفذه — يعمل retry للأبد لحد ما الكل ينفذ.
Distributed Transactions في الواقع
- Database-internal: نفس الداتابيز على كذا نود (VoltDB, MySQL Cluster NDB). أداء معقول لأن كل حاجة من نفس الـ software.
- Heterogeneous: أنظمة مختلفة (داتابيز + message broker) → XA transactions (معيار من 1991، Java: JTA). ممكن تضمن إن رسالة الـ queue تتعالج مرة واحدة بالظبط (exactly-once) مع كتابة الداتابيز.
ليه ليها سمعة وحشة؟ MySQL distributed أبطأ 10 مرات من single-node. والأخطر: transactions “in doubt” ماسكة locks، ولو الـ coordinator log ضاع → orphaned transactions محتاجة تدخل يدوي (أو heuristic decisions = كسر للـ atomicity). والـ coordinator غالبًا single point of failure، وبيخلي الأبلكيشن stateful. وXA مش بيقدر يكتشف deadlocks بين الأنظمة ولا يشتغل مع SSI. والقاعدة: الـ distributed transactions بتضخم الأعطال — أي جزء يبوظ، الكل يبوظ.
Fault-Tolerant Consensus
الخصائص الأربعة:
| الخاصية | المعنى | النوع |
|---|---|---|
| Uniform agreement | مفيش نودين يقرروا حاجتين مختلفين. | Safety |
| Integrity | محدش يقرر مرتين. | Safety |
| Validity | القرار لازم يكون حاجة حد اقترحها. | Safety |
| Termination | كل نود شغالة هتوصل لقرار في الآخر (fault tolerance — ده اللي 2PC مش بيحققه). | Liveness |
بشرط إن الأغلبية شغالة (n=2f+1 تستحمل f أعطال).
الخوارزميات المشهورة: Viewstamped Replication، Paxos (Multi-Paxos)، Raft، Zab (ZooKeeper). وأغلبها عمليًا بتحقق total order broadcast مباشرة (consensus على كل رسالة).
حدود الـ consensus: التصويت ده نوع من synchronous replication (بطيء)، محتاج أغلبية (لو الشبكة قطعت، الأقلية واقفة)، أغلبها بيفترض عدد نودز ثابت، بيعتمد على timeouts — في شبكة متغيرة ممكن انتخابات كتير مالهاش لازمة (Raft عنده edge cases لو لينك واحد بايظ).
Membership & Coordination Services (ZooKeeper / etcd)
زي “داتابيز key-value صغيرة” بس مش هتستخدمها مباشرة غالبًا — بتستخدمها مكتبات ومشاريع (HBase, Hadoop YARN, Kafka…). بتديك consensus جاهز ومجرب:
- Allocating work: اختيار leader/primary، وتوزيع الـ partitions على النودز وإعادة توزيعها.
- بتشتغل على 3 أو 5 نودز ثابتين حتى لو السيستم آلاف — الـ consensus على عدد صغير.
- مناسبة للداتا اللي بتتغير ببطء (مين الـ leader لـ partition 7) مش للـ runtime state اللي بيتغير آلاف المرات في الثانية.
- Service discovery (IP الخدمة دي إيه؟) — DNS عادي أنسب غالبًا لأنه مش محتاج linearizability.
- Membership: مين النودز الحية في الكلاستر — ومع consensus الكل يتفق على القائمة.
🧪 Lab
شغّل etcd بـ Docker (3 نودز لو تقدر — راجع الـ docs) وجرّب:
etcdctl put /config/leader node-1etcdctl lock my-lock # lock موزع — جرّبه من terminal-ينetcdctl elect my-election node-a # leader election
وبعدين اقتل الـ leader node وشوف الكلاستر بينتخب واحد جديد. وجرّب تقتل نودين من 3 — الكتابة هتقف (مفيش أغلبية).