تضيع الوظائف المؤجلة بشكل متقطع بين مجموعة zset لجدولة Sidekiq والتنفيذ (دون perform، دون إعادة محاولة، دون أثر)

Discourse 2026.10.0-latest.1 / Sidekiq 7.3.10 / Ruby 4.0.9

الملخص

منذ ترقية منتدى مستضاف ذاتيًا من Discourse 2026.2.0-latest (Ruby 3.3.8) إلى الإصدار 2026.10.0-latest.1 (Ruby 4.0.9، Rails 8.1.4، Sidekiq 7.3.10) في 2026-10-26، تفقد المهام المجدولة باستخدام Jobs.enqueue_in بشكل متقطع وصامت: تختفي المهمة من مجموعة البيانات المرتبة schedule عند وقت استحقاقها، لكن الدالة perform لا تنفذ أبدًا. لا توجد إعادة محاولة، ولا توجد مهمة ميتة، ولا استثناء مسجل (نستخدم Logster::RedisStore، لذا تظهر الاستثناءات المكتومة — ولا توجد أي)، ويظل stat:failed عند الصفر. نسبة الفقدان الملاحظة عبر 9 حالات واقعية/مضبوطة: 4 فقدت / 5 نُفذت (~40 %).

البيئة

  • تثبيت Docker الرسمي (./launcher rebuild)، موقع واحد.
  • Discourse 2026.10.0-latest.1، صورة discourse/base:2.0.20261026-0138.
  • Ruby 4.0.9 (2026-10-23)، Rails 8.1.4، Sidekiq 7.3.10.
  • UNICORN_SIDEKIQS=1 (يتم مراقبة sidekiq بواسطة Demon::Sidekiq، وتفرعه بواسطة pitchfork).
  • حالة Sidekiq في قاعدة بيانات Redis مخصصة (Discourse.sidekiq_redis_config)، منفصلة عن Redis الخاص بـ Discourse.
  • عملية Sidekiq سليمة خلال جميع الملاحظات: 0 إعادة تشغيل، [0 من 5 مشغولة]، الطوابير فارغة، retry = 0، dead = 0، stat:failed = 0.
  • لا يوجد استنفاد لذاكرة الوصول العشوائي (OOM)، ولا أحداث dmesg، ولا إعادة تشغيل للحاويات.

الملاحظات (2026-10-30، حالة واقعية + تجارب مضبوطة)

المهمة المتأثرة هي Jobs::NotifyMailingListSubscribers، والتي تم إدراجها في الطابور باستخدام Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes, ...) (600 ثانية) → مجموعة البيانات المرتبة schedule مع النتيجة = وقت الاستحقاق.

وقت الاستحقاق (UTC) المهمة النتيجة
15:33:05 حقيقية (منشور 282) فقدت — اختفت من schedule، لم تُنفذ أبدًا
15:50:10 مضبوطة (post_id 999999 → عودة صامتة مبكرة) نُفذت (+1 stat:processed بعد ~8 ثوانٍ من الاستحقاق)
16:04:18 حقيقية (منشور 283) نُفذت (تمت كتابة أخطاء SMTP، انظر أدناه)
16:21:44 حقيقية ×2 + مضبوطة نُفذت جميعها
17:34:39 حقيقية ×2 فقدت ×2
17:42:39 مضبوطة نُفذت (+1، بعد 11 ثانية من الاستحقاق)
17:55:59 حقيقية فقدت

الأدلة المجموعة لكل مهمة “مفقودة”:

  • zrange schedule لم يعد تحتوي على الحمولة (حدثت عملية zrem)، وهي غير موجودة في retry، ولا في dead، ولا في أي طابور (queue:low وما إلى ذلك كلها 0)، ولا في نسبة stat:processed.
  • سجّل مراقب pg_stat_activity الذي يأخذ عينات كل 300 مللي ثانية عبر نافذة الاستحقاق صفرًا من استعلامات SQL من اتصال Sidekiq — حتى SELECT ... FROM posts الأولية التي ينفذها execute أولاً لم تُسجّل.
  • لا يحتوي Logster::RedisStore (__LOGSTER__MAP) على أي مدخل جديد لهذه المهام، بينما يتم كتابة المداخل لعمليات التنفيذ الناجحة (فشل SMTP لمشكلة أخرى غير ذات صلة قمنا بإصلاحها بالتوازي).
  • يبقى عنوان عملية Sidekiq [0 من 5 مشغولة]؛ لا إعادة تشغيل لـ sidekiq (نفس المعرف PID منذ الإقلاع)، لا OOM.
  • لم يتغير عدد SkippedEmailLog للمهام المفقودة (عمليات التنفيذ التي تفشل تكتب صفوفًا هناك بالفعل).

تم التأكيد الإيجابي على عمليات التنفيذ “الناجحة”: أنتجت مدخلات خطأ SMTP لكل مستخدم (التُقطت في SkippedEmailLog.custom_reason)، لذا فإن فئة المهمة وشكل الحمولة متطابقان بين الحالات المفقودة والمنفذة (نفس الفئة، نفس الطابور low، فقط post_id يختلف).

لم يعرض التثبيت السابق (Discourse 2026.2.0-latest، Ruby 3.3.8) هذا السلوك: كانت مهام قائمة البريد المؤجلة المماثلة تُسلم بشكل موثوق في 2026-10-01 و 2026-10-21 (تم التحقق عبر EmailLog وطوابير MTA اللاحقة).

التخفيف المطبق

قمنا بتعيين email_time_window_mins = 0 لهذا الموقع بحيث يتخطى enqueue_in(0) المفتاح at ويدفع المهمة مباشرة إلى طابورها (client_push بدون at)، متجاوزًا نظام سحب الجداول الزمنية (poller) بالكامل. بعد هذا التغيير، تم إنشاء جميع صفوف EmailLog المتوقعة (6 صفوف) فورًا، وقبول رسائل البريد الإلكتروني من قبل MTA اللاحق.

المقايضة المقبولة: يتم الآن إرسال إشعارات قائمة البريد الإلكتروني فورًا بدلاً من تجميعها +10 دقائق.

الأسئلة

  1. هل هذا انحدار معروف في مزيج 2026.10 / Sidekiq 7.3.10 (أو في Demon::Sidekiq / نظام السحب المجدول تحت Ruby 4.0)؟
  2. ما هي التشخيصات الإضافية التي تريدون التقاطها عند الحدوث التالي (على سبيل المثال، تفعيل قياس أداء المهام عبر DISCOURSE_LOG_SIDEKIQ=1، تتباعات الاستثناءات في Sidekiq، دumps لخيوط النظام المجدول)؟
إعجابَين (2)