كيفية تشغيل المهام الخلفية في تطبيق ويب: الطوابير والجدولة والـ Cron
لتشغيل المهام الخلفية في تطبيق ويب، قسّم العمل أولًا إلى نوعين. المهام المُطلَقة بحدث (إرسال رسالة ترحيب، ومعالجة رفع ملف) يمكن حفظها في طابور مهام (job queue)، وغالبًا ما يكون جدولًا في قاعدة بياناتك، ويعالجها عامل (worker) مع إعادة المحاولة. أما المهام المجدولة (تقرير ليلي، أو تذكيرات كل 15 دقيقة) فتحتاج إلى شيء يُطلق وفق جدول زمني: ميزة cron لدى المضيف، أو جدول خارجي يستدعي نقطة نهاية محمية في تطبيقك. اجعل المهام آمنة للتشغيل مرتين دائمًا، وسجّل الأخطاء، ولا تعتمد أبدًا على مؤقت داخل خادم يمكن أن ينام أو يُعاد تشغيله.
يشرح هذا الدليل الخيارات، ومتى يناسب كل منها، وكيفية إعدادها مع منشئ تطبيقات بالذكاء الاصطناعي، بما في ذلك على المضيفين الذين ينخفضون إلى الصفر.
ما الذي يُعدّ مهمة خلفية
أي عمل بطيء أو غير موثوق أو غير لازم للاستجابة الفورية للمستخدم:
- إرسال رسائل البريد الإلكتروني والإشعارات والإيصالات
- تغيير حجم الصور، وتحويل ترميز الفيديو، وإنشاء ملفات PDF
- استدعاء واجهات برمجة خارجية بطيئة، بما فيها نماذج الذكاء الاصطناعي للمهام الطويلة
- استيراد البيانات من نظام آخر أو مزامنتها معه
- التنظيف الليلي والتقارير والتذكيرات
تنفيذ هذا داخل الطلب يجعل الصفحات بطيئة وهشة: إذا تعطّل مزوّد البريد، يفشل تسجيل المستخدم. أما نقله إلى الخلفية فيعني أن المستخدم يحصل على استجابة فورية، ويمكن إعادة المحاولة عند الفشل.
الخيارات
| الخيار | كيف يعمل | الإيجابيات | السلبيات |
|---|---|---|---|
| إطلاق وإهمال داخل العملية نفسها | الرد ثم متابعة العمل في الخادم | لا إعداد | يضيع إذا توقف الخادم؛ ولا إعادة محاولة |
مؤقت داخل العملية (setInterval، setTimeout) | مؤقت في الخادم الجاري تشغيله | بسيط جدًا | لا يعمل أثناء النوم؛ وتتكرر المهام مع عدة نسخ |
| طابور مدعوم بقاعدة البيانات | تُحفظ المهام كصفوف؛ ويستولي عليها العامل وينفذها | يستخدم قاعدة البيانات الموجودة؛ دائم؛ سهل الفحص | تبني أنت إعادة المحاولة والقفل؛ يحتاج شيئًا يشغّل العامل |
| طابور مخصص (مبني على Redis أو مُدار) | خدمة طوابير مع عمّال | ميزات نضجة لإعادة المحاولة والجدولة | خدمة أخرى تحتاج إلى تشغيل ودفع ثمن |
| خدمة مهام أو سير عمل مستضافة | طرف ثالث يستدعي نقاط نهايتك مع إعادة المحاولة والجداول | تُدار الجدولة وإعادة المحاولة لك | الاعتماد على المورّد؛ حساب ومفتاح إضافيان |
| جدول خارجي يستدعي نقطة نهاية | مؤقت في مكان آخر يرسل طلب HTTP وفق الجدول | يعمل على أي مضيف، بما فيه الانخفاض إلى الصفر | حد أدنى للفواصل الزمنية وتأخيرات؛ يجب حماية نقطة النهاية |
| cron مدمج لدى المضيف | تشغّل المنصة أمرًا وفق جدول | بسيط حيثما يتوفر | ليس كل مضيف يوفره |
لا توجد إجابة واحدة صحيحة. غالبًا يحتاج التطبيق الصغير إلى طابور قاعدة بيانات فقط مع جدول خارجي.
لماذا تغيّر المضيفات التي تنخفض إلى الصفر الصورة
كثير من المضيفات الحديثة تُنيم تطبيقك عندما لا يستخدمه أحد، وتوقظه عند الطلب التالي. هذا يوفّر المال، لكن الخادم النائم لا يشغّل أي شيفرة: المؤقتات لا تعمل، والعمال لا يستطلعون. في هذه المضيفات يجب أن يبدأ العمل المجدول بطلب وارد، وأسهل مصدر لهذا الطلب هو جدول خارجي. كل استدعاء يوقظ التطبيق (وغالبًا قاعدة بياناته)، لذا فالاستدعاء كل دقيقة يكلّف أكثر من كل 15 دقيقة. اختر أبطأ جدول يلبّي الحاجة.
النمط الأول: طابور مهام في قاعدة البيانات
للمهام المُطلَقة بحدث:
- أنشئ جدول
jobs: النوع، والحمولة (JSON)، والحالة (pendingوrunningوdoneوfailed)، والمحاولات، وrun_after، وlast_error، والطوابع الزمنية. - عندما يحدث شيء، أدرج صف مهمة داخل المعاملة (transaction) نفسها للتغيير الذي سبّبه، بحيث ينجحان أو يفشلان معًا.
- يلتقط العامل المهام المستحقة، ويعلّمها
running، وينفذ العمل، ويعلّمهاdone. - عند الفشل، زد
attempts، واضبطrun_afterلوقت لاحق (مثلًا بعد 1 ثم 5 ثم 30 دقيقة)، واحفظ الخطأ. بعد الحد الأقصى، علّمهاfailedونبّه شخصًا ما.
إذا كان من الممكن أن يعمل أكثر من عامل في وقت واحد، فيجب أن تُقفل المهام بأمان. في Postgres، تسمح SELECT ... FOR UPDATE SKIP LOCKED لكل عامل بأخذ صفوف مختلفة دون تعارض.
ما الذي يشغّل العامل؟ على خادم يعمل دائمًا، حلقة في عملية منفصلة. أما على مضيف ينخفض إلى الصفر، فنقطة النهاية المجدولة في النمط الثاني يمكنها معالجة دفعة من المهام المستحقة عند كل استدعاء.
النمط الثاني: جدول خارجي يستدعي تطبيقك
للمهام المجدولة:
- أضف نقطة نهاية مثل
POST /api/cron/send-reminders. - احمِها: اشترط رمزًا سريًا في ترويسة (header) مخزّنًا كسر لدى الطرفين، وارفض أي طلب آخر.
- اجعلها idempotent: تشغيلها مرتين في الدقيقة نفسها لا يجب أن يرسل تذكيرين. سجّل ما أُرسل.
- اجعل كل استدعاء قصيرًا: عالج دفعة (مثلًا 100 عنصر) ودع الاستدعاء التالي يلتقط الباقي.
- وجّه جدولًا إلى نقطة النهاية.
خيارات الجدولة:
- سير عمل GitHub Actions المجدول. مجاني ضمن حدود الاستخدام في GitHub. اعتبارًا من سبتمبر 2026، تقول وثائق GitHub إن أقصر فاصل هو مرة كل 5 دقائق، وقد تتأخر التشغيلات في أوقات الذروة (خاصة في بداية كل ساعة)، وتُحدَّد الجداول بتوقيت UTC افتراضيًا، وتُعطَّل سير العمل المجدولة في المستودع العام بعد 60 يومًا دون نشاط في المستودع.
- خدمات cron المستضافة. توجد خدمات كثيرة مهمتها الوحيدة استدعاء عنوان URL وفق جدول. تحقق من الموثوقية والحد الأدنى للفاصل وإمكانية التنبيه قبل أن تعتمد عليها.
- جدولة مزوّد السحابة لديك. إذا كنت تستخدم مزوّد سحابة كبيرًا، فلديه جدولة مُدارة تستطيع استدعاء نقطة نهاية HTTPS.
- خدمة مهام أو سير عمل مستضافة. تجمع الجدولة والطوابير وإعادة المحاولة، وتستدعي نقاط نهايتك لكل خطوة. قارن الأسعار والحدود على موقع كل مورّد.
النمط الثالث: دع المزوّد يتولى الأمر
أحيانًا تكون أفضل مهمة خلفية هي ألّا تكتب أي شيء:
- المدفوعات. تعيد Stripe محاولة الدفعات الفاشلة للاشتراكات وترسل Webhooks؛ وأنت تتفاعل بدلًا من الاستطلاع. راجع ما هو Webhook.
- البريد الإلكتروني. يضع المزوّدون الرسائل في طابور ويعيدون محاولة التسليم بعد أن تسلّمهم الرسالة.
- العمل الجماعي بالذكاء الاصطناعي. تقدّم OpenAI وAnthropic واجهات دفعات (batch APIs) للطلبات غير العاجلة، ترسلها ثم تجمع نتائجها لاحقًا.
أوامر لمنشئ التطبيقات بالذكاء الاصطناعي
للطابور:
أضف طابور مهام خلفية باستخدام جدول jobs في قاعدة بياناتنا (النوع، وحمولة JSON، والحالة، والمحاولات، وrun_after، وlast_error). عند تسجيل مستخدم جديد، أدرج مهمة send_welcome_email داخل المعاملة نفسها بدلًا من إرسال البريد مباشرة. أضف دالة processJobs(limit) تحجز المهام المستحقة باستخدام SELECT ... FOR UPDATE SKIP LOCKED، وتنفذها، وتعيد محاولة الفاشلة بعد 1 ثم 5 ثم 30 دقيقة، وتعلّمها فاشلة بعد 4 محاولات.
للمُطلِق المجدول:
أضف POST /api/cron/tick محمية برمز bearer من السر CRON_SECRET؛ ارفض أي طلب بدونه. كل استدعاء يشغّل processJobs(100) ويرسل تذكيرات الحجز المستحقة خلال الـ 24 ساعة القادمة التي لم تُرسل بعد، ويسجّل التذكيرات المُرسلة حتى لا يُرسل شيء مرتين. أعِد ملخصًا بصيغة JSON لما نُفّذ. ثم اكتب سير عمل GitHub Actions يستدعي هذه النقطة كل 15 دقيقة، ويقرأ العنوان والرمز من أسرار المستودع (repository secrets).
أخطاء شائعة
- الاعتماد على
setInterval. يتوقف بصمت عندما ينام الخادم أو يُعاد تشغيله، ويتضاعف عند تشغيل نسختين. - نقطة cron غير محمية. أي شخص يجدها يستطيع تشغيل مهامك، أو رفع فاتورة البريد الإلكتروني لديك.
- مهام ليست idempotent. ستحدث إعادة المحاولات واستدعاءات الجدولة المكررة. صمّم لذلك.
- لا رؤية للفشل. مهمة تفشل بصمت لأسبوع أسوأ من مهمة تفشل بوضوح. سجّل الأخطاء ونبّه عند الفشل المتكرر.
- تشغيلات طويلة في طلب واحد. الطلبات تنتهي مهلتها. عالج على دفعات.
- جدولة متكررة أكثر من اللازم. كل استدعاء على مضيف ينخفض إلى الصفر يوقظ التطبيق ويحاسبه. طابق الفاصل مع الحاجة الحقيقية.
- المناطق الزمنية. تعني «9 صباحًا» أشياء مختلفة لجدولتك (غالبًا UTC) ولمستخدميك. خزّن الأوقات بتوقيت UTC وحوّلها لكل مستخدم.
- أسرار داخل ملف سير العمل. احتفظ بالرموز في مخزن أسرار الجدولة. راجع كيفية حماية مفاتيح API.
قائمة التحقق
- نُقل العمل البطيء خارج طلبات المستخدمين
- تُخزَّن المهام بشكل دائم، لا في الذاكرة فقط
- إعادة محاولة بفواصل متزايدة وحد أقصى
- كل مهمة آمنة للتشغيل مرتين
- نقطة النهاية المجدولة محمية برمز سري
- كل تشغيل يعالج دفعة محدودة
- المهام الفاشلة مرئية، مع تنبيهات
- اختير فاصل الجدولة مع مراعاة تكلفة الاستضافة
- الأوقات مخزّنة بتوقيت UTC
المهام الخلفية في Mythex
لا تصف وثائق Mythex المهام المجدولة أو cron، والتطبيقات المنشورة وقواعد بياناتها تنام عند الخمول وتستيقظ عند الطلب التالي (راجع حدود الاستضافة). لذلك لا تعتمد على المؤقتات داخل تطبيقك. النهج الذي ينجح هو ما سبق: اطلب من الوكيل جدول مهام ونقطة نهاية محمية بالرمز، ثم وجّه جدولًا خارجيًا إلى عنوان تطبيقك المنشور. احفظ الرمز كسر للمشروع، وتذكّر أن كل استيقاظ يستهلك اعتمادات بقدر مدة بقاء التطبيق وقاعدة بياناته مستيقظتين، فاختر فاصلًا معقولًا. وإذا كان تطبيقك يحتاج إلى واجهة API خلفية لهذا الغرض، فوصفة الواجهة الخلفية REST API نقطة بداية، ويغطي كيفية بناء REST API تصميم نقاط النهاية. وإذا كانت الأدوات الخلفية الدائمة أو الروبوتات هي جوهر منتجك، فقد يناسبك مضيف مصمم للعمليات المستمرة.
الأسئلة
ما هي المهمة الخلفية؟
المهمة الخلفية عمل يقوم به تطبيقك خارج الطلب الذي ينتظره المستخدم، مثل إرسال رسائل البريد الإلكتروني، أو تغيير حجم الصور، أو إنشاء التقارير، أو مزامنة البيانات. يحصل المستخدم على استجابة سريعة، وتعمل المهمة بشكل منفصل ويمكن إعادة محاولتها إذا فشلت.
كيف أشغّل مهمة مجدولة إذا لم يوفر مضيفي خاصية cron؟
أضف نقطة نهاية محمية تقوم بالعمل، ثم اجعل جدولًا خارجيًا يستدعيها وفق جدول زمني. من الخيارات سير عمل GitHub Actions مجدول، أو خدمة cron مستضافة، أو جدولة مزوّد السحابة لديك. احمِ نقطة النهاية برمز سري.
هل يمكنني استخدام setInterval أو setTimeout للمهام المجدولة؟
فقط للمهام الصغيرة غير المهمة على خادم يعمل دائمًا. إذا أُعيد تشغيل الخادم، أو توسّع إلى الصفر، أو عمل منه عدة نسخ، فستضيع المؤقتات أو تعمل أكثر من مرة. خزّن العمل المجدول في قاعدة بيانات أو استخدم جدولة مخصصة.
هل يدعم Mythex مهام cron؟
لا تصف وثائق Mythex المهام المجدولة أو cron، والتطبيقات المنشورة تنام عند الخمول. النهج الموثوق هو أن يستدعي جدول خارجي نقطة نهاية محمية في تطبيقك، فيوقظ التطبيق ويشغّل العمل.