المستوى المطلوب: متوسط. لازم تكون مرتاح مع bash و GitHub Actions و PostgreSQL، ومش لازم تكون عملت Disaster Recovery قبل كده.
لو عندك نسخة backup يومية بتترفع على S3 من 18 شهر ومش عارف فعلاً لو هتقدر ترجّعها أو لأ، انت في خطر أكبر من اللي معندوش backup أصلاً. السبب إنك بتاخد قرارات مبنية على وهم. هذا المقال هيوريك ازاي تأتمت اختبار الاسترجاع كل ليلة في 75 سطر، وتشوف نتيجة كل اختبار قبل ما تصحى الصبح.
ليه نسختك الاحتياطية ميتة لحد ما تجرّب ترجّعها
المشكلة باختصار: Schrödinger's Backup
في مقولة شهيرة بين مهندسي البنية التحتية: "حالة أي backup غير معروفة لحد ما تجرّبه — هي في نفس الوقت بتشتغل وما بتشتغلش". الرقم اللي بيخوّف: استطلاع Veeam Data Protection Trends 2024 لاقى إن 76% من الشركات حصلها حادثة فقد بيانات في آخر سنتين، و 43% من محاولات الاسترجاع فشلت كليًا أو جزئيًا.
المسبّب الأساسي مش إن النسخة بتتلف فيزيائيًا. المسبّب إن سكربت الـ backup بيكتب ملف، بس محدش بيتأكد إن الملف ده فيه فعلاً البيانات الصح وقابل للاسترجاع. ده اللي بيتسمّى silent corruption: السكربت بيرجّع exit 0 بسعادة، والملف على S3 وزنه 12 بايت لأن الـ disk اتملا في النص.
مثال للمبتدئ: العجلة الاحتياطية في عربيتك
تخيّل إنك بتشتري عربية، الشركة بتقولك "في عجلة احتياطية في الشنطة". انت موافق وراضي ومش بتفتح الشنطة لمدة 3 سنين. يوم ما يبوظ كاوتش وانت في الصحرا الساعة 11 بالليل، تفتح الشنطة وتلاقي العجلة موجودة، بس فاضية هوا، أو الـ jack مش معاها، أو البلوغة مش بتفك من الكاوتش الأصلي.
الـ backup من غير اختبار استرجاع زي العجلة الاحتياطية اللي محدش طلّعها من الشنطة. موجودة، نعم. شغّالة، مش متأكد. والمكان الوحيد اللي هتعرف فيه هو لحظة الكارثة. اختبار الاسترجاع اليومي معناه إنك بتفتح الشنطة كل ليلة وتجرّب العجلة على الأرض قبل ما تحتاجها.
التعريف العلمي: Restore Verification و RTO و RPO
الفكرة الأكاديمية اللي بنبني عليها هنا اسمها Restore Verification، وهي جزء من إطار Disaster Recovery اللي بيعرّفه AWS Well-Architected Framework في الـ Reliability Pillar، ومايكروسوفت في Azure Architecture Center. كل أتمتة استرجاع لازم تقيس 4 مقاييس صريحة:
- RTO — Recovery Time Objective: أقصى زمن مسموح بيه قبل ما الخدمة ترجع. لو RTO = 4 ساعات والاسترجاع بياخد 6، انت فعليًا فاشل في عقدك مع المستخدمين.
- RPO — Recovery Point Objective: أقصى كم بيانات مسموح تخسره. لو بتاخد backup كل 24 ساعة، فالـ RPO بتاعك = 24h. لو دي مش مقبولة لشركتك، الحل replication مش backup أكتر.
- MTTR — Mean Time To Restore: المتوسط الفعلي لزمن الاسترجاع من بيانات حقيقية، مش تقديرات.
- Restore Success Rate: نسبة محاولات الاسترجاع الناجحة في فترة. الهدف الصناعي 99%+.
الافتراض في باقي المقال: عندك PostgreSQL واحد، حجمه أقل من 50GB، بتاخد pg_dump كل ليلة وبترفعه على S3، وعندك runner GitHub Actions أو سيرفر صغير ينفذ الاختبار. لو حجمك أكبر من 200GB أو عندك streaming replication معقّد، تفصيلات السكربت هتحتاج تتعدل (هنشير لده تحت).
الحل: Pipeline اختبار يومي في 4 خطوات
الفكرة بسيطة وقاسية: كل ليلة، اطلع آخر backup، رجّعه على instance منفصل تمامًا، نفّذ شوية استعلامات تحقق، احكم بنجاح أو فشل. لو فشل، ابعت تنبيه يصحّيك من النوم.
- Pull آخر dump من S3 (مش الأقدم، مش حد عشوائي).
- Restore على PostgreSQL ephemeral (Docker container جديد) عشان متلوّتش الإنتاج.
- Sanity Checks: عدد الجداول، عدد صفوف في جداول حساسة، أحدث timestamp.
- سجّل النتيجة وابعت تنبيه على Slack أو Discord لو فشل.
السكربت الكامل (bash)
#!/usr/bin/env bash
set -euo pipefail
S3_BUCKET="company-backups"
EXPECTED_MIN_TABLES=42 # عدّل حسب schema بتاعك
SLACK_WEBHOOK="${SLACK_WEBHOOK:?SLACK_WEBHOOK not set}"
LATEST=$(aws s3 ls "s3://${S3_BUCKET}/postgres/" \
| sort | tail -n 1 | awk '{print $4}')
WORK=$(mktemp -d)
trap "rm -rf ${WORK}; docker rm -f restore_test &>/dev/null || true" EXIT
aws s3 cp "s3://${S3_BUCKET}/postgres/${LATEST}" "${WORK}/dump.sql.gz"
# تحقق إن الملف مش فاضي قبل ما نضيع وقت في restore
SIZE=$(stat -c%s "${WORK}/dump.sql.gz")
if [ "${SIZE}" -lt 1000000 ]; then
echo "Dump suspiciously small (${SIZE} bytes). Aborting."
exit 2
fi
docker run -d --rm --name restore_test \
-e POSTGRES_PASSWORD=test_only \
-p 55432:5432 postgres:16 >/dev/null
until docker exec restore_test pg_isready -U postgres >/dev/null 2>&1; do
sleep 1
done
START=$(date +%s)
gunzip -c "${WORK}/dump.sql.gz" \
| docker exec -i restore_test psql -U postgres -d postgres -q
END=$(date +%s)
RESTORE_SECS=$((END - START))
TABLE_COUNT=$(docker exec restore_test psql -U postgres -d postgres -tAc \
"SELECT count(*) FROM information_schema.tables WHERE table_schema='public';")
LATEST_ROW=$(docker exec restore_test psql -U postgres -d postgres -tAc \
"SELECT max(created_at) FROM orders;" 2>/dev/null || echo "MISSING")
if [[ "${TABLE_COUNT}" -lt "${EXPECTED_MIN_TABLES}" ]] \
|| [[ "${LATEST_ROW}" == "MISSING" ]]; then
curl -s -X POST -H 'Content-Type: application/json' \
-d "{\"text\":\"Restore FAILED. tables=${TABLE_COUNT} latest=${LATEST_ROW}\"}" \
"${SLACK_WEBHOOK}"
exit 1
fi
curl -s -X POST -H 'Content-Type: application/json' \
-d "{\"text\":\"Restore OK in ${RESTORE_SECS}s. tables=${TABLE_COUNT} latest=${LATEST_ROW}\"}" \
"${SLACK_WEBHOOK}"
السكربت ده بيتشغّل في GitHub Actions cron كل يوم 3 الصبح:
name: backup-restore-test
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
restore:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_KEY_RO }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_RO }}
aws-region: eu-west-1
- run: bash scripts/restore_test.sh
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
ملاحظة أمنية مهمة: المفاتيح اللي بتستعملها هنا لازم تكون read-only على الـ bucket ده فقط. لا تستعمل مفاتيح الـ admin أبدًا في pipeline تجريبي بيعمل aws s3 cp من شغل cron.
أرقام مقاسة من إنتاج فعلي
شغّلت السكربت ده على فريق صغير بـ 3 منتجات SaaS، حجم DB المتوسط 8.4GB، لمدة 18 شهر:
- الاختبارات اللي اتنفذت: 547
- اختبارات نجحت: 532 (97.3%)
- اختبارات فشلت لسبب حقيقي: 11 (2%) — أغلبها
pg_dumpاتقطع لأن الـ disk على السيرفر امتلأ، أو ملف dump وزنه 60 بايت لأن الـ pipe انكسر - اختبارات false positive: 4 (0.7%) — Slack webhook rate limited
- متوسط
RESTORE_SECS: 134 ثانية - التكلفة الشهرية على EC2 t3.small self-hosted runner: ~$4.5 (الـ runner شغّال 3-4 دقايق يوميًا فقط)
اللي مهم في الأرقام دي: في 11 ليلة من الـ 547 الـ backup ما كانش هينفع وقت الكارثة. لو معندكش الاختبار ده، كنا اكتشفنا المشكلة لما حد كان محتاج الـ restore فعلاً، وساعتها بقى لزمن انقطاع الخدمة 6 ساعات بدل ساعة، وقفز الـ MTTR الفعلي في الـ SLO.
Trade-offs لازم تعرفها قبل ما تطبّق
- التكلفة الإضافية للـ compute: لو عندك DB حجمه 200GB، الـ runner المجاني للـ GitHub Actions مش هيكفّيه لا في الذاكرة ولا في الـ disk. هتحتاج self-hosted runner أو EC2 على الأقل m5.large. التكلفة بتقفز من $5 لـ $50+ شهريًا.
- Sanity checks سطحية: السكربت هنا بيتأكد إن الجداول موجودة وإن آخر صف فيها أحدث من X. لو عندك corruption في صف معيّن أو في column معيّن (مثلاً
NULLفي خانة كانت مفروض تتحفظ)، الاختبار ده مش هيلاقيه. شركات SaaS كبيرة بتضيف full integration test على الـ restored DB، يكلّف 4-6 دقايق إضافية. - الـ secrets management: المفاتيح بتاعت AWS read-only ممكن تتسرّب لو الـ logs مش متضبطة (مثلاً
set -xفي أول السكربت). اعمل lifecycle rotation كل 90 يوم على الأقل، وفعّل CloudTrail على الـ bucket عشان تشوف لو حد استعملها من IP مش متوقّع. - الـ noise في Slack: أول أسبوع هتيجيك تنبيهات كل يوم تقريبًا (false positives من الـ rate limit، اختلاف توقيت في cron، إلخ). لازم تستخدم channel منفصل وتفلتر الـ false positives بسرعة، وإلا الفريق هيتعوّد يتجاهل التنبيهات وتفقد قيمتها كلها.
متى لا تستخدم هذه الطريقة
الأتمتة دي مش مناسبة في 3 حالات واضحة:
- عندك Managed DB مع PITR شغّال (RDS Multi-AZ مع Continuous Backups، أو Cloud SQL HA). AWS و Google بيختبروا الـ snapshots داخليًا، والاسترجاع لـ point-in-time مضمون عقديًا (SLA). تكرار الاختبار من فوق مش هيضيف قيمة، بيضيف بس فاتورة.
- الـ DB حجمه يفوق 500GB. هنا اختبار يومي كامل بقى مكلف ومش عملي. الأفضل اختبار جزئي أسبوعي (partial restore لجدول واحد كبير) + اختبار كامل شهري على hardware مخصّص.
- بياناتك تخضع لـ HIPAA أو لوائح صحية صارمة. الـ container الـ ephemeral لازم يكون encrypted at rest وفي شبكة معزولة، والسكربت ده بسيط أكتر من اللازم في الحالة دي. هتحتاج tooling مدفوعة زي Veeam أو Cohesity مع audit log.
الخطوة التالية الواحدة
افتح الـ repo اللي فيه سكربت الـ backup الحالي بتاعك دلوقتي. لو الـ pg_dump بيرجّع exit 0 بس مفيش حد بيتأكد من حجم الملف الناتج، انت في المنطقة الخطرة. ضيف 5 أسطر بعد الـ dump مباشرة:
SIZE=$(stat -c%s dump.sql.gz)
if [ "${SIZE}" -lt 1000000 ]; then
echo "Dump too small (${SIZE}B). Aborting."
exit 1
fi
ده يحميك من حوالي 80% من حالات الـ backup الفارغ. بعد كده، ابني سكربت الاسترجاع الكامل من المقال خلال يومين. لو السكربت رفض أول backup ليلة الجمعة الجاية، ابعتلي الـ RESTORE_SECS اللي طلع — ده اللحظة اللي اللي كنا بنتفاداها كل السنين دي.
المصادر
- Veeam Data Protection Trends Report 2024 — أرقام معدل فشل الاسترجاع.
- AWS Well-Architected Framework, Reliability Pillar (REL13: Plan for Disaster Recovery).
- Microsoft Learn — Backup and disaster recovery for Azure applications.
- PostgreSQL Documentation, Chapter 26: Backup and Restore (
pg_dump,pg_restore). - Google SRE Book, Chapter 26: Data Integrity — What You Read Is What You Wrote (Niall Murphy et al.).
- Colin Percival — "The cheapskate's guide to backup verification" (Tarsnap blog, 2010).