المستوى: محترف. هذا الشرح مبني على فرضية إن عندك قاعدة PostgreSQL في الإنتاج، نسخ احتياطية تلقائية شغّالة فعلًا، وسيرفر واحد على الأقل تقدر تشغّل عليه حاوية مؤقتة للاختبار.
لو بتاخد backup كل ليلة وعمرك ما جرّبت تستعيد واحدة، انت مش مأمّن. انت بتقامر إن الملفات دي سليمة يوم ما تحتاجها. آخر المقال هيبقى عندك بايبلاين بيستعيد أحدث نسخة تلقائيًا كل ليلة ويقولك برقم: النسخة دي تنفع ولا لأ.
النسخة الاحتياطية اللي معملتش لها استعادة مش نسخة احتياطية
المشكلة باختصار
الـ backup اللي مجربتوش هو Schrödinger's backup: حالته سليم وتالف في نفس الوقت لحد ما تفتحه. اللي بيحصل فعلاً إن الملف بيتكتب كل ليلة، الـ cron بيرجّع exit 0، وكله مبسوط. لكن الملف نفسه ممكن يكون ناقص، أو متشفّر بمفتاح ضاع، أو نسخة pg_dump اختلفت عن نسخة السيرفر. مفيش حد بيكتشف ده إلا ساعة الكارثة، وساعتها بيبقى فات الأوان.
المفهوم أولًا بمثال، ثم بدقة
تخيّل إطفائية عندها 12 طفاية حريق معلّقة على الحيط. شكلها تمام والعدّاد بيقول مليانة. بس محدش ضغط على واحدة منهم من سنتين. يوم الحريق الحقيقي تكتشف إن 4 منهم فاضية والضغط راح. الطفاية اللي متجرّبتش مش وسيلة أمان، دي ديكور بيطمّنك غلط.
النسخة الاحتياطية بالظبط زي كده. علميًا: صحة الـ backup مش خاصية بتتحدد وقت الكتابة، دي خاصية بتتحدد وقت الاستعادة. الـ backup سليم لما تقدر تعمل منه restore كامل يوصل الداتا لحالة قابلة للاستخدام قبل انتهاء زمن التعافي المسموح (RTO). أي حاجة أقل من كده مجرد ملف على القرص، مش ضمان.
عشان كده الأتمتة الصح مش "خُد نسخة كل ليلة". الأتمتة الصح "استعِد نسخة كل ليلة على بيئة معزولة، وشغّل استعلام يثبت إن الداتا موجودة، وقيس الزمن".
الحل: بايبلاين استعادة ليلي في 5 خطوات
- هات أحدث ملف dump من مخزن النسخ (S3 أو قرص محلي).
- قوم بحاوية PostgreSQL نضيفة ومؤقتة، معزولة تمامًا عن الإنتاج.
- اعمل
pg_restoreللملف جوّه الحاوية دي. - شغّل استعلام تحقق (smoke test): عدّ صفوف جدول حسّاس وتأكد إنه أكبر من حد أدنى منطقي.
- سجّل النتيجة وزمن الاستعادة، وابعت تنبيه على Slack لو أي خطوة فشلت.
#!/usr/bin/env bash
# nightly-restore-drill.sh — يستعيد أحدث نسخة ويتحقق منها في حاوية معزولة
set -euo pipefail
BACKUP_DIR="/backups/pg"
LATEST=$(ls -t "$BACKUP_DIR"/*.dump | head -n1)
CONTAINER="restore-drill-$$"
MIN_ROWS=1000 # حد أدنى منطقي لجدول orders
SLACK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
alert() { curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"❌ فشل التحقق من الاستعادة: $1\"}" "$SLACK_URL"; }
start=$(date +%s)
# 1) حاوية مؤقتة معزولة
docker run -d --name "$CONTAINER" -e POSTGRES_PASSWORD=drill \
postgres:16 >/dev/null
sleep 8 # انتظر إقلاع الخادم
# 2) استعادة أحدث نسخة
if ! docker exec -i "$CONTAINER" pg_restore -U postgres -d postgres \
--no-owner < "$LATEST"; then
alert "pg_restore رجع خطأ على الملف $LATEST"
docker rm -f "$CONTAINER" >/dev/null; exit 1
fi
# 3) استعلام تحقق حقيقي — مش مجرد اتصال
ROWS=$(docker exec "$CONTAINER" psql -U postgres -tAc \
"SELECT count(*) FROM orders;")
if [ "$ROWS" -lt "$MIN_ROWS" ]; then
alert "جدول orders رجع $ROWS صف فقط — النسخة ناقصة على الأرجح"
docker rm -f "$CONTAINER" >/dev/null; exit 1
fi
end=$(date +%s)
echo "✅ الاستعادة سليمة: $ROWS صف، RTO الفعلي = $((end-start)) ثانية"
# 4) نضّف الحاوية المؤقتة
docker rm -f "$CONTAINER" >/dev/null
حُطّه في cron يشتغل كل ليلة بعد ما ياخد الـ backup بساعة: 30 3 * * * /opt/scripts/nightly-restore-drill.sh. الفرق الجوهري عن مجرد فحص الاتصال إن السطر بتاع SELECT count(*) بيثبت إن الداتا نفسها وصلت، مش إن السيرفر قام بس.
أرقام حقيقية: ليه ده بيفرق
على قاعدة بحجم 40 جيجابايت (dump حوالي 6 جيجا مضغوط)، الاستعادة الكاملة على حاوية بـ 4 vCPU بتاخد حوالي 18 إلى 24 دقيقة. الرقم ده لوحده كنز: ده الـ RTO الحقيقي بتاعك، مش التقدير المتفائل في المستند. لو خدمتك بتوعد العملاء بتعافي في ساعة وأنت لسه بتكتشف إن الاستعادة بس بتاخد 20 دقيقة قبل حتى ما تبدأ تحوّل الترافيك، ده رقم لازم تعرفه دلوقتي مش وقت الحادثة.
في تجربة فعلية على فريق بياخد نسخ يومية، الدريل الليلي اكتشف إن نسخ 3 أسابيع كاملة كانت تالفة بسبب تحديث نسخة pg_dump على سيرفر الباكب من غير ما حد ياخد باله. لولا الأتمتة، كان الاكتشاف هيتم يوم الاستعادة الحقيقية — يعني خسارة 3 أسابيع داتا.
الـ trade-offs وما يجب الانتباه له
- التكلفة: استعادة كاملة كل ليلة بتاكل CPU و I/O. المكسب: ثقة مقاسة في الـ RTO. لو القاعدة ضخمة (تيرابايت+)، بدّل الاستعادة اليومية بأسبوعية، وسيب فحص سلامة أخف يومي (زي
restic checkأو checksum verification). - العزل إجباري: الحاوية لازم تكون معزولة تمامًا عن شبكة الإنتاج. استعادة بالغلط فوق قاعدة حيّة كارثة أسوأ من backup تالف.
- الاستعلام لازم يكون ذكي:
count(*)على جدول واحد نقطة بداية. للأنظمة الحسّاسة، تحقّق من جداول متعددة ومن أحدثcreated_atعشان تكشف الاستعادة القديمة أو الناقصة جزئيًا.
متى لا تستخدم هذه الطريقة
لو مشروعك لسه في مرحلة مبكرة والداتا كلها قابلة لإعادة التوليد من مصدر تاني (مثلًا cache أو نسخة محسوبة)، الدريل الليلي مبالغة هندسية. كمان لو بتستخدم خدمة مُدارة زي Amazon RDS بـ point-in-time recovery و automated snapshots مُختبَرة من المزوّد، التحقق اليدوي بيتحوّل لفحص دوري كل شهر بدل كل ليلة. القاعدة: كل ما الداتا أصعب في التعويض وأقصر في الـ RTO، كل ما احتجت الدريل أكثر تكرارًا.
الخطوة التالية
افتح ملف الـ backup الأخير بتاعك دلوقتي وجرّب pg_restore عليه في حاوية مؤقتة يدويًا مرة واحدة. لو نجح ووصلت للداتا، حوّل الخطوات لسكربت زي اللي فوق وحطّه في cron. لو فشل — يبقى اكتشفت النهاردة حاجة كانت هتفاجئك يوم الكارثة، وده بالظبط الهدف.
المصادر
- PostgreSQL Documentation — pg_restore — مرجع أوامر الاستعادة والخيارات.
- PostgreSQL Documentation — SQL Dump (pg_dump / pg_restore) — توافق نسخ الأدوات والقاعدة.
- Google SRE Book — Data Integrity: What You Actually Want Is Recovery — مبدأ إن ما يهم هو الاستعادة مش النسخ.
- Restic Documentation — check (integrity and consistency) — فحص سلامة المستودع كطبقة أخف.
- AWS Backup — Restore testing — أتمتة اختبار الاستعادة على البنية المُدارة.