تخطَّ إلى المحتوى

الهجرة التي أسقطت الموقع

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

قراءة 4 دقيقة

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

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

أي العمليات مجانية وأيها ليس

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

آمن عمومًا، على المحركين الرئيسيين:

  • إضافة عمود قابل للإفراغ بلا قيمة افتراضية
  • حذف عمود
  • إعادة تسمية جدول
  • إضافة قيد لا يُتحقَّق منه فورًا، حيث يُدعم ذلك

غير آمن عمومًا على جدول كبير:

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

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

التي تفاجئ الناس

Schema::table('orders', function (Blueprint $table) {
    $table->index('customer_id');
});

إضافة فهرس تبدو قراءةً لا كتابة. وعلى MySQL بلا مسار تغيير بنية على الخط، وعلى Postgres بلا CONCURRENTLY، تأخذ قفلًا طوال البناء - وعلى جدول كبير ذلك دقائق.

يقدّم Postgres خيار التوازي، ولا يُصدره Laravel، فيجب كتابته يدويًا:

public function up(): void
{
    DB::statement('CREATE INDEX CONCURRENTLY orders_customer_id_index ON orders (customer_id)');
}

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

تغيير عمود دون إعادة كتابة الجدول

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

  1. أضف العمود الجديد، قابلًا للإفراغ، بلا قيمة افتراضية. فوري.
  2. اكتب في العمودين من التطبيق، وانشر ذلك، ودعه يعمل.
  3. املأ الصفوف القديمة على دفعات في طابور، بفاصل بين الدفعات كي تتنفس النسخ المتماثل وبقية الحركة.
  4. تحقق من تطابقهما - عدّ الصفوف التي تختلف فيها القيمتان ينبغي أن يكون صفرًا.
  5. حوّل القراءات إلى العمود الجديد. انشر. انتظر.
  6. أوقف الكتابة في القديم. انشر.
  7. احذفه، في إصدار لاحق.

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

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

Order::whereNull('customer_uuid')
    ->select('id')
    ->chunkById(1000, function ($orders) {
        Order::whereIn('id', $orders->pluck('id'))
            ->update([/* ... */]);
 
        usleep(100_000);
    });

قواعد تستحق الاتباع

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

كل ما يعيد الكتابة يعمل منفصلًا عن النشر. مراقَبًا، من شخص، بإمكانية إيقافه.

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

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

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

أي العمليات تقفل، وكم تقفل عند أعداد صفوفك، أمرٌ تقيسه مهمة قواعد البيانات على بياناتك. والتقديرات قليلة النفع هنا. وإن كنت لا تزال تختار محركًا وكان هذا هو الفيصل، فالفروق مفصَّلة لكل محرك.

أسئلة ذات صلة

هل يؤثر هذا في Postgres كما في MySQL؟
في الاثنين، بطرق مختلفة. يستطيع Postgres إضافة عمود قابل للإفراغ فورًا وبناء فهرس بالتوازي، لكنه يظل يحتاج قفلًا حصريًا قصيرًا ليبدأ - وذلك القفل يصطف خلف المعاملات الطويلة، وهكذا تعطّل هجرة "آمنة" جدولًا مزدحمًا. ولدى MySQL تغيير بنية على الخط لكثير من العمليات لا لكلها.
ما حجم الجدول الذي يدعو للقلق؟
لا يوجد عدد صفوف يجعل الأمر آمنًا، لأن المهم كم تستغرق العملية مقابل كم تستطيع حركتك الانتظار. مليون صف على تخزين سريع قد يكون ثوانٍ؛ والجدول نفسه بحمل كتابة عالٍ وتقرير طويل مفتوح عليه إجابة أخرى.
ألا يمكن تشغيل الهجرات في نافذة صيانة؟
يمكن، وهو جواب جيد تمامًا لنشاط له ساعات هادئة. وأغلب أساليب هذا المقال موجودة لأنظمة ليست لها - وحتى مع نافذة، فمعرفة أي العمليات تحتاجها هي الجزء المفيد.
هل ينبغي تشغيل الهجرات تلقائيًا عند النشر؟
نعم للروتينية عادةً، لأن الخطوة اليدوية خطوة منسية. والاستثناء بالضبط هو العمليات التي يتناولها هذا المقال: كل ما يعيد كتابة جدول يريد تشغيلًا متعمَّدًا ومراقَبًا ومفصولًا عن النشر الذي يعتمد عليه.

← العودة إلى كل المقالات

اتصل بنا+1 848 272 7583واتساب+90 850 308 5436البريدinfo@codefacture.comصفحة التواصل