أتمتة Migration Safety: امسك ALTER TABLE الخطر قبل ما يلمس الإنتاج
لو فريقك مرّر merge على PR فيه ALTER TABLE ADD COLUMN ... NOT NULL DEFAULT now() على جدول 80 مليون صف، PostgreSQL بياخد AccessExclusiveLock لمدة 14 دقيقة وبيوقّف كل القراءة والكتابة على الجدول. مفيش حد في الـ code review لاحظ السطر ده، والـ CI عدّى بدون شكوى. الحل مش "خلّي الـ DBA يراجع كل migration" — الحل lint آلي بيرفض الـ PR قبل ما يوصل main.
المشكلة باختصار
PostgreSQL بياخد قفل حصري في عمليات DDL كتيرة، وأخطر تلاتة شائعة في فرق الـ backend العربية:
ALTER TABLE ADD COLUMN ... NOT NULL DEFAULT <volatile>— لما الـ default يكون expression متغيّر زيnow()أوgen_random_uuid()، الـ engine بيعيد كتابة كل صف في الجدول. على جدول 80M صف بـ SSD، ده بياخد 12 إلى 18 دقيقة كلهم downtime.CREATE INDEXبدونCONCURRENTLY— بيقفل كل الـ writes على الجدول طول مدة البناء. على جدول 40M صف، ممكن يقفل 6 دقايق.ALTER TABLE ... SET NOT NULL— بيفحص كل صف للتأكد من عدم NULL، وفي الفحص ده الجدول مقفول للكتابة.
المراجعة البشرية بتفوت الحاجات دي. مهندس على 14 PR في اليوم مش هيلاحظ سطر SQL واحد في ملف 200 سطر.
مثال للتقريب قبل التفاصيل التقنية
تخيّل مكتبة فيها 100 ألف كتاب، وأنت عايز تضيف لكل كتاب ملصق جديد فيه "تاريخ التحديث = الآن". لازم تقفل المكتبة بالكامل، تمسك كل كتاب واحد واحد، وتلصق عليه الملصق. مفيش زبون يقدر يدخل في الفترة دي. ALTER TABLE ADD COLUMN ... NOT NULL DEFAULT now() بيعمل نفس الشيء بالظبط على مستوى الـ DB: قفل تام، ومرور على كل صف.
البديل الذكي: ضيف العمود NULL الأول (عملية metadata بحتة، أقل من 50ms)، ابعت deploy، املأ القيم على دفعات في background، وبعدين فقط حوّله NOT NULL مع constraint اسمه NOT VALID ثم VALIDATE. الـ Squawk بيفرض السلسلة دي.
الأداة: Squawk linter
Squawk أداة CLI مكتوبة بـ Rust بتقرأ ملفات SQL وبتطلق تحذيرات على 38 نوع من DDL الخطر. مفتوحة المصدر تحت Apache 2.0، ومُستخدمة داخلياً في Robinhood وKraken كحارس على الـ migrations في الإنتاج.
# تثبيت محلي عبر Cargo
cargo install squawk
# أو عبر Docker (الأنسب لـ CI)
docker run --rm -v "$PWD":/migrations sbdchd/squawk:v2.5.0 \
/migrations/0042_add_user_status.sqlالمخرج لو في ALTER TABLE خطر:
]]>