الاسترجاع: عندما لا تكون العودة إلى الإصدار السابق بسيطة كما تبدو

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

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

استرجاع التطبيق: الحالة الأسهل نسبياً

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

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

إذا كنت تستخدم الإصدارات التجريبية (canary releases)، فإن الاسترجاع يعني إيقاف تدفق حركة المرور إلى الإصدار الجديد وتوجيه كل شيء مرة أخرى إلى الإصدار السابق. يتم إيقاف الإصدار التجريبي، ويتولى الإصدار المستقر جميع الطلبات مرة أخرى.

إذا كنت تستخدم التحديثات المتداولة (rolling updates) المباشرة، فإن الاسترجاع يعني إعادة نشر الأرتيفكت السابق على نفس الخوادم أو الحاويات. يستغرق هذا وقتاً أطول لأن كل مثيل يحتاج إلى التحديث واحداً تلو الآخر، لكن العملية لا تزال قابلة للتنبؤ.

على سبيل المثال، إذا كنت تستخدم Kubernetes، يمكن لأمر واحد إرجاع النشر إلى مراجعته السابقة:

kubectl rollout undo deployment/my-app -n production

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

kubectl rollout undo deployment/my-app -n production --to-revision=3

لعرض تاريخ المراجعات قبل الاسترجاع:

kubectl rollout history deployment/my-app -n production

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

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

flowchart TD A[ما نوع الاسترجاع؟] --> B[التطبيق] A --> C[قاعدة البيانات] A --> D[البنية التحتية] B --> B1[أزرق-أخضر: تبديل حركة المرور] B --> B2[إصدار تجريبي: إيقاف حركة المرور التجريبية] B --> B3[متداول: إعادة نشر الأرتيفكت القديم] C --> C1[هل تغير المخطط؟] C1 -- نعم --> C2[تشغيل الترحيل العكسي] C1 -- لا --> C3[لا حاجة لإجراء على قاعدة البيانات] C2 --> C4[هل هناك خطر فقدان البيانات؟] C4 -- نعم --> C5[الاستعادة من النسخة الاحتياطية] C4 -- لا --> C6[تم إرجاع المخطط] D --> D1[إرجاع ملفات التكوين] D1 --> D2[التحقق من التبعيات] D2 --> D3[تطبيق الحالة السابقة]

استرجاع قاعدة البيانات: حيث تصبح الأمور فوضوية

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

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

النهج الأكثر أماناً هو جعل كل ترحيل قاعدة بيانات قابلاً للعكس منذ البداية. هذا يعني أن كل سكريبت ترحيل يتضمن خطوة up التي تطبق التغيير وخطوة down التي تعكسه. عندما تقوم بالاسترجاع، تقوم بتشغيل الترحيل العكسي down لاستعادة المخطط السابق.

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

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

استرجاع البنية التحتية: شبكة التبعيات الخفية

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

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

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

حدود الاسترجاع كاستراتيجية

الاسترجاع ليس شبكة أمان عالمية. إنه يعمل فقط عند استيفاء ثلاثة شروط:

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

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

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

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

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

قبل تنفيذ الاسترجاع، اطرح هذه الأسئلة:

  • هل الإصدار القديم لا يزال قيد التشغيل وجاهزاً لاستقبال حركة المرور؟
  • هل تغير مخطط قاعدة البيانات بطريقة تجعل الكود القديم غير متوافق؟
  • كم من الوقت كان الإصدار الجديد قيد التشغيل، وكم من بيانات المستخدم تم إدخالها؟
  • هل يمكن عكس ترحيل قاعدة البيانات دون فقدان البيانات؟
  • هل الاسترجاع أسرع من كتابة إصلاح تقدمي؟
  • هل قمت بإبلاغ خطة الاسترجاع للفريق وأصحاب المصلحة؟

إذا كانت الإجابة على أي من هذه الأسئلة تثير علامة حمراء، ففكر في الإصلاح التقدمي بدلاً من ذلك.

الخلاصة

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