ترحيل المخطط دون توقف
ترحيل المخطط دون توقف
كل مخطط قاعدة بيانات إنتاجي يحتاج في النهاية إلى تغيير. إضافة عمود، إعادة تسمية جدول، حذف مؤشر، أو تقسيم حقل — كلها عمليات روتينية خلال تطوير المنتج. النهج الساذج هو إيقاف التطبيق، تطبيق الترحيل، ثم إعادة التشغيل. هذا مقبول لمشروع شخصي بلا مستخدمين. أما على نطاق التقنية الكبرى، حيث تتطلب SLOs توافراً بنسبة 99.9% وأكثر وحركة مرور تبلغ الملايين من الطلبات في الدقيقة، فإن أي تغيير في المخطط يُقفل جدولاً لثوانٍ يُحوَّل إلى حادثة تمس العملاء مباشرة.
ترحيل المخطط دون توقف ليس أداةً واحدة — بل هو منهجية مبنية على ثلاثة أفكار متشابكة: نمط التوسع والتقليص، وDDL الآني (دعم محرك قاعدة البيانات للتغييرات الهيكلية غير المُعيقة)، وأدوات الترحيل المدمجة في خطوط CI/CD. اتقن الثلاثة وستتمكن من تطوير مخطط قاعدة البيانات باستمرار وأمان، دون الحاجة إلى تنسيق نوافذ صيانة.
لماذا تقتل عمليات الترحيل الساذجة التوافر؟
تأخذ كل من MySQL وPostgreSQL أقفالاً على مستوى بيانات الجدول أثناء عمليات DDL. قد يمسك أمر ALTER TABLE orders ADD COLUMN loyalty_points INT على جدول بـ 500 مليون صف قفلاً حصرياً لدقائق. كل استعلام يصل إلى هذا الجدول — قراءات وكتابات — يُصطف خلف القفل. تمتلئ مجموعة الاتصالات. يُعيد التطبيق 503. يُنبَّه المهندس المناوب في الساعتين صباحاً.
حتى DDL "السريع" له مزالق خفية. إضافة عمود بقيمة DEFAULT في MySQL القديمة (قبل 8.0) تُعيد كتابة الجدول بأكمله. إعادة تسمية عمود تُعطّل أي كود تطبيق لا يزال يستخدم الاسم القديم — وفي النشر المتدرج، تعمل الـ pods القديمة والجديدة في آنٍ واحد. هذه هي المشكلة الجوهرية التي يحلها نمط التوسع والتقليص.
user_id إلى account_id، تكتب الـ pods القديمة إلى user_id (الذي لم يعد موجوداً)، وتكتب الجديدة إلى account_id. تفشل كلتا مجموعتي الكتابة. تحدث أخطاء وفقدان بيانات حتى لو بدت كل قطعة صحيحة منفردةً.
نمط التوسع والتقليص
يُفكّك نمط التوسع والتقليص (المعروف أيضاً بالتغيير الموازي أو ترحيل المخطط الأزرق-الأخضر) كلَّ تغيير مخطط كاسر إلى ثلاث مراحل نشر على الأقل، كل منها متوافقة مع رجوعية كود التطبيق الحي:
- التوسع: أضف البنية الجديدة بجانب القديمة. أضف
account_idكعمود قابل للقيمة الفارغة. انشر كوداً يكتب في كلا العمودين ويقرأ من القديم. العمود القديم لا يزال هو المرجع؛ لا يُفقد أي بيانات. - ترحيل البيانات: امأل
account_idمنuser_idعلى دُفعات (لا تستخدم أبداً أمرUPDATEواحداً على الجدول كله). بعد الملء، انشر كوداً يقرأ منaccount_id. أصبح كلا العمودين الآن متزامنَين. - التقليص: بعد تأكد 100% من الـ pods أنها على الكود الجديد وتأكيد المقاييس عدم قراءة العمود القديم، احذف
user_id. هذا الآن DDL آمن وسريع لأنه لا يوجد كود حي يستخدمه.
الفكرة الجوهرية هي أن مخطط قاعدة البيانات وإصدار كود التطبيق مفصولان. لا حاجة أبداً لنشرهما في آنٍ واحد. كل مرحلة قابلة للتراجع بشكل مستقل، ولأن إصدارات التطبيق القديمة والجديدة دائماً متوافقة مع المخطط الحالي، فإن التراجع في أي مرحلة آمن.
NOT NULL DEFAULT 0 في MySQL قبل 8.0 يُجري إعادة كتابة كاملة للجدول. في PostgreSQL 11+، NOT NULL DEFAULT بقيمة ثابتة يكون فورياً (بلا إعادة كتابة). اعرف إصدار محركك قبل اختيار صيغة DDL.
DDL الآني: التغييرات غير المعيقة على مستوى المحرك
تمتلك محركات قواعد البيانات الحديثة آليات مدمجة لإجراء التغييرات الهيكلية دون تعطيل القراءات والكتابات المتزامنة. فهم هذه الآليات ضروري للتنبؤ بما إذا كان الترحيل آمناً أم سيُسبب انقطاعاً.
MySQL / InnoDB Online DDL (MySQL 5.6+، 8.0): معظم عمليات ALTER TABLE تدعم ALGORITHM=INPLACE, LOCK=NONE، التي تُطبق التغييرات في مكانها مع الاستمرار في خدمة حركة المرور. ليست كل العمليات تدعم ذلك — حذف المفتاح الأساسي أو تغيير مجموعة أحرف العمود لا يزال يتطلب نسخة كاملة. دائماً اختبر باستخدام EXPLAIN FORMAT=TREE ALTER TABLE ... أو راجع مصفوفة توثيق MySQL.
pt-online-schema-change (pt-osc) وgh-ost أدوات تُطبق DDL الآني على مستوى طبقة التطبيق للحالات التي لا يستطيع فيها المحرك القيام بذلك نيتاً، أو حيث تحتاج إلى مزيد من التحكم (التقييد، الترحيل القابل للإيقاف المؤقت). gh-ost (أداة GitHub) هو المعيار الصناعي للجداول الكبيرة في MySQL — يستخدم دفق السجل الثنائي لإعادة تطبيق الكتابات على جدول ظل دون محفزات، ثم يُعيد تسمية الجداول ذرياً عند التحويل.
PostgreSQL لديه المُعدِّل CONCURRENTLY لعمليات المؤشر: CREATE INDEX CONCURRENTLY يبني المؤشر دون قفل حصري، بتكلفة وقت أطول. PostgreSQL 12+ أضاف CREATE MATERIALIZED VIEW CONCURRENTLY. لكن ALTER TABLE ADD COLUMN NOT NULL العادي بلا قيمة افتراضية من جانب الخادم لا يزال يتطلب مسح الجدول كاملاً في الإصدارات القديمة.
الملء على دُفعات: الطريقة الأكثر أماناً لنقل البيانات
خلال مرحلة التوسع قد تحتاج إلى تعبئة عمود جديد من بيانات موجودة. لا تُشغّل أبداً UPDATE orders SET account_id = user_id — على جدول كبير هذا يأخذ قفل صف حصري على كل صف في آنٍ واحد، يُشبع تأخير النسخ المتماثل، ويستمر لساعات. استخدم حلقة ملء دُفعي بدلاً من ذلك:
UPDATE المُقسَّمة على دُفعات تُنسخ إلى النسخ المتماثلة للقراءة وقد تُسبب ارتفاع التأخير. اضبط حد تأخير (مثل الإيقاف المؤقت إذا تجاوز التأخير 2 ثانية) باستخدام SHOW SLAVE STATUS أو pt-heartbeat. gh-ost لديه هذا مدمجاً عبر --max-lag-millis.
أدوات الترحيل في خطوط CI/CD
عمليات الترحيل المنفصلة التي تُشغَّل يدوياً هي مصدر للانجراف والأخطاء والحالة غير الموثقة. المعيار الصناعي هو إصدار التحكم في كل ترحيل وتشغيله تلقائياً عبر خط الأنابيب، مع ضوابط تُفرز السلامة في كل مرحلة.
أدوات الترحيل الشائعة حسب البيئة:
- Flyway (Java، SQL) — ترحيلات إصدارية مبنية على الملفات (V1__add_column.sql). يتكامل مع Maven وGradle وGitHub Actions. يمتلك وضع
dryRunللمراجعة المسبقة. - Liquibase (Java، XML/YAML/SQL) — أغنى من Flyway؛ يدعم سكريبتات التراجع والشروط المسبقة وتصنيف مجموعات التغيير لاستهداف البيئات.
- Atlas (Go، HCL) — المخطط ككود؛ يولد خطة الترحيل من الفارق بين المخططات، يتكامل مع Terraform، ولديه واجهة برمجية سحابية للتدقيق.
- golang-migrate — CLI + مكتبة Go؛ طبيعي لخدمات Go المصغرة مع ملفات ترحيل صاعدة/نازلة.
- Alembic (Python) — أداة ترحيل SQLAlchemy؛ تُولّد الترحيلات تلقائياً بمقارنة نماذج ORM بالمخطط الحي.
يتبع خط أنابيب CI جاهز للإنتاج لترحيل قواعد البيانات هذا التسلسل:
DROP TABLE وDROP COLUMN وTRUNCATE في خطوط الأنابيب الآلية عالية الخطورة. اربطها بخطوة موافقة يدوية في GitHub Actions (عبر environment: production مع مُراجعين مطلوبين)، أو انشرها في PR منفصل يُطلق تشغيل خط أنابيب منفصل بموافقة يدوية.
تدقيق المخطط وقائمة التحقق من السلامة
قبل أن يصل أي ترحيل إلى الإنتاج، يجب أن يُشير المدقق تلقائياً إلى الأنماط المعروفة بتسبب حوادث. أوامر migrate lint في Atlas وSquawk (خاص بـ PostgreSQL) وskeema diff من GitHub يمكنها جميعاً التشغيل في CI لحجب SQL الخطير. الأنماط التي يجب رفضها تلقائياً:
- إضافة عمود
NOT NULLبلا قيمة افتراضية من جانب الخادم (إعادة كتابة كاملة للجدول أو تتطلب ملء البيانات الموجودة أولاً). - إنشاء مؤشر بلا
CONCURRENTLY(PostgreSQL) أو بدون التحقق منALGORITHM=INPLACE(MySQL). ALTER TABLE RENAME COLUMNبدون مرحلة توسع سابقة (ستُعطّل الـ pods القديمة أثناء النشر المتدرج).- أي ترحيل يُعدّل جدولاً أكبر من N غيغابايت بلا تصنيف موافقة صريح.
- غياب ترحيل نازل (
V2__undo.sql) — كل ترحيل يجب أن يكون قابلاً للعكس، وإلا فإن خطة التراجع تتطلب تجميد الكود.
ترحيل المخطط دون توقف هو من أكثر المهارات التي تُقلَّل قيمتها في هندسة الإنتاج. الأنماط هنا — التوسع والتقليص، DDL الآني، الملء على دُفعات، والتدقيق المدمج في خط الأنابيب — هي كيف تشحن الفرق في Google وStripe وGitHub تغييرات قواعد البيانات عشرات المرات يومياً دون نافذة صيانة. استوعب دورة حياة التوسع والتقليص أولاً؛ كل تقنية أخرى تنبع منها بشكل طبيعي.