عندما تفشل ترحيلات قاعدة البيانات: لماذا التقدم للأمام أفضل من التراجع للخلف

لقد نشرت للتو ترحيل قاعدة بيانات أضاف عمود phone_number إلى جدول users. تم تشغيل الترحيل بنجاح. ثم أدرك فريقك أن كود التطبيق الذي يستخدم هذا العمود لم يتم نشره بعد. يفشل الآن كل تسجيل مستخدم جديد لأن الكود القديم يحاول إدراج صف دون توفير قيمة للعمود الجديد NOT NULL.

نظام الإنتاج الخاص بك معطل. ماذا تفعل؟

معظم الفرق تلجأ غريزيًا إلى الترحيل العكسي (down migration) - السكربت الذي يعكس التغيير ويزيل العمود. لكن هذه الغريزة قد تسبب ضررًا أكبر من المشكلة الأصلية. هناك نهج أفضل: التقدم للأمام (roll-forward).

مشكلة الترحيلات العكسية

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

في الممارسة العملية، الترحيلات العكسية خطيرة لعدة أسباب.

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

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

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

ما هو التقدم للأمام (Roll-Forward)؟

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

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

إليك كيف يبدو ترحيل الإصلاح هذا في SQL:

-- version_002: fix phone_number constraint
-- هذا الترحيل يجعل phone_number قابلاً للقيم الخالية حتى يتمكن كود التطبيق القديم
-- من إدراج صفوف دون توفير قيمة.
ALTER TABLE users
ALTER COLUMN phone_number DROP NOT NULL;

يصبح كل ترحيل تغييرًا تراكميًا. أضاف الترحيل الأول العمود. أصلح الترحيل الثاني قيد العمود. يسجل متتبع الترحيل كلا التغييرين بالتسلسل، بحيث يمكنك رؤية أن version_002 صحح version_001 دون محو تاريخه.

لماذا تفضل الفرق التقدم للأمام

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

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

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

عندما يصبح التقدم للأمام معقدًا

ليس كل إصلاح بالتقدم للأمام بسيطًا مثل تغيير قيد عمود. فكر في ترحيل غيّر نوع بيانات عمود من VARCHAR إلى INTEGER. إذا أدى التحويل إلى اقتطاع أو إفساد البيانات الموجودة، فقد يحتاج ترحيل الإصلاح الخاص بك إلى:

  1. إضافة عمود جديد بنوع البيانات الأصلي
  2. نسخ البيانات من العمود التالف، مع تطبيق تحويلات لاستعادة القيم
  3. تحديث مراجع كود التطبيق لاستخدام العمود الجديد
  4. حذف العمود التالف في ترحيل لاحق

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

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

قائمة ممارسات عملية للتقدم للأمام

قبل أن تلتزم بالتقدم للأمام كاستراتيجية لفريقك، تأكد من وجود هذه الممارسات:

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

flowchart TD A[الترحيل يسبب مشكلة] --> B{خطر فقدان بيانات مرتفع؟} B -->|نعم| C[تقدم للأمام] B -->|لا| D{كود التطبيق غير متوافق مع المخطط؟} D -->|نعم| E[تقدم للأمام] D -->|لا| F[فكر في التراجع] C --> G[اكتب ترحيل إصلاح] E --> G F --> H[شغل الترحيل العكسي بحذر]
  • يجب أن يكون كل ترحيل قابلاً للعكس نظريًا، ولكن ليس بالضرورة في الكود. افهم ما سيفعله الترحيل العكسي، لكن لا تكتب واحدًا إلا إذا كان لديك سبب محدد لاحتياجه.
  • اختبر ترحيلات الإصلاح في بيئة الاختبار أولاً. قم بتشغيل الترحيل الأصلي، وقدم سيناريو الفشل، ثم طبق ترحيل الإصلاح. تحقق من سلامة البيانات بعد ذلك.
  • حافظ على موثوقية متتبع الترحيل. لا تقم أبدًا بتعديل أو حذف سجلات الترحيل يدويًا. المتتبع هو سجل التدقيق الخاص بك لفهم ما تغير ومتى.
  • وثق نمط الفشل. عندما تكتب ترحيل إصلاح، أضف تعليقًا يشرح ما حدث خطأ ولماذا يعمل الإصلاح. هذا يساعد أعضاء الفريق المستقبليين الذين يواجهون أنماطًا مماثلة.
  • مارس سيناريوهات التقدم للأمام. قم بإجراء تدريب ربع سنوي حيث يقوم شخص ما عمدًا بإدخال ترحيل سيء، ويمارس الفريق كتابة ونشر إصلاح تحت ضغط الوقت.

الخلاصة

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

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