الانتقال إلى المحتوى الرئيسي

نظرة عامة

تشغيل الدالة المنطقية محدّد بحده الأقصى timeoutSeconds ‏(900 ثانية كحد أقصى). أي شيء لا يمكنه أن ينتهي ضمن تلك المهلة الزمنية — مثل إعادة المزامنة الكاملة، أو التوزيع على مستوى كل سجل، أو واجهة برمجة تطبيقات لطرف ثالث تفرض حدودًا على المعدل — يجب تقسيمه إلى عمليات تشغيل أصغر. يقوم enqueueJobs بذلك بالضبط: فهو يطلب من عمّال Twenty تشغيل إحدى دوال منطق تطبيقك لاحقًا، مرة واحدة لكل حمولة، بحيث يتم كل تشغيل في عملية مستقلة وبمهلة زمنية خاصة به. يعود المستدعي فورًا.

إضافة عمليات التشغيل إلى قائمة الانتظار

استورد enqueueJobs من twenty-sdk/logic-function، ووجّهه إلى universalIdentifier الخاص بدالة المنطق المراد تشغيلها، ومرّر حمولة واحدة لكل تشغيل لإضافتها إلى قائمة الانتظار.
src/logic-functions/sync-all-contacts.ts
يتلقى كل تشغيل حمولته كوسيط للمعالج، تمامًا مثل أي مشغّل آخر. يجب أن تنتمي الدالة الهدف إلى نفس التطبيق الذي تنتمي إليه الدالة المستدعية — يُرفض وضع دالة تطبيق آخر في قائمة الانتظار مع ظهور Logic function not found، ولا تتم إضافة أي شيء إلى قائمة الانتظار. تقبل مكالمة واحدة ما يصل إلى 200 وظيفة.
تُرجِع enqueueJobs فور قبول المهام، وليس عند تشغيلها. لا تُرجِع نتائج الأهداف — اجعل كل دالة هدف تكتب ما تُنتجه في مخزن المفاتيح والقيم أو في سجل مساحة العمل إذا كنت بحاجة إلى قراءته لاحقًا. لمعرفة ما إذا كان تشغيل قد اكتمل، استعلم دوريًا عن حالة الوظيفة.
تم إهمال المساعد الأقدم enqueueJob، الذي يضيف مهمة واحدة إلى قائمة الانتظار لكل استدعاء. استخدم enqueueJobs مع قائمة jobs تضم عنصرًا واحدًا بدلًا من ذلك. الوسيطة payloads مهجورة أيضًا — مرر jobs بدلًا منها، إذ تقبل الحمولة نفسها لكل تشغيل وتتيح لك تسمية كل واحدة.

استعلم دوريًا عن الوظائف

يعيد enqueueJobs قيمة jobId واحدة لكل عملية تشغيل موضوعة في قائمة الانتظار، بالترتيب نفسه للمهام التي مررتها. تسترجع getJobs مدى تقدّم عمليات التشغيل تلك في استدعاء واحد.
يقرأ استدعاء واحد لـ getJobs ما لا يزيد على 200 معرّف، وهو الحد الأقصى نفسه الذي يطبّقه enqueueJobs على دفعة. يحمل كل إدخال المعرّف الذي طلبته: تظهر المهمة التي لم يستلمها عامل بعد عادةً بالحالة PRIORITIZED بدلاً من WAITING، لأن مهام التطبيق تُضاف إلى قائمة الانتظار بأولوية منخفضة ثابتة. COMPLETED وFAILED هما الحالتان النهائيتان. ينبغي أن تتوقف حلقة الاستعلام أيضاً من تلقاء نفسها بعد عدد محدود من المحاولات بدلاً من الانتظار إلى الأبد، إذ لا يمكن قراءة عملية التشغيل إلا ما دامت قائمة الانتظار تحتفظ بها: 4 ساعات بعد الاكتمال، و7 أيام بعد الفشل، وفي كلتا الحالتين لا يُحتفَظ إلا بأحدث 1000 مهمة في تلك الحالة. تعيد getJobs المهام التي لا تزال تحتفظ بها فقط، لذا فإن المعرّف الذي تمّت إزالته — أو الذي لم يوجد قط — لا يظهر ببساطة في النتيجة بدلاً من أن يتسبب في خطأ للدفعة بأكملها. قارن ما تسترده بما طلبته لاكتشاف تلك الحالات، وتعامل مع المعرّف المفقود على أنه “لم يعد معروفاً”، وهي ليست الإجابة نفسها التي تعني “لم يتم تشغيله قط”. تقتصر الحالات على مساحة العمل التي يُجرى منها الاستدعاء، لذلك لا يمكن لمساحة عمل أبداً قراءة مهمة مساحة عمل أخرى.

اختر معرّف مهمتك بنفسك

مرّر jobs بدلاً من payloads لتسمية كل عملية تشغيل بنفسك. المعرّف الذي تمرره هو المعرّف الذي تأخذه getJobs، لذا يمكن للمستدعي الاستعلام عن مهمة دون الحاجة إلى تخزين ما أعاده enqueueJobs.
يتكوّن المعرّف من 128 حرفاً كحد أقصى من الأحرف أو الأرقام أو _ أو . أو -، ولا يجوز لمهمتين في استدعاء واحد مشاركة معرّف واحد. مرّر إما payloads أو jobs، وليس كليهما أبداً. عندما تحذف jobId، ينشئ Twenty معرّفاً. يُعد jobId أيضاً مفتاحاً لعدم التكرار: تُقبل إضافة معرّف إلى قائمة الانتظار إذا كانت قائمة الانتظار لا تزال تحتفظ به، ولكنها لا تبدأ عملية تشغيل ثانية، وتستمر getJobs في الإبلاغ عن الأولى. وهذا يجعل إعادة محاولة الإضافة إلى قائمة الانتظار آمنة. هناك تحذيران: لا يستمر الضمان إلا ما دامت المهمة محفوظة، لذا يمكن للمعرّف نفسه بدء عملية تشغيل جديدة بمجرد إزالة العملية الأصلية، وتكون المعرّفات مشتركة بين جميع التطبيقات في مساحة عمل، لذا أضف بادئة إلى معرّفاتك إذا كان التصادم مهماً.

خيارات المهمة

تنطبق الخيارات على كل عملية تشغيل في الدفعة.
أولوية التنفيذ غير قابلة للضبط بعد. عمليات المهام في قائمة الانتظار تعمل دائمًا بأدنى أولوية، بحيث لا يتأخر عمل المنصة خلف مهام التطبيق. سيتم توفير إمكانية التحكّم في الأولوية قريبًا.
يرث التشغيل المُضاف إلى قائمة الانتظار المستخدم الفعلي (acting user) الخاص بالدالة التي أضافته، لذلك يعمل بنفس الأذونات.

أعِد محاولة فشل عابر

لا يعيد Twenty محاولة كل استثناء صادر عن تعليمات التطبيق البرمجية. يُعامل الخطأ العادي الذي تم طرحه على أنه فشل دائم. بالنسبة إلى فشل عابر، يمكن لدالة منطقية موضوعة في قائمة الانتظار طلب ما يصل إلى ثلاث إعادات محاولة عبر طرح RetryableLogicFunctionError.
اطرح RetryableLogicFunctionError مباشرةً عندما يكون ذلك ممكنًا. إذا وسّعته، فلا تستبدل name الخاص به: يتعرّف Twenty على الاسم المُسلسل RetryableLogicFunctionError عبر بيئات وقت التشغيل المختلفة. تكون قيمة retryCount هي 0 للتنفيذ الأولي، ولا تزداد إلا عندما تطلب تعليمات التطبيق البرمجية إعادة محاولة. تكون قيمة maxRetries بحد أقصى 3، وقد تكون أقل عندما تكون للمهمة الموضوعة في قائمة الانتظار حدود إجمالية أصغر لإعادة المحاولة. لا تؤدي حالات فشل المنصة إلى زيادة retryCount، رغم أنها لا تزال تستهلك ميزانية الأمان الإجمالية لقائمة الانتظار. تؤخر قائمة الانتظار محاولات إعادة المحاولة باستخدام تراجع أسي وتذبذب. لا يُضمن التأخير الدقيق عمدًا، لذا ينبغي ألا تعتمد تعليمات التطبيق البرمجية على حدوث إعادة محاولة في وقت محدد بدقة. بمجرد الوصول إلى maxRetries، يُسجَّل RetryableLogicFunctionError آخر باعتباره فشل التطبيق النهائي دون تنفيذ آخر.
تعيد عمليات إعادة المحاولة تشغيل المعالج بالكامل، وقد تحدث بعد نجاح بعض الآثار الجانبية. اجعل المعالج متوافقًا مع التكرار قبل طلب إعادات المحاولة.

طريقة الاستخدام: التمرير عبر مزامنة طويلة

النمط الكلاسيكي هو دالة تضيف نفسها إلى قائمة الانتظار مع المؤشر (cursor) التالي. يقوم كل تشغيل بتنفيذ صفحة واحدة من العمل ضمن مهلة التنفيذ الخاصة به، وتتوقّف السلسلة عندما لا يبقى شيء.
src/logic-functions/sync-contacts-page.ts

توزيع متشعّب لكل سجل

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

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

قاعدتان تغطيان تقريبًا كل مهمة طويلة: استخدم الاستدعاء الذاتي (recursion) بدلًا من الحلقات (looping)، وعالِج جزءًا محدود الحجم في كل تشغيل. التشغيل الذي يحاول تنفيذ كل شيء دفعة واحدة هو نمط الفشل — يصل إلى مهلة التنفيذ، ومع إعادة المحاولة يبدأ كل شيء من الصفر. بدلًا من ذلك، اضبط حجم الجزء بحيث ينتهي بشكل مريح ضمن timeoutSeconds، ثم خزّن موقعك الحالي، وأضِف التشغيل التالي إلى قائمة الانتظار.
src/logic-functions/enrich-companies-batch.ts
ما الذي يجعل هذا متماسكًا:
  • اضبط حجم الجزء بناءً على أبطأ عنصر، وليس على المتوسّط. يجب أن يتناسب حاصل ضرب CHUNK_SIZE × worst-case item time ضمن timeoutSeconds مع هامش احتياطي، وإلا فإن نهاية الجزء ستُفقَد عندما يتم إيقاف التشغيل بسبب انتهاء المهلة.
  • اجعل شرط الإنهاء صريحًا. استخدم الاستدعاء الذاتي فقط عندما يعود جزء كامل. سلسلة تتوقّف على أساس “عدم وجود نتائج” فقط ستستمر إلى الأبد إذا أعاد المصدر صفحة قصيرة في منتصف الطريق.
  • ثبّت التقدّم قبل إضافة التشغيل التالي إلى قائمة الانتظار، بحيث يُعاد تشغيل الحلقة الفاشلة من آخر جزء مكتمل بدلًا من البداية.
  • اجعل كل جزء قابلًا للتكرار دون آثار جانبية (idempotent). يجب ألّا يؤدّي إعادة معالجة جزء واحد بعد إعادة المحاولة إلى كتابة مزدوجة — اربط عمليات الكتابة بالسجل أو المعرّف الخارجي الذي تعالجه.
  • فضّل سلسلة مجزّأة على توزيع واحد ضخم (giant fan-out) عندما يتعامل العمل مع طرف ثالث محدود المعدّل (rate-limited): سلسلة مع delayMs تنظّم نفسها، في حين أن آلاف المهام المضافة إلى قائمة الانتظار دفعة واحدة تصبح جميعها مؤهّلة فورًا.