كتابة ترحيلات قاعدة بيانات لا تعطل الإنتاج

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

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

لهذا السبب، لا تقتصر ترحيلات قاعدة البيانات الآمنة على كتابة SQL صحيحة فحسب. بل تتعلق بهيكلة التغييرات بحيث يمكن مراجعتها واختبارها وعكسها دون ذعر.

كل ترحيل يحتاج إلى ملفين

النمط الأبسط الذي ينقذ الفرق مرارًا هو زوج الترحيل الصاعد والهابط.

إليك مثال ملموس على ترحيل مقترن صاعد وهابط:

-- 20241101_add_last_login_at.up.sql
ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP;

-- 20241101_add_last_login_at.down.sql
ALTER TABLE users DROP COLUMN last_login_at;

الترحيل الصاعد يحتوي على SQL التي تُجري التغيير. الترحيل الهابط يحتوي على SQL التي تلغي التغيير. كل تغيير يحصل على كلا الملفين، مخزنين معًا بمعرّف فريد بحيث يكون الترتيب واضحًا.

20241101_add_email_index.sql          -- up
20241101_add_email_index_down.sql     -- down

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

بدون ترحيل هابط، يكون البديل الوحيد هو استعادة كاملة لقاعدة البيانات. ذلك يستغرق وقتًا، ويتطلب تنسيقًا، ويخاطر بفقدان البيانات الحديثة. الترحيل الهابط يُنفّذ في ثوانٍ.

عندما لا تكفي الترحيلات الهابطة

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

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

لهذه الحالات، النهج الآمن هو تقسيم التغيير إلى عدة ترحيلات صغيرة، كل منها قابل للعكس بمفرده:

  1. أضف عمودًا جديدًا بالنوع المطلوب.
  2. املأ البيانات على دفعات.
  3. حدّث كود التطبيق لاستخدام العمود الجديد.
  4. احذف العمود القديم.

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

اكتب ترحيلات يمكن تشغيلها عدة مرات

الأنابيب تفشل. تحدث انقطاعات الشبكة، وانتهاء المهلات، وأحيانًا يُشغّل الترحيل في منتصف الطريق قبل أن يتعطل. عندما يعيد الأنبوب المحاولة، يُشغّل الترحيل مرة أخرى.

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

اجعل كل ترحيل غير حساس للتكرار. استخدم IF NOT EXISTS عند إنشاء الجداول أو الفهارس. استخدم IF EXISTS عند حذف الكائنات. تحقق مما إذا كان العمود موجودًا بالفعل قبل تعديله. الهدف بسيط: تشغيل نفس الترحيل مرتين يجب أن ينتج نفس نتيجة تشغيله مرة واحدة.

تجنب الأقفال الطويلة على الجداول الكبيرة

ALTER TABLE التي تغير نوع عمود يمكنها قفل الجدول بأكمله لدقائق على جدول بملايين الصفوف. خلال ذلك الوقت، كل قراءة وكتابة على ذلك الجدول تنتظر. يرى المستخدمون انتهاء المهلات. تتراكم قوائم الانتظار.

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

  • أضف عمودًا جديدًا بالنوع المطلوب.
  • حدّث الصفوف على دفعات لملء العمود الجديد.
  • أضف فهرسًا إذا لزم الأمر.
  • احذف العمود القديم في ترحيل لاحق.

كل خطوة تقفل لفترة وجيزة. يمكن للتطبيق الاستمرار في العمل بين الخطوات. هذا النمط أبطأ في الكتابة ولكنه أكثر أمانًا في التشغيل.

أبقِ القيم الخاصة بالبيئة خارج ملفات الترحيل

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

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

خزّن الترحيلات بجانب كود التطبيق

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

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

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

في كلتا الحالتين، المفتاح هو أن ملفات الترحيل مُدارة بالإصدارات، ومُراجعة، وقابلة للتتبع. يجب أن يترك تغيير قاعدة البيانات نفس النوع من مسار التدقيق مثل تغيير الكود.

قائمة تحقق عملية لكتابة ترحيلات آمنة

قبل دمج ترحيل في الأنبوب، راجع هذه النقاط:

  • هل كل ترحيل له ترحيل هابط مقابل؟
  • هل يمكن للترحيل الهابط استعادة الحالة السابقة فعليًا دون فقدان بيانات؟
  • هل الترحيل غير حساس للتكرار؟ هل يمكن تشغيله مرتين دون خطأ؟
  • هل سيقفل الترحيل جدولاً كبيرًا لأكثر من بضع ثوانٍ؟
  • هل القيم الخاصة بالبيئة غائبة عن ملف SQL؟
  • هل ملف الترحيل مخزّن في مستودع مع كود التطبيق أو مستودع قاعدة بيانات مخصص؟

الخلاصة

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