Get a quote

ترحيل مخططات PostgreSQL بدون توقف في منتج SaaS حي

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

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

ماذا يحدث فعلياً عند تشغيل ALTER TABLE على جدول كبير؟

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

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

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

نمط التوسع والتقليص: أساس الترحيلات الآمنة

يُقسِّم نمط التوسع والتقليص تغيير مخطط واحد إلى خطوات متعددة متوافقة مع الإصدارات السابقة يمكن نشرها بشكل مستقل.

المراحل الثلاث:

التوسع: إضافة عناصر مخطط جديدة دون إزالة القديمة. الكود القديم لا يزال يعمل. الكود الجديد يمكنه استخدام العناصر الجديدة.

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

التقليص: إزالة عناصر المخطط القديمة بعد تحديث ونشر كل كود التطبيق لاستخدام البنية الجديدة.

مثال ملموس: إعادة تسمية عمود user_name إلى display_name.

بنمط التوسع والتقليص:

  1. الترحيل 1: إضافة عمود display_name. نسخ البيانات من user_name إلى display_name.
  2. النشر: تحديث كود التطبيق لقراءة وكتابة display_name. الكود القديم لا يزال يعمل لأن user_name لا يزال موجوداً.
  3. الترحيل 2: إسقاط عمود user_name. الكود الجديد لا يُشير إليه أبداً.

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

إنشاء الفهارس بدون تأثير على الإنتاج

CREATE INDEX القياسي يحتفظ بقفل يحجب الكتابات طوال مدة الإنشاء. استخدم CREATE INDEX CONCURRENTLY بدلاً من ذلك:

CREATE INDEX CONCURRENTLY idx_orders_tenant_id_created_at
ON orders(tenant_id, created_at DESC);

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

إعداد golang-migrate للإنتاج

يُدير golang-migrate ملفات الترحيل ذات الإصدارات ويتتبع الترحيلات المُطبَّقة. لخلفية Go لـ SaaS، يتكامل بنظافة مع تسلسل بدء التشغيل أو كبرنامج ثنائي مستقل للترحيل.

تسلسل النشر لترحيل آمن:

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

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

متى تستخدم pg_repack للتغييرات الكبيرة؟

pg_repack هو امتداد PostgreSQL يُعيد كتابة الجداول في الخلفية دون الاحتفاظ بقفل جدول كامل. هو الأداة المناسبة عندما تحتاج إلى:

  • إضافة عمود NOT NULL بقيمة افتراضية محسوبة على جدول كبير جداً
  • إزالة الصفوف الميتة من جدول تم تحديثه بكثرة (انتفاخ الجدول)

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

استراتيجيات التراجع التي تعمل فعلاً

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

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

دروس من الإنتاج

nمط التوسع والتقليص يُلغي غالبية مشاكل الترحيل بدون توقف. يتطلب ترحيلات أكثر لكل تغيير في المخطط، لكن كل ترحيل آمن للتشغيل دون تنسيق مع نشر التطبيق.

CREATE INDEX CONCURRENTLY غير قابل للتفاوض للجداول التي تحتوي على أكثر من بضع مئات الآلاف من الصفوف.

استعلامات الملء الدُّفعي تُقسَّم حسب نطاق المفتاح الأساسي. UPDATE واحد على ملايين الصفوف هو حادثة إنتاجية. التجميع يحوّلها إلى عمل خلفي.

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

العودة إلى المدونة
Chat on WhatsApp