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

مشروع: دليل تشغيل قاعدة البيانات في الإنتاج

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

مشروع: دليل تشغيل قاعدة البيانات في الإنتاج

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

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

نظامنا المرجعي هو كلستر PostgreSQL 16 يعمل خلف PgBouncer على إعداد Patroni ثلاثي العقد: عقدة أساسية واحدة وعقدتا نسخ متماثل متزامنتان، مع HAProxy VIP يوجّه عمليات الكتابة على المنفذ 5000 وعمليات القراءة على المنفذ 5001. تُؤخذ النسخ الاحتياطية ليلًا عبر pgBackRest إلى مخزن كائنات متوافق مع S3 ويُحتفظ بها لمدة 30 يومًا. وتُدار ترحيلات المخطط باستخدام Flyway وتُطبَّق عبر خط CI/CD.

Production database cluster layout: HAProxy, PgBouncer, Patroni, pgBackRest Application Pods HAProxy VIP :5000 writes · :5001 reads PgBouncer Pool transaction-mode · pool_size=50 Primary (Leader) Patroni + pg 16 Replica 1 (sync) hot standby Replica 2 (sync) hot standby pgBackRest → S3 etcd (Patroni DCS)
الكلستر المرجعي: يوجّه HAProxy حركة الكتابة والقراءة عبر PgBouncer إلى كلستر Patroni ثلاثي العقد؛ يشحن pgBackRest أرشيفات WAL إلى S3؛ وetcd هو مخزن الإعدادات الموزّع.

الجزء الأول — إجراءات التوافرية العالية

الفشل التلقائي المُخطَّط (التبديل في الصيانة)

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

# 1. التحقق من صحة الكلستر قبل المساس بأي شيء patronictl -c /etc/patroni/patroni.yml list # المتوقع: قائد واحد، نسخة متماثلة واحدة على الأقل تعمل، replication_lag = 0 # 2. التحقق من لحاق كلتا النسختين بالكامل psql -h localhost -U postgres -c " SELECT application_name, state, sent_lsn - replay_lsn AS replay_lag_bytes, sync_state FROM pg_stat_replication; " # المتوقع: replay_lag_bytes = 0 لكلتا النسختين # 3. تنفيذ التبديل (يختار النسخة الأكثر تقدمًا كعقدة أساسية جديدة) patronictl -c /etc/patroni/patroni.yml switchover --master db-node-1 --candidate db-node-2 --scheduled now --force # 4. تأكيد القائد الجديد patronictl -c /etc/patroni/patroni.yml list # 5. تأكيد أن HAProxy VIP يشير الآن إلى العقدة الأساسية الجديدة (المنفذ 5000) psql -h vip.db.internal -p 5000 -U app_user -c "SELECT pg_is_in_recovery();" # المتوقع: f (false — العقدة الأساسية الجديدة تؤكد قبول الكتابة)

الفشل التلقائي غير المُخطَّط (انهيار العقدة)

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

# بعد اشتعال التنبيه وفتح حاسوبك المحمول: # 1. ماذا يرى Patroni؟ patronictl -c /etc/patroni/patroni.yml list # ابحث عن قائد جديد؛ إذا لم يكن هناك قائد فقد يكون etcd في حالة تقسيم # 2. تأكيد أن العقدة الأساسية الجديدة تقبل الكتابة psql -h vip.db.internal -p 5000 -U app_user -c " SELECT now(), pg_is_in_recovery(), pg_current_wal_lsn(); " # 3. التحقق من فتحات النسخ المتماثل المرتبطة بالعقدة الميتة psql -h vip.db.internal -p 5000 -U postgres -c " SELECT slot_name, active, pg_size_pretty( pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots WHERE NOT active; " # احذف أي فتحات غير نشطة تعيق استرداد WAL # psql -c "SELECT pg_drop_replication_slot('stale_slot_name');" # 4. إعادة انضمام العقدة المُستردة كنسخة متماثلة (يتولى Patroni هذا تلقائيًا # عند عودة العقدة — يستدعي pg_basebackup داخليًا) # راقب بـ: patronictl -c /etc/patroni/patroni.yml list # 5. تحديث PagerDuty / صفحة الحالة وفتح تذكرة حادثة
فتحات النسخ المتماثل القديمة قنبلة تملأ القرص بصمت. تجبر الفتحة غير النشطة العقدةَ الأساسية على الاحتفاظ بـ WAL إلى أجل غير مسمى للمستهلك الغائب. يمكن أن يمتلئ دليل WAL لقاعدة بيانات بحجم 10 تيرابايت في غضون ساعات إذا تُركت فتحة تشير إلى مستهلك CDC ميت. يجب أن يتضمن دليل التشغيل فحصًا فوريًا للفتحات غير النشطة بعد كل فشل غير مُخطَّط.

الجزء الثاني — إجراءات النسخ الاحتياطي والاسترداد

التحقق من اكتمال النسخة الاحتياطية الليلية

# التحقق من معلومات نسخة pgBackRest الاحتياطية من النسخة المتماثلة (مفضّل — لا تأثير للإدخال/الإخراج على العقدة الأساسية) pgbackrest --stanza=db-primary info # المتوقع في المخرجات: # status: ok # db (current): ... # full backup: timestamp < منذ 25 ساعة # wal archive: min = ... max = [في غضون 5 دقائق من الآن] # إذا كانت النسخة مفقودة أو أرشيف WAL قديم، تحقق من السجل: journalctl -u pgbackrest-backup --since "24 hours ago" | grep -E "ERROR|WARN|P00"

إجراء الاسترداد إلى نقطة زمنية محددة

هذا الإجراء هو الأكثر أهمية والأقل اختبارًا. قم بإجراء تدريب استرداد كامل إلى كلستر تجريبي كل ربع سنة. يجب أن يحتوي دليل التشغيل على الأوامر الدقيقة حتى يستخدم التدريب والاسترداد الحقيقي خطوات متطابقة.

# الاسترداد إلى نقطة زمنية محددة (مثلًا 30 دقيقة قبل الترحيل السيئ) # شغّل هذا على مضيف استرداد منفصل — لا تستعد أبدًا فوق العقدة الأساسية الحية # 1. إيقاف PostgreSQL إذا كان يعمل على مضيف الاسترداد systemctl stop postgresql # 2. مسح دليل البيانات rm -rf /var/lib/postgresql/16/main/* # 3. الاسترداد من أقرب نسخة احتياطية كاملة مع تطبيق WAL حتى الوقت المستهدف pgbackrest --stanza=db-primary \ --delta \ --type=time \ "--target=2025-09-15 03:45:00 UTC" \ --target-action=promote \ restore # 4. تشغيل PostgreSQL — سيُعيد تشغيل WAL حتى الوقت المستهدف ثم يُرقّي نفسه systemctl start postgresql # 5. التحقق من سلامة البيانات عند نقطة الاسترداد psql -U postgres -c "SELECT max(created_at) FROM orders;" # تأكد من أنها متوافقة مع الطابع الزمني المتوقع # 6. تشغيل اختبارات الدخان على التطبيق ضد مضيف الاسترداد قبل التحويل # إذا كانت جيدة، رقّ هذه العقدة وأعد توجيه سلسلة اتصال التطبيق
تدريب الاسترداد الفصلي: جدوِل حدثًا في التقويم كل ربع سنة لتشغيل استرداد من نسخ الإنتاج الاحتياطية إلى نسخة EC2 مؤقتة. احسب الوقت، ووثّق RTO الفعلي، وقارنه بـ SLO الخاص بك. إذا كان SLO الخاص بـ RTO 4 ساعات لكن التدريب يستغرق 5.5 ساعات، فلديك فجوة يجب سدّها قبل الحادثة الحقيقية — لا أثناءها.

الجزء الثالث — إجراء ترحيل المخطط دون توقف

يمر كل ترحيل للمخطط بأربع بوابات قبل الوصول إلى الإنتاج. يُحدد دليل التشغيل ما تتحقق منه كل بوابة.

# البوابة الأولى: مراجعة محلية — هل يتطلب الترحيل قفل جدول كامل؟ # خطير: ALTER TABLE users ADD COLUMN age INT NOT NULL DEFAULT 0; # (يحصل على AccessExclusiveLock على الجدول بأكمله لملء DEFAULT) # آمن: ALTER TABLE users ADD COLUMN age INT; -- ثم الملء دفعةً دفعةً # البوابة الثانية: التطبيق على التجريبي — قس وقت التنفيذ ووقت انتظار القفل psql -U postgres -c " SET lock_timeout = '2s'; -- الفشل السريع إذا كان استعلام آخر يحتجز القفل SET statement_timeout = '30s'; \i migrations/V45__add_age_column.sql " # البوابة الثالثة: التشغيل الجاف في خط CI باستخدام وضع Flyway validate + dryRun flyway -url=jdbc:postgresql://staging-db:5432/app \ -user=flyway \ -password=\${FLYWAY_PASSWORD} \ -dryRunOutput=/tmp/V45_dryrun.sql \ migrate # البوابة الرابعة: تطبيق الإنتاج مع تغيير المخطط عبر الإنترنت للجداول الكبيرة (>50 مليون صف) # استخدم pg_repack للجداول الحساسة للانتفاخ pg_repack --host=vip.db.internal --port=5000 --username=postgres \ --table=users --no-superuser-check # مراقبة انتظارات القفل خلال نافذة الترحيل psql -U postgres -c " SELECT pid, wait_event_type, wait_event, query_start, left(query, 80) FROM pg_stat_activity WHERE wait_event_type = 'Lock' ORDER BY query_start; "

الجزء الرابع — شجرة قرار الحوادث

يجب أن يُقلّل دليل التشغيل العبءَ المعرفي إلى الحد الأدنى خلال الحادثة. تفرض شجرة القرار التفكير المنظّم عندما يُضعفه الأدرينالين.

Database incident decision tree: triage from alert to resolution DB Alert Fires Primary reachable? patronictl list / psql ping Reachable — check metrics lag · connections · locks YES Patroni promoting? check etcd + patronictl NO Repl lag high → parallel workers Conn saturated → PgBouncer tuning YES — wait auto-promote <30s YES NO — manual patronictl failover NO No replica viable? → Restore from pgBackRest Incident resolved → Write postmortem
شجرة قرار الحادثة: الفرز من التنبيه المُشتعل عبر الفشل التلقائي والتشخيص القائم على المقاييس والاسترداد، وصولًا إلى تقرير ما بعد الحادثة.

الجزء الخامس — صيانة دليل التشغيل واختباره

دليل التشغيل الذي لم يُمارَس مؤخرًا هو مسؤولية لا أصل. في كبرى الشركات التقنية، تمتلك أدلة تشغيل قواعد البيانات فريقٌ مُسمًّى، ومُصدَرة في git، ومُراجَعة ربع سنوي، ومُصادَق عليها من خلال أيام اللعب المجدوَلة. يُحدد ما يلي الحد الأدنى من دورية الاختبار لهذا الدليل:

  • شهريًا: تحقق من أن جميع تنبيهات المراقبة تُطلَق بشكل صحيح من خلال تشغيل patronictl pause على العقدة الأساسية في التجريبي والتأكد من اشتعال تنبيه PagerDuty "العقدة الأساسية غير متاحة" في غضون دقيقتين.
  • ربع سنويًا: تدريب استرداد كامل من نسخة pgBackRest الاحتياطية للإنتاج إلى مضيف مؤقت. سجّل RTO وRPO الفعليين بالساعة وقارنهما بـ SLO الموثّقة.
  • قبل كل ترحيل رئيسي: طبّق الترحيل على نسخة تجريبية بحجم الإنتاج. سجّل وقت احتجاز القفل. إذا تجاوز 500 ميللي ثانية، ارفع الأمر إلى استراتيجية تغيير مخطط عبر الإنترنت.
  • بعد كل حادثة: حدّث دليل التشغيل بالخطوات الفعلية المتخذة، وأضف أي وضع فشل جديد اكتُشف، وضع علامة على إيداع git بمعرّف الحادثة.
أهم جملة في أي دليل تشغيل: "تم اختبار هذا الإجراء آخر مرة في [التاريخ] من قِبل [الشخص] واستغرق [المدة] لإتمامه." إذا لم يمكن ملء هذه الجملة، فالإجراء نظري لا تشغيلي. يجب أن يحمل كل قسم في دليل التشغيل هذا الختام.

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