إعادة خبز جميع المنشورات تحسين

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

يستغرق هذا الاستدعاء المحدد ساعات لإكماله على نظام الاختبار الحالي الخاص بي:

Post.update_all('baked_version = NULL')

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

أيضًا، ما يبدو أنه أكبر ضربة للأداء هو استخدام OFFSET في الكود. يتسبب هذا السطر في مشاكل ضخمة:

Post.order(id: :desc).offset(i).limit(batch).each do |post|

عندما تصل إلى ملايين المشاركات، يصبح هذا مشكلة لأن offset يتطلب معالجة الجدول بأكمله حتى النطاق الحالي المتضمن. ثم يتجاهل المحرك كل شيء أقل من نطاق الإزاحة قبل إرجاع مجموعة النتائج النهائية. هذا يتسبب في استغراق الاستدعاء عدة دقائق حتى عند وجود بضعة ملايين من المشاركات فقط. يعتمد عدد المرات التي يحدث فيها هذا على حجم الدفعة (محفوظة بشكل ثابت عند 1000 افتراضيًا).

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

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

في الواقع، في حالتي، أرى تحسنًا بمقدار 3 رتب من حيث الحجم (1000x) عن طريق إجراء هذا التغيير:

(0..(total).abs).step(batch) do |i|
      Post.order(id: :desc).where('id BETWEEN ? AND ?', i, (i + batch - 1)).each do |post|

i هو مؤشر الدفعة الحالي و BETWEEN شامل، لذلك أستبعد آخر مشاركة في كل دفعة حتى لا تتم معالجة أي شيء مرتين. يجب أن يحصل هذا على جميع المشاركات ولكن من الممكن أن يكون لدي خطأ منطقي بسيط في مكان ما…

هذا هو الفرق في وقت المعالجة بين الإصدار الأصلي والجديد للاستعلام النهائي:

الأصلي:

                                                                         QUERY PLAN                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=12541237.13..12544635.84 rows=1000 width=783) (actual time=289375.555..289456.445 rows=1000 loops=1)
   ->  Index Scan Backward using posts_pkey on posts  (cost=0.56..100746846.76 rows=29642680 width=783) (actual time=0.038..288798.236 rows=3691000 loops=1)
         Filter: (deleted_at IS NULL)
 Planning Time: 0.175 ms
 JIT:
   Functions: 6
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 80.019 ms, Inlining 47.933 ms, Optimization 261.731 ms, Emission 106.263 ms, Total 495.947 ms
 Execution Time: 289538.294 ms
(9 rows)

لاحظ عدد الصفوف في Index Scan في قسم الوقت الفعلي: 3691000
هذا استعلام يقارب 5 دقائق.

هذا هو الإصدار الجديد باستخدام BETWEEN:

                                                               QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
 Index Scan Backward using posts_pkey on posts  (cost=0.56..4137.79 rows=1100 width=783) (actual time=1.956..273.467 rows=1001 loops=1)
   Index Cond: ((id >= 3690000) AND (id <= 3691000))
   Filter: (deleted_at IS NULL)
 Planning Time: 26.421 ms
 Execution Time: 274.035 ms
(5 rows)

يتم فحص وإرجاع 1000 صف فقط من الصفوف ذات الأهمية. يعمل في 274 مللي ثانية بدلاً من 289000 مللي ثانية.

هذا تحسن لطيف، ولكنه سيكون أفضل إذا كان يمكن أن تكون العملية متعددة الخيوط. هذا على الأرجح أعلى قليلاً من خبرتي ولكن قد أتمكن من ابتكار طريقة لتشغيل عمليات إعادة خبز متعددة في وقت واحد باستخدام باقي المعرف (MOD) لتقسيم جدول المشاركات عبر العمليات المختلفة.

التعليقات مرحب بها. هل يمكن تحسين هذا بشكل أكبر؟

إعجابَين (2)

أعطني إياه GIF

يمكنك فقط إصدار post.rebake! لكل مشاركة تحتاج إلى إعادة خبز.
لا حاجة للعبث بـ baked_version.

إذًا، هل يمكن إزالة هذا الجزء من التعليمات البرمجية من مهمة rake؟ لماذا سيكون موجودًا إذا لم يكن ضروريًا؟

على حد علمي، فإن baked_version موجودة لمعالجة إعادة الخبز في حالة تغير منطق إعادة الخبز في Discourse (على عكس عندما تم تغيير المحتوى الخام للمنشور).

إعجاب واحد (1)