منتديات كبيرة تحتوي على أكثر من مليون مشاركة، كيف أداء البحث الخاص بك؟

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

بدأنا نلاحظ حدوث مهلات زمنية في البحث عبر حوالي 1.2 مليون منشور.

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

شكرًا لكم على تعليقاتكم!

ما هي مواصفات خادمك وما هي الموارد التي تبدو الأكثر تقييدًا؟

أنا أبحث في إمكانية نقل لوحة تحتوي على حوالي 20 مليون منشور (منشورات عامة + محادثات خاصة)، لذا أنا مهتم حقًا بالتعرف على مشكلات الأداء المحتملة في اللوحات الكبيرة.

المسافة بين مليون منشور و20 مليون منشور كبيرة، لذا فإن النصائح القليلة المتاحة قد لا تنطبق على كلا الحالتين. وبغض النظر عن ذلك، لدي موقع يحتوي على 9 ملايين منشور ويحقق أداءً مقبولاً على معالج i7-7500U بتردد 2.7 جيجاهرتز مع ذاكرة وصول عشوائي (RAM) سعتها 16 جيجابايت. يوجد أيضًا عدد قليل من المواقع الأخرى منخفضة الحجم على نفس الخادم.

أما الموقع الذي يستخدم قاعدة بيانات على AWS بسعة ذاكرة 8 جيجابايت و4 ملايين منشور، فيواجه بعض الصعوبات، خاصة في عمليات البحث.

@Jumanji، ما هي مواصفات خادمك؟

نحن نستخدم خدمات AWS. تعمل منتدياتنا على 4 экземпляرات، وأعتقد أنها من نوع t3.large، أي 4 وحدات معالجة مركزية افتراضية (vCPUs) و 16 جيجابايت من ذاكرة الوصول العشوائي (RAM). ندير Kubernetes على صورة Docker مخصصة. قاعدة بياناتنا مستقلة على экземпляر من نوع m4.large، أي 2 وحدة معالجة مركزية افتراضية و 8 جيجابايت من ذاكرة الوصول العشوائي. بالتأكيد، قد تكون ذاكرة الوصول العشوائي على خوادم قاعدة البيانات هي عنق الزجاجة.

الاستعلامات التي تواجه وقت انتهاء مهلة (timeout) هي عادةً مصطلحات عامة وكلمات مفردة. لقد شهدنا بعض الاستعلامات التي استمرت لأكثر من 60 ثانية. الآن، شهدنا أيضًا تدفقًا كبيرًا من الزيارات إلى موقعنا الإلكتروني خلال الشهر الماضي. في الشهر الماضي، قفزت حركة المرور بنسبة 40% مقارنة بالشهر السابق. كما قمنا بالانتقال من الإصدار 1.9 إلى الإصدار 2.4 خلال تلك الفترة، وأعلم أنه منذ الإصدار 1.9، تمت إضافة ميزة البحث التلقائي (autocomplete search) إلى وظيفة البحث، لذا أفترض أن هذا يضيف حملاً إضافيًا على خادم قاعدة البيانات.

قمنا بإجراء تحليل للاستعلامات، وكان الاستعلام التالي يستغرق من 20 إلى 60 ثانية للتنفيذ:

`SELECT "posts".*
FROM "posts"
JOIN (
    SELECT *, row_number() over() row_number
    FROM (
        SELECT topics.id, min(posts.post_number) post_number
        FROM "posts"
        INNER JOIN "post_search_data" ON "post_search_data"."post_id" = "posts"."id"
        INNER JOIN "topics" ON "topics"."id" = "posts"."topic_id" AND ("topics"."deleted_at" IS NULL)
        LEFT JOIN categories ON categories.id = topics.category_id
        WHERE 
            ("posts"."deleted_at" IS NULL)
            AND "posts"."post_type" IN (1, 2, 3)
            AND (topics.visible)
            AND (topics.archetype <> 'private_message')
            AND (post_search_data.search_data @@ TO_TSQUERY('english', '''a'':*ABD & ''price'':*ABD'))
            AND (categories.id NOT IN (SELECT categories.id WHERE categories.search_priority = 1))
            AND ((categories.id IS NULL) OR (NOT categories.read_restricted))
        GROUP BY topics.id
        ORDER BY
            MAX((
                TS_RANK_CD(
                    post_search_data.search_data,
                    TO_TSQUERY('english', '''a'':*ABD & ''price'':*ABD'),
                    1|32
                ) * (
                    CASE categories.search_priority
                    WHEN 2
                    THEN 0.6
                    WHEN 3
                    THEN 0.8
                    WHEN 4
                    THEN 1.2
                    WHEN 5
                    THEN 1.4
                    ELSE
                    CASE
                        WHEN topics.closed
                        THEN 0.9
                        ELSE 1
                        END
                    END
                )
            )) DESC,
            topics.bumped_at DESC
        LIMIT 6
        OFFSET 0
    ) xxx
) x ON x.id = posts.topic_id AND x.post_number = posts.post_number
WHERE ("posts"."deleted_at" IS NULL)
ORDER BY row_number;

---

 Sort  (cost=495214.55..495214.56 rows=1 width=1177) (actual time=20529.725..20529.727 rows=6 loops=1)
   Sort Key: (row_number() OVER (?))
   Sort Method: quicksort  Memory: 29kB
   ->  Nested Loop  (cost=495164.08..495214.54 rows=1 width=1177) (actual time=20525.899..20529.703 rows=6 loops=1)
         ->  WindowAgg  (cost=495163.65..495163.80 rows=6 width=16) (actual time=20520.976..20521.078 rows=6 loops=1)
               ->  Limit  (cost=495163.65..495163.66 rows=6 width=24) (actual time=20520.969..20521.063 rows=6 loops=1)
                     ->  Sort  (cost=495163.65..495232.24 rows=27438 width=24) (actual time=20520.967..20520.969 rows=6 loops=1)
                           Sort Key: (max((ts_rank_cd(post_search_data.search_data, '''price'':*ABD'::tsquery, 33) * (CASE categories.search_priority WHEN 2 THEN 0.6 WHEN 3 THEN 0.8 WHEN 4 THEN 1.2 WHEN 5 THEN 1.4 ELSE CASE WHEN topics.closed THEN 0.9 ELSE '1'::numeric END END)::double precision))) DESC, topics.bumped_at DESC
                           Sort Method: top-N heapsort  Memory: 25kB
                           ->  GroupAggregate  (cost=493642.90..494671.83 rows=27438 width=24) (actual time=19082.214..20506.763 rows=32951 loops=1)
                                 Group Key: topics.id
                                 ->  Sort  (cost=493642.90..493711.50 rows=27438 width=400) (actual time=19082.184..19283.907 rows=191436 loops=1)
                                       Sort Key: topics.id
                                       Sort Method: external merge  Disk: 77632kB
                                       ->  Hash Left Join  (cost=36655.60..486646.69 rows=27438 width=400) (actual time=1562.696..18611.724 rows=191436 loops=1)
                                             Hash Cond: (topics.category_id = categories.id)
                                             Filter: (((categories.id IS NULL) OR (NOT categories.read_restricted)) AND (NOT (SubPlan 1)))
                                             ->  Gather  (cost=36645.63..486471.60 rows=58991 width=400) (actual time=1562.623..18249.349 rows=191436 loops=1)
                                                   Workers Planned: 2
                                                   Workers Launched: 2
                                                   ->  Nested Loop  (cost=35645.63..479572.50 rows=24580 width=400) (actual time=1556.547..18541.793 rows=63812 loops=3)
                                                         ->  Hash Join  (cost=35645.20..328688.61 rows=157831 width=25) (actual time=1551.912..13356.416 rows=285279 loops=3)
                                                               Hash Cond: (posts_1.topic_id = topics.id)
                                                               ->  Parallel Seq Scan on posts posts_1  (cost=0.00..286245.92 rows=504559 width=12) (actual time=0.280..11249.160 rows=404770 loops=3)
                                                                     Filter: ((deleted_at IS NULL) AND (post_type = ANY ('{1,2,3}'::integer[])))
                                                                     Rows Removed by Filter: 21884
                                                               ->  Hash  (cost=33938.80..33938.80 rows=92912 width=17) (actual time=1549.103..1549.103 rows=80351 loops=3)
                                                                     Buckets: 65536  Batches: 2  Memory Usage: 2557kB
                                                                     ->  Seq Scan on topics  (cost=0.00..33938.80 rows=92912 width=17) (actual time=0.010..1492.606 rows=80351 loops=3)
                                                                           Filter: ((deleted_at IS NULL) AND visible AND ((archetype)::text <> 'private_message'::text))
                                                                           Rows Removed by Filter: 216751
                                                         ->  Index Scan using posts_search_pkey on post_search_data  (cost=0.43..0.96 rows=1 width=383) (actual time=0.017..0.017 rows=0 loops=855836)
                                                               Index Cond: (post_id = posts_1.id)
                                                               Filter: (search_data @@ '''price'':*ABD'::tsquery)
                                                               Rows Removed by Filter: 1
                                             ->  Hash  (cost=9.43..9.43 rows=43 width=9) (actual time=0.059..0.059 rows=43 loops=1)
                                                   Buckets: 1024  Batches: 1  Memory Usage: 10kB
                                                   ->  Seq Scan on categories  (cost=0.00..9.43 rows=43 width=9) (actual time=0.007..0.045 rows=43 loops=1)
                                             SubPlan 1
                                               ->  Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.000..0.000 rows=0 loops=191436)
                                                     One-Time Filter: (categories.search_priority = 1)
         ->  Index Scan using index_posts_on_topic_id_and_post_number on posts  (cost=0.43..8.45 rows=1 width=1169) (actual time=1.435..1.435 rows=1 loops=6)
               Index Cond: ((topic_id = topics.id) AND (post_number = (min(posts_1.post_number))))
               Filter: (deleted_at IS NULL)
 Planning time: 3.508 ms
 Execution time: 20541.411 ms
(46 rows)`

هل حجم قاعدة بياناتك الإجمالي أقل من 8 جيجابايت؟ ستحتاج إلى تخزين أكبر قدر ممكن من بياناتك في ذاكرة الوصول العشوائي.

أوه، هذا سيكون بطيئًا. وسيكون أسوأ بكثير إذا كان القرص نقطة ربط شبكية.

أيضًا، راجع ردي أعلاه للحصول على بعض التحسينات.

@Jumanji هل يمكنك مشاركة بعض مواقع المنتديات ذات الزيارات العالية التي تعمل على Discourse، مع روابط من فضلك؟
زيارات تتجاوز الملايين.

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

نحن عند 24 جيجابايت. بالتأكيد أكبر من ذاكرة الوصول العشوائي المتاحة للوحدة.

في بعض الحالات، بعد التحركات الكبيرة، يمكن أن يؤدي تشغيل vacuum analyze إلى تحسين الأداء.

أيضًا، إذا كنت تستخدم AWS، فهل تستخدم RDS؟ إذا لم تكن كذلك… لماذا؟

شكرًا لك. نعم، قمنا بتشغيله ونحن الآن على RDS.