عمل Sidekiq طويل المدة يعيد تشغيل الكود الداخلي

أقوم حاليًا بإنشاء إضافة مرتبطة بموقع meritmoot.com (الذي لا يزال قيد التطوير ويصدر في نسخ متتالية). ونظرًا لاستخدامه بيانات خارجية، أستخدم مهمة Sidekiq طويلة الأمد. للأسف، لسبب ما، تستمر المهمة في إعادة تشغيل الكود الداخلي دون أن تفشل أو تعرض أي مخرجات خطأ. هل هناك شيء قد أغفله في Sidekiq قد يتسبب في إعادة تشغيل المهمة؟

في آخر مهمة لي (التي يُفترض أن تُنفذ مرة واحدة يوميًا) المتعلقة بـ “دعوات الحضور”، أعيد تشغيل المهمة في الفترات الزمنية التالية دون ظهور أي خطأ:

0س-58د-42ث (وقت بدء التكرار الأول)
1س-30د-12ث (إعادة التشغيل الأولى)
2س-1د-1ث (إعادة التشغيل الثانية)
3س-46د-49ث
4س-17د-11ث
4س-47د-33ث

لم تنتهِ المهمة، وتم إعادة بدء جميع التقدم المحرز. وتجدر الإشارة إلى أن لدي عملية تسجيل خاصة بي تعيد توجيه مخرجات الخطأ (stderr) والمخرجات القياسية (stdout) للكود الداخلي، رغم أنني أشك في أنها قد تتداخل مع Sidekiq. (اسألني إذا أردت إلقاء نظرة عليها، فهي مفيدة جدًا في التطوير!)

من الممكن لي حفظ التقدم المحرز داخل الكود، لكنني أفضل عملية أبسط لأن حفظ التقدم يخلق عبئًا إضافيًا. هل هناك شيء قد أغفله في Sidekiq قد يتسبب في إعادة تشغيل الكود؟

إذن، تريد مهمة Sidekiq تستغرق عمداً أكثر من ساعة في كل تشغيل؟ هل يمكنك شرح السبب قليلاً؟

أنا أجمع كمًا كبيرًا من البيانات الخارجية وأخزّنها داخليًا على موقعي الإلكتروني. تتعلق البيانات بمعلومات الكونغرس الأمريكي

تعديل: مثل مشاريع القوانين والتصويتات العلنية وأعضاء الكونغرس المتاحين للجمهور

هل تعاني من نفاد الذاكرة؟ في استضافتنا، قمنا بإعداد Sidekiq لإعادة التشغيل تلقائيًا إذا بدأ في استخدام ذاكرة زائدة.

إليك بعض الأفكار:

  • دفع البيانات عبر واجهة برمجة التطبيقات (لكنك ستصل إلى حدود المعدل)
  • تشغيل سكريبت خارجي، خاصة إذا كان يحتاج فقط إلى جلب الدفعة الأولية من البيانات، وستكون التحديثات أسرع

هل تقصد الذاكرة في قاعدة البيانات أو القرص الصلب؟ نعم، أستخدم الكثير منها حاليًا. أنا أبحث حاليًا في اقتراح مُعدّل قليلاً من @pfaffman، حيث أقوم بفork العملية، مما يسمح للخيط الرئيسي بالخروج، مع إنشاء عملية فرعية لها نفس السياق (وهو في الأساس سكريبت خارجي فيما يتعلق بـ sidekiq)

اختبار النمط

pid = Process.fork
if pid.nil? then
  # في العملية الفرعية
  exec "whatever --take-very-long"
else
  # في العملية الأصلية
  Process.detach(pid)
end

من https://stackoverflow.com/questions/806267/how-to-fire-and-forget-a-subprocess#806326 لحل المشكلة. إنها مشكلة غريبة بعض الشيء، لكن الواجهة البرمجية التي أتعامل معها لا تدعم وظيفة التحديث، لذا فإنني في الأساس أعيد تحميل البيانات عن طريق إعادة تنزيل أجزاء كبيرة من الواجهة البرمجية كل يوم :expressionless: :man_shrugging:

سأخبرك بكيفية سير الأمور خلال بضع ساعات

تعديل: توقف مرة أخرى - أعتقد أنني سأحفظ تقدمي بشكل دوري وأبحث عن طرق لجعل العملية أكثر كفاءة

بدلاً من ذلك، لماذا لا تجعل وظيفتك تعمل في دفعات أصغر؟ هل من الضروري حقًا أن تستغرق 4 ساعات؟ مزامنة 10 مواضيع، ثم 10 أخرى… وهكذا.

لا يحتوي Sidekiq على أي آلية لإنهاء الوظائف طويلة الأمد؛ فإعادة بناء التطبيق ستفعل ذلك، وكذلك الترقية عبر واجهة الويب.

شكرًا لك على كل المساعدة،

لقد انتهيت في النهاية بحذف كود العملية، حيث لم يكن فعالًا. كانت مهمة إعادة التشغيل مجرد تغطية لمشكلة أساسية تتمثل في عدم الكفاءة الحقيقية :sweat_smile:. وبدلاً من ذلك، قمت بما يلي:

  • كتابة كود SQL جماعي (وهو أسرع بكثير من التسلسلي)، مع تحديد متى يحتاج حقًا إلى التحديث، والسماح لي بتجاوز استخدام فئة PostRevisor لإعادة تحديث العناصر التي لم تتغير.
  • زيادة الكفاءة في استرجاع البيانات عبر HTTP باستخدام اتصالات دائمة وبيانات أخرى تتضمن عناصر مضغوطة (حيثما أمكن).

لقد وجدت أن كتابة أوامر SQL جماعية توفر تسريعًا هائلاً. ما أقوم بتحديثه هو:

جدول المنشورات: الأعمدة cooked و last_updated
جدول المواضيع: الأعمدة title و last_updated

فكرتي التالية هي تجاوز فئة PostRevisor تمامًا عن طريق القيام بشيء من هذا القبيل:

1 - نقل البيانات إلى جدول مؤقت

2 - UPDATE topics FROM temp_table
SET topics.title = temp_table.title, topics.last_updated = temp_table.last_updated
WHERE topics.id = temp_table.id AND topics.title != temp_table.title

3 - UPDATE posts FROM temp_table
SET posts.raw = temp_table.raw, posts.last_updated = temp_table.last_updated
WHERE posts.id = temp_table.id AND posts.raw != temp_table.raw

4 - ثم تشغيل مهمة إعادة فهرسة البحث لأن العنوان والمحتوى قد تغيرا.

هل هناك شيء أغفلته؟ Discourse معقد، وبتجاوز فئة PostRevisor أشعر أنني قد أخطو على جداول ليس لدي خبرة فيها (مثل post_stats و post_timings و post_uploads و quoted_posts، وهي بعض الجداول التي أراها في قاعدة البيانات). ومع ذلك، لا أحتاج أيضًا إلى كل التحقق الذي توفره فئة PostRevisor، لأن النظام يحصل على هذه التعديلات من مصدر موثوق ومتوقع. يبدو أن هذا الحل عشوائي إلى حد ما.

ما رأيك؟

تحديث - كنت أقوم ببعض فحوصات الكود نظرًا لوجود عدد غريب من التحديثات بمرور الوقت، ووجدت أن هناك شيئًا يسبب تحديثات غير مبررة لعناصر البيانات التي لم تتغير فعليًا في تنسيق JSON الخام الخاص بها. بمجرد حل هذا الخطأ، قد لا يكون ما ورد أعلاه ضروريًا بعد الآن :tipping_hand_man: كان ينبغي لي إجراء الاختبارات… كان سيوفر لي الكثير من الوقت. أعتقد أنني قد أجرب ما ورد أعلاه على أي حال، لكنه لن يكون أولوية. سيساعد في التحديثات السريعة عندما أقوم بتغيير تنسيق عرض البيانات. بالإضافة إلى ذلك، فقد كُتب بالفعل، لكنه لم يُختبر بعد.

تم الانتهاء من كود التحديث الجماعي - هل تود أن يتم دفعه إلى فرع محدد بمجرد أن يصبح أكثر استقرارًا؟ استخدامه محدد جدًا، لكنه قادر على تحديث آلاف السجلات بسرعة، بما في ذلك الوسوم. تم بناؤه لتمديد TopicsBulkAction. إليك ملف readme الذي كتبته إذا أردت معلومات أكثر تفصيلاً:

  # المدخلات
  #   - قائمة من الهاش تحتوي على cooked، post_id، topic_id، title، updated_at، tags (سيشير raw إلى cooked)
  #   [{post_id: #, cooked: "", topic_id: #, title: "", updated_at: date_time, tags: [{tag: "", tagGroup: ""}, ... ] } ,  ... ]
  #   - category_name، اسم الفئة التي يتم تحديثها. يُستخدم هذا لأغراض فهرسة البحث.

  # سمات الهاش الاختيارية لتضمينها في عناصر القائمة:
  #   - raw، إذا لم يتم تضمينها ستكون مساوية لـ cooked.
  #   - fancy_title، إذا لم يتم تضمينها ستكون مساوية لـ title.
  #   - slug، إذا لم يتم تضمينها سيتم معالجتها من title (هذا يتعلق بعنوان URL الخاص بها).

  # حالة الاستخدام: تحديث المواضيع بانتظام من مصدر بيانات خارجي متغير غير مخصص لـ Discourse بطريقة فعالة
  # لعكس تحديث المعلومات. لاحظ أن هذا الكود ليس مخصصًا للنشر العام للمواضيع أو المنشورات، بل لتحديث
  # عنوان الموضوع والمنشور الرئيسي للموضوع. أما بالنسبة لمراجعة المنشورات العامة، انتقل إلى PostRevisor في lib/post_revisor.rb.

  # - يفترض أن البيانات مطبوخة مسبقًا، أو مطبوخة بشكل مخصص، أو تُعرض كما هي. لا يتم التحقق من صحة البيانات.
  # - يجب أن تحتوي المنشورات على (cook_methods: Post.cook_methods[:raw_html]) عند الإنشاء إذا كان raw == cooked.
  #     افعل ذلك إذا كنت تكتب HTML مخصصًا للعرض داخل المنشور.
  #     وإلا فقد يعيد Discourse طبخها في المستقبل وهو أمر غير مرغوب فيه. تأكد من أن مصدر المعلومات
  #     موثوق وأن محتوياتها محمية من حقن الأكواد.
  # - إذا لم يكن ما سبق مثاليًا، فتأكد من تضمين raw، وتحديد طريقة الطهي الصحيحة عند إنشاء المنشور
  #     (في حال أعاد النظام طبخه)، ثم مرر raw عبر طريقة الطهي المختارة، وأدرج raw والمخرجات المطبوخة
  #     في الهاش الخاص بك.
  # - يتتبع عدد الكلمات من خلال ملاحظة الفروقات بين عدد الكلمات قبل وبعد المنشور، ونقل ذلك
  #     إلى الموضوع.
  # - يتتبع عدد الوسوم بطريقة مماثلة.