معالجة جدول DB ضخم بإستخدام الدالة Chunk

لنبدأ بتعريف كلمة “تشنك” Chunk في اللغة الإنكليزية:
إسماً:  قطعة ثابتة سميكة من شيئ ما.
فعلاً: تقطيع شيئ إلى قطع أي Chunks

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

الحل الأول الذي سيخطر على بالك هو كتابة حلقة برمجة ــ ولتكن foreach ــ تقوم بمعالجة هذه السطور وتعديل القيمة المرادة، ولكن لنكون واقعين هذه الحلقة ستأخذ إلى الأبد لتنتهي! أو على الأغلب سوف تعرض لك رسالة خطأ على أنّ هذه الأسطر من التعليمات قد وصلت للحد الأعظمي من التنفيذ وهو ثلاثون ثانية!

لحسن حظنا وكالعادة تقدم لارافل حلاً نظيفاً و سهلاً 🙂

أولاً لنطلع على سطور توضح المشكلة التي نواجهها في حالتنا

$users = User::all();
foreach ($users as $user) {
  $some_value = ($user->some_field > 0) ? 1 : 0;
  // might be more logic here
  $user->update(['some_other_field' => $some_value]);
}

هل ترى المشكلة في هذه الحالة؟ الشفرات المكتوبة في الأعلى مناسبة في حالة 100 أو 500 مستخدم، ولكن ماذا عن رقم أكبر من هذا كـ 10000؟ أو 100000؟ أو حتى أكثر؟!

المسألة هنا ليست عن السرعة فقط! وإنما أيضاً عن تخزين البيانات في جدول DB ( أنت فعلياً تقوم بتخزين الجدول كاملاً بمتحولات، وبالتالي سريعاً ما ستنفذ من الذاكرة وطبعاً لا أحد فينا يرغب في هذا مطلقاً  )

وهنا الحل بإستخدام الدالة ()Chunk والتي تقوم بتقسيم جميع البيانات إلى تحديدات منفصلة، كما في عملية ترقيم الصفحات Pagination ، دعونا نرى مثالاً:

User::chunk(100, function ($users) {
  foreach ($users as $user) {
    $some_value = ($user->some_field > 0) ? 1 : 0;
    // ... من الممكن وجود أسطر أخرى
    $user->update(['some_other_field' => $some_value]);
  }
});

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

 

والآن كن على حذر عند تصفية النتائج الأولية، لا تقم بكتابة شفرات كالتالية:

User::where('approved', 0)->chunk(100, function ($users) {
  foreach ($users as $user) {
    $user->update(['approved' => 1]);
  }
});

فنياً هذه الشفرات تعمل ويتم تنفيذها بدون أي مشاكل ولكن المشكلة هنا أنك تقوم بتصفية المستخدمين الغير موافق عليهم (أي يملكون قيمة صفر للحقل approved) ومن ثم تقوم بالموافقة عليهم و في الكتلة chunk التالية عندما تحاول لارافل بتنفيذ كويري إلى قاعدة البيانات لتأخذ “صفحة” أخرى، في ذلك الوقت البيانات قد تغيرها مسبقاً! وبالتالي سوف تفوت صفحة من البيانات التي لم تتم معالجتها بعد، مما يعني أنّ شفراتك التي في الأعلى سوف تعالج نصف المدخلات.

كملخص:
لا تقم بتحديث القيمة التي تستخدمها نفسها في عملية التصفية الأولية للنتائج.

رأيان على “معالجة جدول DB ضخم بإستخدام الدالة Chunk”

التعليقات مغلقة.