مقالات معمّقة حول التكنولوجيا التي تشكّل المستقبل.

الهندسة الدفاعية: أنظمة تقاوم الكوارث

تعرّف على أنماط الهندسة الدفاعية التي تمنع كوارث الإنتاج: molly guard وأوضاع dry-run والحذف الناعم، ولماذا تفشل نوافذ التأكيد.

زر طوارئ أحمر محمي بغطاء بلاستيكي شفاف قابل للفتح على لوحة فولاذية

عمل مهندس مبتدئ في شركة فنتيك متوسطة الحجم على تشغيل سكربت ترحيل لقاعدة بيانات بعد ظهر يوم جمعة. كان السكربت مفترضاً أن ينظف السجلات اليتيمة في بيئة الـ staging. لكنه بدلاً من ذلك اتصل بقاعدة بيانات الإنتاج وحذف 4.2 مليون سجل لمعاملات العملاء. والنسخة الاحتياطية؟ عمرها ثلاثة أيام. قضت الشركة الـ72 ساعة التالية في استجابة كاملة للحادثة، تعيد بناء السجلات يدوياً من سجلات مزود الدفع. وتجاوزت التكلفة الإجمالية 400 ألف دولار بين وقت الهندسة وتعويضات العملاء وتقارير الجهات التنظيمية.

لم يكن في السكربت أي فحص للبيئة، ولا علم dry-run، ولا نافذة تأكيد تُظهر أي قاعدة بيانات يتصل بها. جاءت سلسلة الاتصال من متغير بيئة صادف أنه مضبوط على الإنتاج في حاسوب المهندس بعد جلسة تصحيح قبل أسبوعين. وكل واحد من هذه الإخفاقات كان يمكن تجنبه.

لماذا لا تنفع نوافذ «هل أنت متأكد؟»

أكثر آلية أمان شيوعاً في البرمجيات هي نافذة التأكيد، وهي أيضاً الأقل فائدة. تشير دراسات «إرهاق التأكيد» إلى أن المستخدمين يتجاوزون رسائل «هل أنت متأكد؟» تقريباً بنسبة 100% بعد مواجهتها أكثر من بضع مرات. تصبح الرسالة غير مرئية، مجرد نقرة أخرى في الطريق إلى ما قررت فعله أصلاً.

المشكلة ليست أن التأكيد فكرة سيئة، بل أن التأكيدات العامة لا تحمل أي معلومة. عبارة «هل تريد المتابعة بالتأكيد؟» لا تخبرك بشيء عن ما ستفعله. قارن ذلك برسالة مثل: «أنت على وشك حذف 4,217,893 صفاً من جدول transactions في prod-us-east-1. اكتب اسم قاعدة البيانات للتأكيد.» النسخة الثانية تجبرك فعلاً على قراءة ما يحدث. وهذا هو الفرق بين مطبّ إبطاء وغطاء حماية من نوع molly guard.

ما هو molly guard ولماذا يهم

يأتي مصطلح «molly guard» من غطاء بلاستيكي مادي يوضع فوق الزر الأحمر الكبير في الحواسيب المركزية لمنع الإيقاف غير المقصود، ويُقال إنه سُمّي تيمناً بابنة صغيرة لأحد المبرمجين اسمها مولي كانت تضغط عليه باستمرار. في البرمجيات، الـ molly guard هو أي آلية تجعل الإجراءات المدمرة صعبة الحدوث بالخطأ مع بقائها ممكنة عند القصد.

حزمة لينكس molly-guard تفعل ذلك تماماً. تعترض أوامر shutdown وreboot وhalt في جلسات SSH، وتطلب منك كتابة اسم المضيف للجهاز الذي على وشك إيقافه. لا يمكنك المرور عبرها بشكل تلقائي، بل يجب أن تثبت أنك تعرف على أي جهاز أنت.

$ sudo reboot
Good grief! You're on a remote SSH session to prod-web-03.
Please type the hostname to confirm: prod-web-03
# Compare with the useless version:
$ sudo reboot
Are you sure? (y/n): y
# *types y without reading, muscle memory*

المبدأ بسيط: يجب أن يتطلب التأكيد معلومة تثبت أن المشغّل يفهم الإجراء. كتابة «y» لا تثبت شيئاً. أما كتابة اسم المضيف أو اسم الجدول أو عدد السجلات المتأثرة فتثبت أنك قرأت التحذير.

أوضاع Dry-Run: اعرض قبل أن تنفّذ

يجب أن تكون لكل عملية مدمرة وضع dry-run. ليس «ينبغي» من باب أفضل الممارسات، بل «ينبغي» بمعنى أنك ستندم لاحقاً إن لم تكن موجودة. تنفّذ عملية dry-run المنطق الكامل للعملية، وتسجّل بالضبط ما سيحدث، ثم تتوقف. لا آثار جانبية، ورؤية كاملة.

النمط سهل التطبيق. إليك سكربت ترحيل مع dry-run مدمج:

#!/bin/bash
set -euo pipefail
DRY_RUN=${DRY_RUN:-true}  # Default to dry-run!
DB_HOST=$(get_db_host)
DB_NAME=$(get_db_name)
echo "Target: $DB_HOST / $DB_NAME"
echo "Mode: $([ "$DRY_RUN" = true ] && echo 'DRY RUN' || echo 'LIVE')"
ROWS=$(psql -h "$DB_HOST" -d "$DB_NAME" -t -c \
"SELECT count(*) FROM orphaned_records WHERE created_at < now() - interval '90 days'")
echo "Records to delete: $ROWS"
if [ "$DRY_RUN" = true ]; then
echo "Dry run complete. Set DRY_RUN=false to execute."
exit 0
fi
# Require explicit confirmation for live runs
read -p "Type '$DB_NAME' to confirm deletion: " CONFIRM
if [ "$CONFIRM" != "$DB_NAME" ]; then
echo "Aborted."
exit 1
fi
psql -h "$DB_HOST" -d "$DB_NAME" -c \
"DELETE FROM orphaned_records WHERE created_at < now() - interval '90 days'"
echo "Deleted $ROWS records."

لاحظ ثلاثة أمور: القيمة الافتراضية هي dry-run (يجب أن تختار الدخول في التدمير، لا الخروج منه)، ويعرض قاعدة البيانات المستهدفة وعدد الصفوف قبل أي شيء، وحتى في الوضع الحي يتطلب كتابة اسم قاعدة البيانات. ثلاث طبقات دفاع. أي واحدة منها كانت ستمنع الحادثة التي وصفتها في البداية.

شبكات أمان لترحيلات قواعد البيانات

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

إليك تسلسل آليات الأمان، من الأساسية إلى المحكمة:

  1. فحوصات البيئة — يتحقق سكربت الترحيل من أنه يستهدف البيئة المقصودة قبل التنفيذ. يبدو بديهياً، وستتفاجأ من كثرة غيابه.
  2. لقطات ما قبل الترحيل — إنشاء لقطة لقاعدة البيانات تلقائياً قبل أي ترحيل. إذا فشل أو سبب مشكلات، يمكنك الاستعادة خلال دقائق لا ساعات.
  3. حراس عدد الصفوف — إذا كان الترحيل سيؤثر على أكثر من N صفاً، اطلب تأكيداً صريحاً. الترحيل الذي يلمس ملايين الصفوف بشكل غير متوقع هو غالباً خطأ برمجي.
  4. مهلات الاستعلامات — حدد مهلات صارمة لاستعلامات الترحيل. الترحيل الذي يعمل 45 دقيقة يقفل الجداول ويُضعف الأداء. افشل بسرعة.
  5. التوافق الخلفي فقط — فرض أن كل ترحيل متوافق مع الكود المنشور حالياً. هذا يعني عدم إعادة تسمية الأعمدة، ولا إضافة NOT NULL بدون قيم افتراضية، ولا حذف أعمدة ما زالت مستخدمة.
# Rails example using strong_migrations gem
class AddPhoneToUsers < ActiveRecord::Migration[7.1]
def change
# This will raise an error with a safe alternative:
# add_column :users, :phone, :string, null: false
#
# Instead, do it in two steps:
add_column :users, :phone, :string
# Then in a separate migration after backfill:
# change_column_null :users, :phone, false
end
end

أدوات مثل strong_migrations في Rails وsquawk لـ PostgreSQL وskeema لـ MySQL تلتقط الأنماط الخطيرة قبل وصولها إلى الإنتاج. إذا لم تكن تستخدم شيئاً من هذا القبيل، فأنت تعتمد على مراجعة الكود لالتقاط مشكلات الترحيل الدقيقة، والمراجعون يفوتون أشياء كثيرة.

الحذف الناعم ونوافذ التراجع

الحذف الصلب التزام دائم يُتخذ في لحظة يقين. والمشكلة أن اليقين غالباً ما يكون خاطئاً. الحذف الناعم، أي تعليم السجلات كمحذوفة دون إزالتها فعلياً، يمنحك نافذة للتعافي من الأخطاء.

-- Hard delete: gone forever
DELETE FROM projects WHERE id = 4872;
-- Soft delete: recoverable
UPDATE projects
SET deleted_at = now(),
deleted_by = 'user:priya'
WHERE id = 4872;
-- Recovery is trivial
UPDATE projects
SET deleted_at = NULL,
deleted_by = NULL
WHERE id = 4872;

المقايضة حقيقية: الحذف الناعم يضيف تعقيداً للاستعلامات (تحتاج WHERE deleted_at IS NULL في كل مكان)، ويزيد التخزين، وقد يخلق لبساً حول الحالة الفعلية للبيانات. لكن لأي بيانات يواجهها المستخدم ويمكن حذفها بالخطأ، تستحق المقايضة العناء. لا يحذف GitHub مستودعك فعلياً لمدة 90 يوماً، وتحتفظ Slack بالرسائل المحذوفة للامتثال، ويُفرَّغ سلة المهملات في Gmail بعد 30 يوماً. هذه ليست صدفة، بل قرارات هندسية متعمدة.

المبدأ نفسه ينطبق على البنية التحتية. بدلاً من إنهاء نسخة EC2، أوقفها أولاً. وبدلاً من إسقاط قاعدة بيانات، أعد تسميتها إلى mydb_deleted_20260315 واضبط تذكيراً في التقويم لإسقاطها فعلياً بعد أسبوعين. تكلفة إبقاء نسخة متوقفة أو قاعدة بيانات معاد تسميتها لبضعة أيام ضئيلة مقارنة بتكلفة الاستعادة من النسخ الاحتياطية.

أمان النشر: الكناري وقواطع الدارة والتراجع

النشر إجراء مدمر آخر لا تعامله معظم الفرق بالحذر الكافي. النشر السيئ يمكن أن يُسقط الإنتاج تماماً مثل قاعدة بيانات محذوفة، ويحدث أكثر بكثير.

الحد الأدنى لإعداد أمان النشر القابل للتطبيق يشمل:

  • نشر الكناري — وجّه 1-5% من حركة المرور إلى النسخة الجديدة. إذا ارتفعت معدلات الأخطاء، تراجع تلقائياً قبل أن يتسع نطاق الضرر.
  • محفزات التراجع التلقائي — حدد عتبات لمعدل الأخطاء وزمن الاستجابة وفشل فحوصات الصحة لتُفعّل التراجع تلقائياً. لا تعتمد على أن يلاحظ إنسان ذلك الساعة 2 صباحاً.
  • تجميد النشر أثناء الحوادث — إذا كانت هناك حادثة نشطة، أوقف كل عمليات النشر. آخر ما تحتاجه أثناء إطفاء الحريق هو أن يدفع أحدهم تغييرات غير ذات صلة.
  • تراجع بنقرة واحدة — يجب أن يكون التراجع أسهل من التقدم. إذا كانت عملية التراجع تتطلب الدخول عبر SSH إلى الخوادم وتشغيل أوامر يدوية، فأنت لا تملك عملية تراجع أصلاً.
# Kubernetes progressive delivery with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates:
- templateName: error-rate-check
- setWeight: 25
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
# Auto-rollback if error rate exceeds 1%
abortScaleDownDelaySeconds: 30

النمط هنا هو الالتزام التدريجي. لا تنتقل من 0 إلى 100% دفعة واحدة. تخطو خطوات صغيرة، وتتحقق من كل واحدة، وتحافظ على القدرة على التراجع في كل مرحلة. إنه أبطأ من النشر المتهور على جميع الخوادم دفعة واحدة، لكن في المرة الأولى التي يلتقط فيها نشراً سيئاً قبل أن يصل إلى كل المستخدمين، ستكون ممتناً لكل دقيقة إضافية.

الوقاية المعمارية: جعل الخطأ مستحيلاً

أفضل آليات الأمان لا تطلب منك أن تكون حذراً. بل تجعل فعل الشيء الخاطئ مستحيلاً بنيوياً. هذا هو الفرق بين حاجز حماية ولافتة تحذير.

  • البنية التحتية غير القابلة للتغيير — إذا لم تستطع الدخول عبر SSH إلى خوادم الإنتاج، فلن تستطيع تشغيل أوامر عليها بالخطأ. وإذا كانت عمليات النشر دائماً نسخاً جديدة من صورة معروفة، فلن يحدث انحراف في الإعدادات.
  • أقل الصلاحيات — لا ينبغي أن تكون بيانات اعتماد قاعدة بيانات الإنتاج على حواسيب المهندسين. نقطة على السطر. استخدم أدوات الوصول عند الحاجة التي تمنح بيانات اعتماد مؤقتة مع سجلات تدقيق.
  • بيانات اعتماد منفصلة لكل بيئة — إذا كانت بيئة الـ staging والإنتاج تستخدمان مخازن بيانات اعتماد مختلفة، فلن تستطيع حرفياً الاتصال بالإنتاج بأدوات الـ staging بالخطأ.
  • حماية من الحذف — تتيح لك AWS تفعيل حماية الإنهاء على نسخ EC2 وحماية الحذف على قواعد RDS. فعّلها لأي شيء مهم. إنه تغيير إعداد يستغرق خمس ثوانٍ ويمنع فئة كاملة من الأخطاء الكارثية.

إذا كان بإمكان شخص أن يدمر الإنتاج بالخطأ بأمر واحد، فالمشكلة ليست في الشخص، بل في النظام الذي سمح لأمر واحد بتدمير الإنتاج.

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

بناء ثقافة هندسية تُقدّم الأمان

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

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

مراجعات ما بعد الحادثة الخالية من اللوم أصبحت الحد الأدنى الآن. لكن الممارسة الأقل شيوعاً، والتي أعتقد أنها أهم، هي pre-mortem. قبل إطلاق تغيير محفوف بالمخاطر، اجمع الفريق واسأل: «افترض أن الأمور سارت بشكل فظيع. ماذا حدث؟» الناس بارعون بشكل مدهش في تحديد أنماط الفشل عندما تُؤطَّر المسألة كتخيّل لا كتنبؤ. وأنماط الفشل التي يحددونها تصبح آليات الأمان التي تبنيها.

ذلك المهندس المبتدئ الذي أسقط قاعدة بيانات الإنتاج؟ ما زال في الشركة. وهو الآن من أقوى المدافعين عن الهندسة الدفاعية في الفريق. لم تكن الحادثة خطأه، بل كانت فشلاً في النظام. والنظام صار اليوم أصعب بكثير في الكسر.