العمّال والطوابير: متى تنقل العمل خارج دورة الطلب
أي مهمة تتجاوز جزءاً من الثانية ولا ينتظر نتيجتها المستخدم فوراً تنتمي إلى طابور خلفي. ادفع الإيميل والصور وتقارير PDF إلى عامل يشتغل بعيداً عن الطلب، فيرجع الرد فوراً وتبقى الواجهة سريعة.
القاعدة العملية بسيطة: أي عمل يستغرق أكثر من جزء يسير من الثانية ولا ينتظر المستخدم نتيجته على الشاشة الآن، انقله خارج دورة الطلب إلى طابور خلفي يعالجه عامل مستقل. إرسال إيميل ترحيبي، توليد صورة مصغّرة، بناء تقرير PDF، استدعاء واجهة خارجية بطيئة، كلها تُطيل زمن استجابة الصفحة بلا داعٍ إذا نُفّذت داخل الطلب. الفكرة أن يكتب طلب المستخدم "مهمة" في طابور ثم يرجع الرد فوراً في نحو 50 مللي ثانية بدل 3 ثوانٍ، بينما يلتقط عامل يعمل في الخلفية تلك المهمة وينفّذها بهدوء. النتيجة المباشرة: واجهة تبقى سريعة تحت الضغط، ومهام ثقيلة تُعالج بالتوازي، وفشل مؤقت في خدمة خارجية لا يكسر تجربة المستخدم لأن العامل يعيد المحاولة تلقائياً.
في Laravel المكوّنات ثلاثة: مهمة (Job) تصف العمل، ووسيط طابور (queue driver) يخزّنها، وعامل (worker) عملية دائمة تسحب المهام وتنفّذها واحدة تلو الأخرى. تكتب ShouldQueue على الصنف فيذهب تلقائياً إلى الطابور بدل التنفيذ الفوري. الوسيط الأبسط هو قاعدة البيانات، جدول jobs يحمل المهام المنتظرة، ويكفي لأحمال صغيرة. لكن حين يزيد الحجم أو تريد آلاف المهام في الثانية، Redis أسرع بمراحل لأنه في الذاكرة. هذا التصميم يخدم بالضبط الشركات ذات الأنظمة المخصّصة التي نبنيها معها على الخادم السحابي، حيث الطوابير تفصل الواجهة السريعة عن المعالجة الثقيلة.
ما الذي يستحق طابوراً وما الذي يبقى في الطلب
ليست كل مهمة تحتاج طابوراً. القاعدة أن تسأل: هل ينتظر المستخدم هذه النتيجة الآن؟ إن كان يحتاج رقم الفاتورة فوراً بعد الدفع، احسبه في الطلب. أما إرسال نسخة الفاتورة بالإيميل فيذهب للطابور، لأن المستخدم لا يقف أمام الشاشة ينتظر وصول الرسالة. الجدول التالي يوضّح الفرز العملي:
| العمل | داخل الطلب | في الطابور |
|---|---|---|
| التحقق من صحة النموذج | نعم | لا |
| إرسال إيميل أو رسالة واتساب | لا | نعم |
| توليد صور مصغّرة أو ضغطها | لا | نعم |
| بناء تقرير PDF كبير | لا | نعم |
| استدعاء واجهة دفع خارجية | حسب الحالة | غالباً نعم |
| حساب سعر يظهر فوراً للمستخدم | نعم | لا |
المعيار الحاسم هو زمن الاستجابة الملموس. إذا كان تنفيذ العمل داخل الطلب يرفع زمن الصفحة من 80 مللي ثانية إلى ثانيتين، فأنت تدفع ثمناً باهظاً في تجربة كل مستخدم مقابل عمل كان يمكن تأجيله بلا أن يلاحظ أحد.
اختيار الوسيط: قاعدة البيانات أم Redis
وسيط قاعدة البيانات يكتب كل مهمة صفّاً في جدول ويقرؤها العامل بقفل. سهل الإعداد، ولا يحتاج خدمة إضافية، ويكفي لعشرات أو مئات المهام في الدقيقة. عيبه أن كل سحب مهمة يعني استعلاماً على القرص، فتحت الضغط العالي يصير الجدول نفسه عنق زجاجة. Redis يخزّن الطابور في الذاكرة، فيسحب العامل المهمة في أجزاء من المللي ثانية، ويحتمل آلاف المهام في الثانية بلا أن يثقل قاعدة بياناتك الأساسية. القاعدة العملية: ابدأ بقاعدة البيانات إن كان حملك خفيفاً، وانتقل إلى Redis حين تتجاوز بضع مئات من المهام في الدقيقة أو حين ترى الطابور يتأخر. سيرفر نمو بذاكرة 4GB يتّسع لـ Redis مع تطبيقك دون عناء، وكل مهمة نموذجية تشغل ذاكرة يسيرة.
الفشل وإعادة المحاولة وسياسة backoff
الخدمات الخارجية تفشل مؤقتاً: بوابة الدفع تتأخر، خادم البريد يرفض الاتصال لثوانٍ، واجهة الطرف الثالث ترجع خطأ 503. ميزة الطابور الكبرى أنه يعيد المحاولة بدل أن يسقط العمل. تضبط عدد المحاولات عبر tries ومهلة كل محاولة عبر timeout، وتؤخّر إعادة المحاولة بسياسة backoff متصاعدة، مثلاً 10 ثوانٍ ثم 60 ثم 300، حتى لا تقصف الخدمة المتعثّرة بمحاولات متلاحقة. حين تُستنفد كل المحاولات، تنتقل المهمة إلى جدول failed_jobs بدل أن تختفي بصمت. راقب هذا الجدول يومياً، فهو أول مؤشر على خلل حقيقي: تراكم مفاجئ فيه يعني أن خدمة ما تعطّلت أو أن مهمة بها خطأ برمجي. تعيد المهام الفاشلة بأمر واحد بعد إصلاح السبب.
مبدأ idempotency
لأن المهمة قد تُنفَّذ أكثر من مرة عند إعادة المحاولة، اكتبها بحيث لا يضرّ تكرارها. لا تُنشئ فاتورة جديدة في كل تشغيل، بل تحقّق أولاً هل أُنشئت. اجعل النتيجة نفسها سواء نُفّذت المهمة مرة أو ثلاث مرات، فهذا يحميك من رسائل مكرّرة أو خصم مزدوج.
إبقاء العمّال أحياء: supervisor بعد كل نشر
العامل عملية طويلة العمر لا تنتهي مع الطلب، ولو أوقفتها لتوقف الطابور. لذلك لا تشغّل queue:work يدوياً في طرفية، بل ضعه تحت مدير عمليات مثل supervisor يعيد تشغيله تلقائياً إن سقط أو أُعيد إقلاع السيرفر. تضبط عدد العمّال عبر عدد العمليات: عاملان أو ثلاثة يكفيان لأحمال متوسطة، وتزيدهم حين ترى زمن الانتظار في الطابور يطول. نقطة يغفل عنها كثيرون: العامل يحمّل الكود في الذاكرة عند إقلاعه، فلا يرى تعديلاتك بعد النشر حتى تعيد تشغيله. اجعل خطوة أخيرة في نص النشر تنفّذ queue:restart ليلتقط العمّال الكود الجديد. أضف مهلة عليا لكل مهمة حتى لا يعلق عامل على مهمة معطوبة إلى الأبد.
مراقبة الصحة بالأرقام
راقب ثلاثة أرقام: طول الطابور (كم مهمة منتظرة الآن)، زمن الانتظار (كم تبقى المهمة قبل أن يلتقطها عامل)، ومعدل الفشل. إذا كان طول الطابور يتصاعد باستمرار فعمّالك أقل من اللازم أو مهامك أبطأ من معدل ورودها. زمن انتظار يقفز من ثوانٍ إلى دقائق تحت الذروة يعني أنك تحتاج عمّالاً إضافيين أو طوابير بأولويات، طابور سريع للإشعارات وطابور بطيء للتقارير الثقيلة، حتى لا يحبس تقرير ضخم إشعاراً عاجلاً.
طوابير بأولويات وترتيب العمل
حين يكبر تطبيقك تكتشف أن ليست كل المهام متساوية في الإلحاح. إشعار يخبر العميل أن دفعته نجحت يجب أن يخرج خلال ثوانٍ، بينما تقرير شهري ضخم يحتمل أن ينتظر دقائق. لو خلطتهما في طابور واحد، قد يجلس الإشعار العاجل خلف تقرير يستغرق دقيقتين، فيصل متأخراً بلا مبرّر. الحل أن تفصل العمل على طوابير مسمّاة: طابور high للإشعارات العاجلة، وطابور default للعمل المعتاد، وطابور low للتقارير والمهام الثقيلة. ثم توزّع عمّالك بحيث يخدم بعضهم الطابور السريع أولاً. القاعدة العملية: افصل حسب زمن التنفيذ المتوقّع ودرجة الإلحاح، لا حسب نوع الميزة، فما يهمّ المستخدم هو متى يصله الأثر لا اسم الوظيفة التي أنتجته.
راقب أيضاً حجم كل مهمة في الذاكرة. مهمة تعالج صورة عالية الدقة قد تستهلك عشرات الميغابايتات أثناء تشغيلها، فإن شغّلت عشرة عمّال متوازين على مهام كهذه، ضرب الاستهلاك عشرة. على سيرفر بذاكرة 4GB، هذا يعني موازنة عدد العمّال مع حجم مهامك، لا مجرد زيادتهم لأن الطابور طويل. أحياناً عاملان يعالجان بثبات أفضل من ثمانية يتنافسون على ذاكرة تنفد فيقتلها النظام.
توقيت المهام المؤجّلة
الطابور لا يشغّل العمل فوراً فقط، بل يجدوله للمستقبل أيضاً. تؤخّر إرسال رسالة متابعة بعد ساعة من التسجيل، أو تجدول تقريراً يومياً في الثالثة فجراً حين يقل الحمل. هذا يحوّل الطابور من أداة تفريغ حمل إلى أداة توقيت ذكي، توزّع العمل الثقيل على ساعات الهدوء بدل أن يزاحم ذروة النهار. اجمع ذلك مع مراقبة طول الطابور، فترى بوضوح متى تحتاج عاملاً إضافياً ومتى يكفيك ما لديك.
اختبار المهام قبل النشر
مهمة الطابور كود مثل غيره، تستحق اختباراً قبل أن تعتمد عليها في الإنتاج. اختبر أنها تنفّذ العمل الصحيح، وأنها تتصرّف بأمان عند تكرارها، وأنها تفشل بشكل نظيف حين تفشل الخدمة الخارجية. الخطأ الشائع أن تكتب مهمة تعمل في التجربة الأولى ثم تكتشف في الإنتاج أنها تكرّر إيميلاً أو تخصم مرتين عند إعادة المحاولة. اختبار المهمة على حالتي النجاح والفشل يوفّر عليك حوادث محرجة أمام عملائك، ويجعل ثقتك بالطابور مبنية على دليل لا على أمل.
الخلاصة: خطوات عملية
ابدأ بنقل كل عمل لا ينتظره المستخدم فوراً إلى مهمة تحمل ShouldQueue. استخدم وسيط قاعدة البيانات إن كان حملك خفيفاً، وانتقل إلى Redis عند التوسّع. اضبط tries وسياسة backoff متصاعدة، واكتب مهامك بحيث لا يضرّ تكرارها، وراقب جدول failed_jobs يومياً. شغّل العمّال تحت supervisor، وأضف queue:restart في نهاية كل نشر. لو تبني نظاماً مخصّصاً يحتاج طوابير موثوقة على سيرفر سعودي بزمن استجابة نحو 8ms من الرياض، فراجع الأسعار والباقات واختر سيرفراً يتّسع لتطبيقك و Redis معاً، أو ابنِ ذكاءك على استضافة AI بقالب n8n جاهز لتشغيل تدفقات خلفية دون كود.


