عندما تلتقي البيانات القديمة مع المخطط الجديد: تعبئة والتحقق من السجلات القديمة

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

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

لماذا التعبئة الخلفية ليست مجرد تحديث UPDATE بسيط

النهج الساذج هو تشغيل استعلام UPDATE واحد كبير يضبط العمود الجديد لكل صف حيث يكون NULL. إذا كان جدولك يحتوي على بضعة آلاف من الصفوف، فقد ينتهي هذا في ثوانٍ. لكن إذا كنت تتعامل مع عشرات الملايين من الصفوف، فإن هذا الاستعلام الواحد يمكن أن يحمل قفل جدول لفترة طويلة. تصطف الاستعلامات الأخرى. يتباطأ التطبيق. يبدأ المستخدمون في رؤية مهلات.

يجب تخطيط التعبئة الخلفية بنفس العناية التي توليها لأي عملية إنتاجية. الخطوة الأولى هي تحديد البيانات التي تحتاج إلى التعبئة بالضبط. لنفترض أنك أضفت عمود status الذي تُشتق قيمته من عمودين موجودين: is_active و deleted_at. يجب أن يتطابق منطق التعبئة الخلفية مع نفس القواعد التي يستخدمها كود التطبيق الجديد عند كتابة سجلات جديدة. إذا كان التطبيق يضبط status على 'active' عندما يكون is_active صحيحًا و deleted_at فارغًا، فيجب أن تفعل التعبئة الخلفية نفس الشيء.

المعالجة المجزأة: الطريقة الآمنة

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

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

إليك نمط تقريبي يعمل عبر العديد من قواعد البيانات:

while there are rows with NULL in new_column:
    select a batch of primary keys where new_column IS NULL
    update those rows with the computed value
    sleep for a short interval

فاصل النوم ليس عن التباطؤ. إنه عن إعطاء فرصة للعمليات الأخرى للعمل. حتى 100 مللي ثانية بين الدفعات يمكن أن تحدث فرقًا كبيرًا في كيفية تصرف قاعدة البيانات تحت الحمل.

لمثال ملموس، لنفترض أنك أضفت عمود status مشتق من is_active و deleted_at. قد يبدو التحديث المجزأ في PostgreSQL كالتالي:

UPDATE your_table
SET status = CASE
    WHEN is_active = true AND deleted_at IS NULL THEN 'active'
    WHEN is_active = false AND deleted_at IS NULL THEN 'inactive'
    ELSE 'deleted'
END
WHERE id IN (
    SELECT id
    FROM your_table
    WHERE status IS NULL
    LIMIT 1000
);

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

flowchart TD A[ابدأ] --> B{هل توجد صفوف بها NULL؟} B -- نعم --> C[اختر دفعة من 1000 مفتاح أساسي حيث new_column IS NULL] C --> D[احسب قيمة العمود الجديد للدفعة] D --> E[تحديث الصفوف WHERE id IN (دفعة)] E --> F[توقف 100 مللي ثانية] F --> B B -- لا --> G[تم]

مصدران للحقيقة أثناء التعبئة الخلفية

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

  • البيانات المكتوبة بواسطة التطبيق (متسقة دائمًا لأن التطبيق يكتب في كلا المكانين في وقت واحد)
  • البيانات المملوءة بواسطة عملية التعبئة الخلفية (تحتاج إلى مطابقة منطق التطبيق تمامًا)

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

التحقق: أكثر من مجرد فحص NULL

بعد اكتمال التعبئة الخلفية، الغريزة الطبيعية هي تشغيل فحص سريع: "هل توجد أي قيم NULL متبقية؟" هذا يخبرك أن العمود ممتلئ، لكنه لا يخبرك ما إذا كانت القيم صحيحة.

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

يمكن أن يأتي عدم التطابق من عدة مصادر:

  • خطأ في منطق التعبئة الخلفية الذي يحسب القيمة بشكل مختلف عن التطبيق
  • بيانات تغيرت بين الوقت الذي عالجته التعبئة الخلفية والوقت الذي يعمل فيه التحقق
  • كتابة التطبيق لقيمة مختلفة لأن منطقه تم تحديثه في منتصف الترحيل
  • حالات سباق حيث تم تحديث صف بواسطة التطبيق بعد التعبئة الخلفية ولكن قبل التحقق

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

التحقق التدريجي

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

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

SELECT COUNT(*) FROM table
WHERE computed_value != actual_new_value

إذا كان العدد صفرًا، فأنت نظيف. إذا لم يكن صفرًا، فأنت تعرف عدد الصفوف التي تحتاج إلى اهتمام، ويمكنك التحقيق فيها في دفعات.

عندما ينجح التحقق

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

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

قائمة ممارسات عملية

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

الخلاصة العملية

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