نحن نقوم بتشغيل مثيل n8n ذاتي الاستضافة على Railway وواجهنا مشاكل أداء متزايدة مع نمو الاستخدام لدينا.
نحن حالياً نعالج أكثر من 40,000 عملية تنفيذ سير عمل شهرياً.
المشكلة الرئيسية هي أن سير العمل الذي يجب أن ينتهي عادة في حوالي ثانية واحدة يستغرق الآن في الغالب 3-4 ثوانٍ للانتهاء، على الرغم من عدم تغيير منطق سير العمل نفسه.
نحن نرى أيضاً طوابير التنفيذ تتشكل بشكل عشوائي. لا يجب أن يحدث هذا بناءً على إعدادات التكوين الحالية، لأننا لم نقم بتكوين حدود التزامن عن قصد.
مؤخراً أيضاً بدأنا نرى هذا الخطأ:
فشل تنفيذ هذا التنفيذ في المعالجة عدة مرات جداً ولن تتم إعادة محاولته بعد الآن. للسماح بإكمال هذا التنفيذ، يرجى تقسيم سير العمل الخاص بك أو زيادة حجم العمال الخاص بك أو ضبط إعدادات العمال الخاصة بك.
نحن صادقون في عدم معرفة ما يجب أن نحاول بعد ذلك. تبدو مقاييس Railway الخاصة بعمالنا والمثيل الأساسي و PostgreSQL جميعها صحية، بدون اختناقات موارد واضحة. هل واجه أحد شيئاً مشابهاً أو لديه أي أفكار حول ما يجب أن نحققه بعد ذلك؟
ما هي رسالة الخطأ (إن وجدت)؟
يرجى مشاركة سير العمل الخاص بك
(حدد العقد على لوحتك واستخدم اختصارات لوحة المفاتيح CMD+C/CTRL+C و CMD+V/CTRL+V لنسخ ولصق سير العمل.)
مشاركة المخرجات التي يتم إرجاعها بواسطة العقدة الأخيرة
بناءً على الأعراض ورسالة الخطأ التي تراها، فإن مثيل n8n الخاص بك يعاني على الأرجح من Process Overhead و Database Bloat، وهي مشاكل شائعة عندما تتسع المثيلات ذاتية الاستضافة نحو 40,000+ تنفيذ شهرياً.
خطأ “This execution failed to be processed too many times” يحدث عادةً عندما يتم التقاط التنفيذ بواسطة عامل، لكن العامل يفشل في “التسجيل” أو الإنهاء قبل انتهاء صلاحية القفل. يفترض النظام أن العامل توقف عن العمل وينعيد محاولة المهمة حتى يصل إلى الحد الأقصى.
ذكرت إعداد EXECUTIONS_PROCESS. إذا كنت تستخدم وضع own الافتراضي، فإن n8n ينتج عن نفسه عملية Node.js جديدة تماماً لـ كل تنفيذ واحد.
المشكلة: هذا يضيف نفقات عامة كبيرة (CPU و RAM) وينشئ تأخير “cold start” بمدة 1-3 ثواني لكل سير عمل. مع نمو استخدامك، يضع هذا ضغطاً هائلاً على جدول نظام التشغيل والذاكرة.
الحل: غيّر متغير البيئة إلى: EXECUTIONS_PROCESS=main
إذا كنت تقوم بتشغيل n8n في Queue Mode (باستخدام عمال منفصلين)، فأنت على الأرجح تواجه مهلة انتظار القفل. حتى إذا استغرقت سير العمل 4 ثوانٍ فقط، فإن النزاع على قاعدة البيانات أو تذبذب الشبكة على Railway يمكن أن يتسبب في فشل “نبضة القلب”.
الحل: زيادة مدة القفل لإعطاء العمال مساحة أكبر للتنفس. أضف متغير البيئة هذا: QUEUE_WORKER_LOCK_DURATION=120000 (هذا يزيد القفل من 60 ثانية إلى 120 ثانية).
توضح مقاييس Railway وحدة المعالجة المركزية والذاكرة، لكنها لا توضح PostgreSQL table bloat. مع 40,000+ تنفيذ/شهرياً، يمكن لجدولك execution_entity أن ينمو بشكل ضخم، مما يبطئ الاستعلامات التي يستخدمها n8n لإدارة قائمة الانتظار.
التحقيق: تحقق مما إذا كان لديك تقليم التنفيذ مفعلاً. إذا كانت قاعدة البيانات منتفخة، فحتى مقاييس وحدة المعالجة المركزية “الصحية” لن تنقذك من بطء الإدخال والإخراج.
الحل: تأكد من تعيين هذه المتغيرات للحفاظ على قاعدة البيانات الخاصة بك نظيفة:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 (ينظف البيانات التي يزيد عمرها عن 7 أيام؛ اضبط حسب الحاجة).
بما أنك لم تعين حدود التزامن، فإن طفرة مفاجئة من webhooks يمكن أن تشغل عشرات العمليات المتزامنة، مما يتسبب في “قوائم الانتظار العشوائية” وانخفاضات الأداء عندما يعاني النظام.
الحل: عيّن سقفاً عاماً لحماية مثيلك: N8N_CONCURRENCY_PRODUCTION_LIMIT=10 (ابدأ بـ 10 وزيادة إذا سمحت موارد Railway الخاصة بك).
نحن بالفعل نعمل في Queue Mode، وقد طبقنا الآن معظم اقتراحاتك.
كان لدينا بالفعل Queue Mode مُكوّن.
لقد فعّلنا تقليم التنفيذ (EXECUTIONS_DATA_PRUNE، EXECUTIONS_DATA_MAX_AGE، إلخ).
لقد زدنا أيضاً مدة قفل العامل (worker lock duration).
الاقتراح الوحيد الذي لم نطبقه هو EXECUTIONS_PROCESS=main، لأننا من فهمنا أن هذا الإعداد مُتوقف عن الاستخدام في إصدارات n8n الحديثة وليس قابلاً للتطبيق عند استخدام Queue Mode.
في الوقت الراهن، لم تتح لنا الفرصة للتحقق مما إذا كانت هذه التغييرات قد حسّنت الوضع، حيث أن تدهور الأداء يحدث بشكل متقطع. ننتظر الحادثة التالية لنرى ما إذا كانت المشكلة ستظهر مرة أخرى.
لقد فعلت الأشياء الصحيحة بالفعل — وضع الطابور، والتقليم، وقفل الارتفاع — وأنت محق في أن EXECUTIONS_PROCESS مهجور (تم إزالة وضع العملية الخاصة؛ على n8n الحديث يتعلق الأمر بالكل الرئيسي أو الطابور/العامل، لذا كان هذا بلا جدوى بالنسبة لك). المشكلة أنك غيرت أربعة متغيرات في وقت واحد بدون خط أساس، لذا حتى لو تحسن، لا يمكنك معرفة أي مفتاح فعل ذلك. قبل تغيير المزيد، قم بالقياس — إليك كيفية معرفة أي من الاختناقات الثلاثة المعتادة لديك بالفعل: DB I/O، أو تنافس العامل، أو سير عمل واحد يتسرب.
تفعيل التقليم لا يسترجع ما هو موجود بالفعل.EXECUTIONS_DATA_PRUNE يوقف فقط الصفوف الجديدة من التراكم خلال عتبتك للأمام — لا يقلص جدول متضخم بالفعل. في Postgres، تصبح الصفوف المحذوفة tuple ميتة، والحجم على القرص من execution_data (جدول الحمولة — الكبير، وليس execution_entity) بالإضافة إلى فهارسه يبقى كبيراً حتى يتم تنظيفه بـ vacuum، وغالباً ما لا يتمكن autovacuum من مواكبة جدول أصبح كبيراً بالفعل. لذا “فعلنا التقليم ولم يتغير شيء” هو بالضبط ما تتوقعه إذا كان بطؤك هو DB I/O. تحقق منه مباشرة:
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC; — إذا كان execution_data يحتوي على ملايين tuple ميتة و last_autovacuum قديم أو فارغ، فهذا هو إجابتك.
SELECT pg_size_pretty(pg_total_relation_size('execution_data')); — إذا كان الحجم المادي ضخماً، فإن التقليم للأمام لن يساعد؛ تحتاج إلى pg_repack (يعمل بدون اتصال، بدون قفل طويل) لاسترجاع الفضاء فعلاً. VACUUM FULL يعمل أيضاً لكن يقفل الجدول.
رسالة الخطأ الخاصة بك إشارة محددة، وليست بطء عام. “فشل تنفيذ هذا في المعالجة عدة مرات” هي مسار الوظيفة المتوقفة: عامل استأجر التنفيذ، لم ينته أو heartbeat قبل انتهاء القفل، تم إعادة قائمة الانتظار، وبعد N محاولات تم تعليمه كفاشل. رفع القفل الخاص بك يساعد فقط إذا كان السبب تنفيذات طويلة بحق. الذي يعض الناس على Railway ويختبئ خلف “CPU صحي” هو عامل يضرب حده في الذاكرة — Railway تعيد تشغيل حاوية بصمت تتجاوز سقف RAM، وكل تنفيذ أثناء الرحلة على ذلك العامل يرمي بالضبط هذا الخطأ. نسبة CPU% تبدو بخير لأن القتل هو الذاكرة، وليس CPU. انظر إلى عدد إعادة التشغيل لخدمة العامل ورسم الذاكرة (وليس CPU) وتحقق مما إذا كانت إعادة التشغيل محاذاة مع الفشل.
واحدة أخرى، إذا كان أي سير عمل ينقل الملفات. في 40k/شهر، إذا تعاملت مع PDFs/الصور وكان وضع البيانات الثنائية لا يزال افتراضياً، فهذا مصدر تضخم الذاكرة وDB، وهو أيضاً غير موثوق عبر عمال متعددين (الملف ينزل على قرص محلي لعامل واحد). N8N_DEFAULT_BINARY_DATA_MODE=s3 إذا كان الأمر كذلك — تجاهل إذا كنت كل JSON.
الترتيب الذي سأذهب إليه: استعلامات Postgres الاثنان أولاً (قواعد DB داخل أو خارج في حوالي دقيقة واحدة)، ثم رسم إعادة تشغيل العامل/الذاكرة (قواعد OOM)، ثم غير شيء واحد وراقب رقم واحد بحيث تكون الجولة التالية قابلة للقياس بالفعل.
إذا كان مفيداً: تحديد أي من هذه هو بالفعل اختناقك من بيانات pg_stat الحقيقية ومقاييس العامل هو نوع الفحص الذي أفعله كتشخيص مكتوب بسعر ثابت $49 — أنت ترسل نتائج الاستعلام المعقمة + رسم ذاكرة العامل، أرسل سبب الجذر وقائمة إصلاح مرتبة حسب الأولويات، بشكل غير متزامن، بدون مكالمة. إذا كنت تريد بعد ذلك مراقبتها بشكل مستمر — تنبيهات على عمليات OOM-restart للعامل والطابور-انتظار قبل أن تتحول إلى تنفيذات فاشلة — فهذا إعداد مراقبة $149/شهر. لكن قم بتشغيل استعلامي Postgres الاثنان أولاً؛ إذا كان مجرد تراكم متراكم، فإن pg_repack يصلحه ولن تحتاج إلي.
متابعة واحدة مع نقطة كان يجب أن أضيفها في المرة الأولى، لأنها هي الشيء الذي يجعل نصيحة التقليم تبدو وكأنها لم تنجح.
إذا قمت بتشغيل استعلامات pg_stat الاثنين وكان execution_data لا يزال ضخماً، فتحقق مما إذا كانت الصفوف قد اختفت فعلاً أم تم وضع علامة عليها فقط كمحذوفة قبل أن تستنتج أن التقليم معطل. يقوم التقليم في n8n بالحذف على مراحل، لذا توجد نافذة زمنية حيث يتم وضع علامة على عمليات التنفيذ للإزالة لكن صفوف الحمولة الثقيلة لا تزال موجودة فعلياً، وفي نسخة مشغولة أعيد تشغيلها، يمكن للمتراكم المميز أن يجلس هناك إلى أجل غير مسمى. مقارنة عدد الصفوف في execution_entity مع ما تعرضه واجهة المستخدم كعمليات تنفيذ موجودة عادة ما يكون كافياً لمعرفة الوضع الذي أنت فيه، وهذا يغير الحل تماماً: إذا كانت الصفوف قد اختفت بالفعل، فأنت تواجه انتفاخ الصفوف الميتة وتحتاج إلى إعادة تجميع، وإذا كانت لا تزال موجودة، فلن تساعد أي كمية من التنظيف حتى يتم إزالتها فعلياً.
النصف الثاني مهم بشكل خاص على Railway. يعيد pg_repack بناء الجدول جنباً إلى جنب مع الجدول الأصلي قبل التبديل، لذا فهو يحتاج إلى مساحة حرة تقريباً مساوية لحجم الجدول وفهارسه. إذا كان execution_data معظم الحجم، فستعمل إعادة التجميع لفترة من الوقت ثم تفشل على القرص، وستكون في وضع أسوأ مما كنت عليه عندما بدأت. تحقق من المساحة الحرة مقابل pg_total_relation_size أولاً. إذا لم تكن هناك متسع للعمل، فالطريقة الأرخص هي تقليل عدد الصفوف بشدة أولاً، على دفعات حتى لا تمسك معاملة واحدة ضخمة، وفقط بعد ذلك استرجع المساحة.
من الجدير بالذكر بصراحة أنه إذا أشارت الاستعلامات الاثنان إلى العامل بدلاً من قاعدة البيانات، فلا شيء مما ورد أعلاه هو مشكلتك وسأتجاهله.