قواعد البيانات في الإنتاج

التعمق في النسخ المتماثل

18 دقيقة الدرس 3 من 30

التعمق في النسخ المتماثل

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

كيف يعمل النسخ المتماثل فعلاً

يعتمد كل من MySQL/MariaDB وPostgreSQL على النسخ المتماثل القائم على سجل ثنائي (binlog / WAL). يكتب الخادم الأساسي (Primary) كل تغيير مُنفَّذ في سجل دائم. تقوم النسخ المتماثلة (Replicas) بدفق هذا السجل وإعادة تنفيذه بالترتيب. النقطة الجوهرية هنا أن هذه العملية غير متزامنة (Async) بشكل افتراضي: يعتبر الخادم الأساسي المعاملة مُكتملة فور كتابتها على قرصه، قبل أن تُقرّ أي نسخة باستلامها.

Async vs Semi-Sync Replication Flow Primary Binlog / WAL Replica 1 IO thread + SQL thread Replica 2 IO thread + SQL thread async async semi-sync ACK Application reads replicas Async log stream Semi-sync acknowledge
النسخ المتماثل الغير متزامن (الافتراضي) مقابل مسار تأكيد شبه متزامن. يشترط النمط شبه المتزامن تأكيد نسخة واحدة على الأقل قبل أن يستجيب الخادم الأساسي للعميل.

تأخر النسخ المتماثل: الأسباب والقياس والحدود

تأخر النسخ المتماثل (Replication Lag) هو عمر أقدم حدث غير منفَّذ على نسخة ما. ليس رقماً واحداً بل هو محصلة: زمن استجابة الشبكة، واختناق الإدخال/الإخراج على النسخة، وعقبات إعادة التشغيل أحادية الخيط (قبل MySQL 8.0 وقبل تطبيق التوازي في Postgres).

أسباب التأخر الشائعة على نطاق واسع:

  • المعاملات الضخمة — استعلام UPDATE orders SET ... WHERE 1=1 الذي يستغرق 40 ثانية على الخادم الأساسي يُعطّل خيط SQL على النسخة لنفس المدة.
  • إعادة التشغيل المقيّدة بالإدخال/الإخراج — تعمل النسخ غالباً على أجهزة أرخص، فيصبح القرص عنق الزجاجة.
  • جمل DDLALTER TABLE على جدول بحجم 200 GB تُقفل خيط SQL طوال مدة تنفيذها على النسخ المعتمدة على الصف.
  • خيط SQL أحادي — MySQL قبل 5.7 وPostgres قبل التطبيق الموازي في النسخ المنطقية للإصدار 16. فعّل خيوط SQL المتوازية.

قس التأخر من داخل قاعدة البيانات لا من نبضات جانب التطبيق:

-- MySQL / MariaDB — على أي نسخة SHOW REPLICA STATUS\G -- الحقول الرئيسية: -- Seconds_Behind_Source : التأخر بالثواني (NULL = خيط IO منقطع) -- Retrieved_Gtid_Set : ما استلمته النسخة -- Executed_Gtid_Set : ما أعادت النسخة تنفيذه -- Relay_Log_Space : بايتات الأحداث غير المُعاد تشغيلها في المخزن المؤقت -- PostgreSQL — على أي خادم احتياطي SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag; -- PostgreSQL — على الخادم الأساسي، التأخر لكل فتحة نسخ SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag_bytes FROM pg_replication_slots;

في Prometheus، أظهر هذه المقاييس عبر mysqld_exporter أو postgres_exporter وأطلق تنبيهات عند تجاوز التأخر حد مستوى خدمتك (عادةً 30 ثانية لـ OLTP، و5 دقائق لنسخ التحليلات):

# PromQL — تنبيه تأخر نسخة MySQL ALERT MySQLReplicaLagHigh IF mysql_slave_status_seconds_behind_master{job="mysql"} > 30 FOR 2m LABELS { severity="warning" } ANNOTATIONS { summary = "Replica {{ $labels.instance }} is {{ $value }}s behind primary", } # PromQL — تنبيه تأخر خادم PostgreSQL الاحتياطي ALERT PGStandbyLagHigh IF pg_replication_lag{job="postgres"} > 30 FOR 2m LABELS { severity="warning" }
فعّل خيوط SQL المتوازية على نسخ MySQL: اضبط replica_parallel_workers = 8 وreplica_parallel_type = LOGICAL_CLOCK في /etc/mysql/conf.d/replica.cnf. يتيح هذا إعادة تشغيل المعاملات المستقلة بالتوازي، مما يقلل التأخر 4 إلى 8 أضعاف على أحمال الكتابة الكثيفة. تحقق من النتيجة عبر SHOW PROCESSLIST — يجب أن ترى عدة خيوط system user تُنفّذ SQL.

نسخ القراءة في الإنتاج: الأنماط والمزالق

نسخ القراءة قوية — إذ تستوعب 80-95% من حركة المرور في الخدمات كثيفة القراءة. قواعد استخدامها بأمان:

  • لا تقرأ بياناتك الخاصة من نسخة بعد الكتابة. بعد الكتابة على الخادم الأساسي، وجّه القراءة التالية لتلك الجلسة إلى الخادم الأساسي (أو انتظر WAIT_FOR_EXECUTED_GTID_SET / pg_wal_replay_wait() للتأكد من لحاق النسخة). الارتباط بالخادم الأساسي بعد الكتابة هو النمط الأكثر أماناً.
  • وجّه حسب نوع الاستعلام لا عشوائياً. استعلامات التحليلات تنتمي إلى نسخة مخصصة؛ قراءات OLTP تستطيع استخدام مجموعة نسخ عامة. لا تخلطهما — استعلام تحليلي لمدة 60 ثانية سيُؤخر إعادة تشغيل OLTP خلفه.
  • استخدم وكيلاً (Proxy). ProxySQL لـ MySQL وPgBouncer+HAProxy لـ Postgres يديران التوجيه للقراءة/الكتابة والفحوصات الصحية والتحويل التلقائي دون تضمين سلاسل الاتصال في كود التطبيق.
  • افحص التأخر قبل التوجيه. mysql_servers.max_replication_lag في ProxySQL يزيل خادماً من مجموعة القراءة عند تجاوز التأخر الحد. اضبط هذا. نسخة متأخرة ببيانات قديمة أسوأ من توجيه القراءة للخادم الأساسي.

الانفصال الدماغي (Split-Brain): أخطر أنماط الفشل

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

Split-Brain Scenario During Network Partition Before Partition Primary writes accepted Replica reads only partition After Partition (Split-Brain) Old Primary still accepts writes! New Primary promoted, also writes! diverged data A diverged data B On heal: conflicting histories. Data from one side MUST be discarded.
الانفصال الدماغي: كلا الخادمَين يقبلان الكتابة بعد الانقطاع؛ تتباعد البيانات ولا بد من إسقاط أحد الجانبين عند إعادة الاتصال.

استراتيجيات الوقاية (مرتبة حسب الفعالية):

  1. STONITH / العزل (Fencing) — اختصار لـ "أطلق النار على العقدة الأخرى في رأسها". قبل أن تُرقّي النسخة نفسها، يجب أن تعزل الخادم الأساسي القديم (إيقاف تشغيله عبر IPMI، أو فصل قرصه السحابي، أو إنهاء واجهة شبكته). دون عزل، يستطيع عقدتان الاعتقاد في آنٍ واحد أنهما خادم أساسي.
  2. الترقية المبنية على الحصة النسبية (Quorum) — تشترط كل من Patroni (Postgres) وOrchestrator (MySQL) وMHA تصويت أغلبية الكتلة قبل الترقية. الأقلية المعزولة لا تستطيع تحقيق الحصة، فلا تستطيع الترقية.
  3. النسخ شبه المتزامن — يشترط تأكيد نسخة واحدة على الأقل قبل إعادة الخادم الأساسي للنجاح. إذا كانت جميع النسخ غير متاحة، يعود شبه المتزامن إلى وضع غير متزامن بعد rpl_semi_sync_source_timeout (الافتراضي 10 ثوانٍ) — يتدهور بأمان لكنه يمنع أكثر سيناريوهات فقدان البيانات شيوعاً.
  4. مراقبة ضغط فتحات النسخ في Postgres — الفتحات غير المستخدمة تحتجز WAL إلى أجل غير مسمى. نسخة تتأخر كثيراً ثم تعود ستتسبب في نفاد ذاكرة الخادم الأساسي. راقب pg_replication_slots.active وأسقط الفتحات غير النشطة فوراً.
يأتي تكوين Postgres الافتراضي مع max_wal_size = 1GB وبدون فتحات نسخ — ما يعني إعادة تدوير WAL قبل أن تتمكن نسخة بطيئة من إعادة تشغيله. فعّل فتحات النسخ للنسخ التي تهتم بها، لكن اضبط max_slot_wal_keep_size لتحديد سقف الاحتفاظ بـ WAL حتى لا تملأ النسخة الميتة القرص. بدون هذا السقف، نسخة تنقطع لمدة 24 ساعة قادرة على إسقاط الخادم الأساسي حين ينفد تخزين WAL.
على نطاق Google، يتجاوز Spanner تأخر النسخ المتماثل كلياً باستخدام TrueTime (GPS + ساعة ذرية) لتوفير قراءات متسقة خارجياً. في بيئتك الإنتاجية على AWS/GCP، تُقدّم Aurora Global Database وCloud Spanner نسخاً متماثلاً عبر المناطق بأقل من ثانية مع تحويل تلقائي — ادرس ضمانات الاتساق فيهما قبل افتراض أن جميع قواعد البيانات المُدارة تتصرف كـ MySQL العادية.

قائمة مراجعة التشغيل: تشغيل النسخ في الإنتاج

  • فعّل النسخ المتماثل المبني على GTID (gtid_mode=ON + enforce_gtid_consistency=ON في MySQL) حتى لا يتطلب التحويل الحسابَ اليدوي لموضع binlog.
  • راقب Seconds_Behind_Source أو pg_replication_lag مع تنبيهات؛ أطلق تنبيه الاستدعاء عند تأخر يتجاوز 60 ثانية.
  • اضبط read_only=ON على كل نسخة على مستوى المحرك — يجب أن يضبط مدير الـ HA أيضاً super_read_only=ON حتى لا يتمكن حتى المستخدمون المميزون من الكتابة عن طريق الخطأ.
  • تحقق من سلامة النسخ أسبوعياً باستخدام pt-table-checksum (MySQL) أو pg_amcheck (Postgres 14+). الفساد الصامت حقيقي.
  • اختبر التحويل في بيئة تجريبية شهرياً. متوسط وقت التأكيد من أن مكدس الـ HA يعمل فعلاً أقيم من أي دليل تشغيل.