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

نسخ احتياطية تُستعاد فعلًا

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

نسخ احتياطية تُستعاد فعلًا

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

النسخة الاحتياطية الوحيدة التي تُعدّ هي تلك التي استعدتها بنجاح. تُجري فرق Google SRE تدريبات استعادة بانتظام. تشمل هندسة الفوضى في Netflix صراحةً سيناريوهات استرداد قواعد البيانات. إذا لم تستطع إثبات استعادة ضمن وقت RTO لديك، فاستراتيجية النسخ الاحتياطي لديك غير مكتملة.

النسخ الاحتياطي المنطقي مقابل الفيزيائي

كل آلية نسخ احتياطي تقع ضمن إحدى فئتين. اختيار الفئة الخاطئة لوضعك هو خطأ إنتاجي تكتشفه في أسوأ وقت ممكن.

النسخ الاحتياطي المنطقي يُصدِّر قاعدة البيانات كجمل SQL — CREATE TABLE وINSERT INTO وما شابه. pg_dump لـ PostgreSQL وmysqldump لـ MySQL هما الأدوات المعيارية. إنها محمولة (تستعيدها على نظام تشغيل مختلف أو إصدار ثانوي مختلف)، وقابلة للقراءة البشرية للتدقيق، وانتقائية (تفريغ جدول أو مخطط واحد). المقايضة: إنها بطيئة الاستعادة على نطاق واسع لأن قاعدة البيانات يجب أن تُعيد تنفيذ كل جملة SQL، وتُعيد بناء كل فهرس، وتُطبّق كل قيد. يمكن أن يستغرق تفريغ منطقي بحجم 500 جيجابايت من 4 إلى 6 ساعات للاستعادة. هذا مقبول لسير عمل تحديث يومي للتطوير؛ لكنه كارثي أثناء حادثة إنتاجية حيث RTO لديك هو 30 دقيقة.

# PostgreSQL logical backup — dump in custom format (compressed, parallel-restorable) pg_dump \ --host=db-primary.internal \ --port=5432 \ --username=backup_user \ --format=custom \ --compress=9 \ --jobs=4 \ --file=/backups/mydb_$(date +%Y%m%d_%H%M%S).dump \ mydb # Restore a custom-format dump with parallel restore workers pg_restore \ --host=db-restore-target.internal \ --port=5432 \ --username=postgres \ --dbname=mydb \ --jobs=8 \ --verbose \ /backups/mydb_20250801_020000.dump

النسخ الاحتياطي الفيزيائي ينسخ ملفات البيانات الخام — الصفحات والكتل ومقاطع WAL التي تُشكّل قاعدة البيانات على القرص. الأدوات: pg_basebackup لـ PostgreSQL، وPercona XtraBackup لـ MySQL/InnoDB، ولقطات مستوى نظام الملفات (لقطة EBS، وZFS send، ولقطة LVM). النسخ الاحتياطية الفيزيائية سريعة الاستعادة لأنك تنسخ بايتات لا تُعيد تنفيذ SQL. قد تُستعاد نسخة احتياطية فيزيائية بحجم 500 جيجابايت في 20 إلى 40 دقيقة مع شبكة سريعة. المقايضة: إنها غير محمولة عبر الإصدارات الرئيسية ولا يمكنها استعادة جدول واحد انتقائيًا دون استخراجه أولًا في تفريغ منطقي.

# PostgreSQL physical base backup — streams WAL continuously during backup pg_basebackup \ --host=db-primary.internal \ --port=5432 \ --username=replication_user \ --pgdata=/backups/basebackup_$(date +%Y%m%d) \ --format=tar \ --compress=lz4 \ --wal-method=stream \ --checkpoint=fast \ --progress \ --verbose # Result: base.tar.lz4 (data files) + pg_wal.tar.lz4 (WAL from start of backup) # These two together form a consistent snapshot of the database.

في الممارسة العملية، تستخدم استراتيجية النسخ الاحتياطي الناضجة كلتيهما: النسخ الاحتياطية الفيزيائية للقاعدة (استعادة سريعة، RTO صغير)، والنسخ المنطقية لاستعادة الكائنات الانتقائية والتوافق عبر الإصدارات.

الاسترداد في نقطة زمنية (PITR)

تلتقط النسخة الاحتياطية الأساسية لقطة متسقة في لحظة واحدة. لكن ماذا لو تلفت البيانات أو حُذفت بعد ثلاث ساعات من تلك اللقطة؟ هنا يصبح PITR ضروريًا. يتيح لك PITR استعادة قاعدة البيانات إلى أي نقطة زمنية تعسفية — ليس فقط "كما في آخر نسخة احتياطية" بل "كما كانت في 14:27:43 يوم الثلاثاء، قبل تنفيذ DELETE الخاطئ."

يعمل PITR بدمج نسخة احتياطية أساسية مع تدفق مستمر من مقاطع Write-Ahead Log (WAL). كل كتابة إلى قاعدة البيانات — إدراج، تحديث، حذف، DDL — تُسجَّل أولًا كسجل WAL قبل أن تلمس ملفات البيانات. إذا أرشفت هذه المقاطع باستمرار، يمكنك إعادة تشغيلها فوق نسخة احتياطية أساسية لإعادة بناء قاعدة البيانات في أي نقطة بين وقت النسخة الاحتياطية الأساسية والآن.

Point-In-Time Recovery — base backup plus continuous WAL archiving Base Backup 02:00 WAL 000001 WAL 000002 WAL 000003 Bad DELETE 14:27 WAL 000004 Restore Target 14:27:42 Replay WAL 000001 → 000003 (stop before 14:27:43) WAL Archive S3 / GCS / pgBackRest Time ————————————————————————————————>
PITR: استعادة نسخة احتياطية أساسية، ثم إعادة تشغيل مقاطع WAL المؤرشفة حتى الطابع الزمني المستهدف بالضبط — مع التوقف قبل الحدث المدمر.

لتفعيل PITR في PostgreSQL، يجب ضبط أرشفة WAL قبل الحاجة إليها. تفعيلها بعد الكارثة متأخر جدًا:

# postgresql.conf — enable WAL archiving (must be set before disaster strikes) wal_level = replica # or logical; must be replica+ for archiving archive_mode = on archive_command = 'pgbackrest --stanza=mydb archive-push %p' # pgBackRest handles compression, deduplication, and S3/GCS upload # recovery.conf (PostgreSQL 11 and below) OR postgresql.conf (12+) restore_command = 'pgbackrest --stanza=mydb archive-get %f "%p"' recovery_target_time = '2025-08-05 14:27:42' recovery_target_action = 'promote' # make writable once target is reached # Start PostgreSQL in recovery mode — it will replay WAL until the target time
استخدم pgBackRest أو Barman بدلًا من سكريبتات archive_command الخام. تتعامل هذه الأدوات مع إزالة تكرار WAL، والنقل المتوازي، وتحقق التكامل بالمجاميع الاختبارية، وتدوير النسخ الاحتياطية، والاستعادة المباشرة من S3/GCS. إنشاء أرشيف بسكريبت shell خاص بك يعني أنك مسؤول عن كل ذلك أيضًا — وستكتشف الثغرات أثناء حادثة.

تخزين النسخ الاحتياطي: تطبيق قاعدة 3-2-1

قاعدة 3-2-1: احتفظ بـ 3 نسخ، على 2 نوعي وسائط مختلفة، مع 1 نسخة خارج الموقع. بالنسبة لقواعد البيانات يُترجم هذا بشكل ملموس: نسخ احتياطية كاملة ليلية محتفظ بها 30 يومًا، وأرشيفات WAL محتفظ بها 7 أيام على الأقل (أطول للامتثال)، مُنسوخة إلى منطقة AWS مختلفة أو مزود سحابي مختلف. في Amazon وGoogle، تُنسَخ قواعد البيانات الإنتاجية احتياطيًا إلى موقعين جغرافيين منفصلين على الأقل. استراتيجية النسخ الاحتياطي لمنطقة واحدة تفشل مع نفس الكارثة التي أسقطت قاعدتك الأساسية.

لا تخزّن النسخ الاحتياطية على نفس الخادم الذي يستضيف قاعدة البيانات. عطل القرص، أو برنامج الفدية، أو rm -rf العرضي الذي يضرب قاعدتك الأساسية سيضرب نسختك الاحتياطية المجاورة في الوقت نفسه. أرسل النسخ الاحتياطية دائمًا إلى نظام تخزين منفصل — تخزين الكائنات السحابي (S3، GCS) مع تمكين الإصدارات وحذف MFA هو الحد الأدنى المقبول.

اختبار الاستعادة: الممارسة التي تنقذك

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

  1. التحقق الآلي الأسبوعي من الاستعادة — استعادة أحدث نسخة احتياطية إلى نسخة مؤقتة معزولة، وتشغيل مقارنة مجموع اختباري أو عدد صفوف مقابل خط أساس معروف، ثم تفكيكها. يستغرق هذا 20 دقيقة ويعمل دون رقابة عبر مهمة CI أو cron.
  2. تدريب PITR الكامل الشهري — الاستعادة إلى طابع زمني محدد، والتحقق من صحة التطبيق (ليس فقط اتصال قاعدة البيانات)، وتأكيد أن البيانات المستعادة تطابق ما توقعته في تلك النقطة الزمنية. هذا يكتشف فجوات أرشيف WAL التي يفوّتها فحص العدد الآلي.
  3. قياس RTO الفصلي — محاكاة فشل كامل للقاعدة الأساسية، والاستعادة من الحالة الباردة، وتوقيت كل خطوة من البداية للنهاية. قارن مع RTO الموثق لديك. إذا تجاوزته، فقد وجدت ثغرة في العملية قبل أن تكلفك خرقًا في اتفاقية مستوى الخدمة.
#!/bin/bash # restore-test.sh — weekly automated restore validation (runs in CI or cron) set -euo pipefail STANZA="mydb" RESTORE_HOST="restore-sandbox.internal" RESTORE_PORT="5433" RESTORE_PATH="/var/lib/postgresql/15/restore_test" LOG="/var/log/backup-restore-test.log" echo "[$(date)] Starting restore validation" | tee -a "$LOG" # 1. Restore latest backup to sandbox instance pgbackrest \ --stanza="$STANZA" \ --pg1-path="$RESTORE_PATH" \ --pg1-port="$RESTORE_PORT" \ --recovery-option="recovery_target_action=promote" \ restore | tee -a "$LOG" # 2. Start PostgreSQL on the restored data pg_ctl -D "$RESTORE_PATH" -o "-p $RESTORE_PORT" start # 3. Run integrity checks EXPECTED_USERS=142850 ACTUAL_USERS=$(psql -h localhost -p "$RESTORE_PORT" -U postgres -At \ -c "SELECT COUNT(*) FROM users;") if [ "$ACTUAL_USERS" -lt "$((EXPECTED_USERS - 100))" ]; then echo "[FAIL] User count $ACTUAL_USERS is below threshold" | tee -a "$LOG" curl -s -X POST "$PAGERDUTY_WEBHOOK" \ -d "{\"summary\": \"Restore test FAILED: user count $ACTUAL_USERS\"}" exit 1 fi echo "[PASS] Restore validated — $ACTUAL_USERS users. Tearing down." | tee -a "$LOG" pg_ctl -D "$RESTORE_PATH" stop rm -rf "$RESTORE_PATH"
أنذِر عند فشل مهام النسخ الاحتياطي، لا فقط عند فشل الاستعادة. إذا فشلت نسختك الاحتياطية الليلية بصمت لمدة أسبوع ثم وقعت كارثة، فلديك صفر نقاط استرداد صالحة. راقب مخرجات pgbackrest info في منصة مراقبتك وأرسل تنبيهًا إذا كانت آخر نسخة احتياطية ناجحة أقدم من 25 ساعة.

المقاييس الرئيسية للمتابعة

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

  • RPO (هدف نقطة الاسترداد): الحد الأقصى المقبول لفقدان البيانات. إذا كان RPO لديك ساعة واحدة، يجب ألا يتأخر أرشيف WAL عن ذلك.
  • RTO (هدف وقت الاسترداد): المدة التي قد تستغرقها الاستعادة الكاملة. قِسه في التدريبات؛ لا تعتمد أبدًا على تقدير.
  • عمر النسخة الاحتياطية: الوقت منذ اكتملت آخر نسخة احتياطية ناجحة. أنذر عند 25 ساعة للنسخ اليومية.
  • تأخر أرشيف WAL: مدى تأخر الأرشيف عن القاعدة الأساسية. أنذر عند 15 دقيقة لأهداف RPO أقل من ساعة.
  • معدل نجاح اختبار الاستعادة: نسبة الاختبارات الأسبوعية الآلية التي تنجح. أي شيء أقل من 100% يمثل حادثة محجوزة في انتظار الوقوع.

استراتيجية النسخ الاحتياطي التي لا تستطيع إثبات هذه الأرقام في دليل التشغيل هي تطلعية لا تشغيلية. يتلخص هدف هذا الدرس كله في جملة واحدة: اعرف RPO وRTO لديك، وأتمت النسخ الاحتياطي، وأتمت اختبار الاستعادة، وقِس كليهما باستمرار.