تحميل بطيء للصفحات في ملفات المستخدمين

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

إليك بعض المعلومات الإضافية حول إعدادنا:

مواصفات الخادم: 8 أنوية، 32 جيجابايت من ذاكرة الوصول العشوائي، تخزين NVMe SSD مع ZFS

يحتوي المنتدى على حوالي 25 مليون منشور. معظم هذه المنشورات هي محادثات خاصة، بينما يبلغ عدد المنشورات العامة حوالي 10.5 مليون.
لدينا عدد من المواضيع الضخمة (نحن موقع مخصص للكتابة، لذا تميل القصص الفردية إلى التطور ببطء مع مرور الوقت، مما يؤدي إلى تراكم العديد من المنشورات)، وتصل إلى آلاف المنشورات، مما قد يساهم في المشكلة، لكنني لا أعرف كيفية تأكيد ذلك.

إليك أحد الاستعلامات التي تبدو أنها تسبب المشكلة - لقد رأيت وقت تنفيذها يتجاوز 5000 مللي ثانية في محلل الحمل المدمج:

SELECT "topic_links"."id" AS t0_r0, "topic_links"."topic_id" AS t0_r1, "topic_links"."post_id" AS t0_r2, "topic_links"."user_id" AS t0_r3, "topic_links"."url" AS t0_r4, "topic_links"."domain" AS t0_r5, "topic_links"."internal" AS t0_r6, "topic_links"."link_topic_id" AS t0_r7, "topic_links"."created_at" AS t0_r8, "topic_links"."updated_at" AS t0_r9, "topic_links"."reflection" AS t0_r10, "topic_links"."clicks" AS t0_r11, "topic_links"."link_post_id" AS t0_r12, "topic_links"."title" AS t0_r13, "topic_links"."crawled_at" AS t0_r14, "topic_links"."quote" AS t0_r15, "topic_links"."extension" AS t0_r16, "topics"."id" AS t1_r0, "topics"."title" AS t1_r1, "topics"."last_posted_at" AS t1_r2, "topics"."created_at" AS t1_r3, "topics"."updated_at" AS t1_r4, "topics"."views" AS t1_r5, "topics"."posts_count" AS t1_r6, "topics"."user_id" AS t1_r7, "topics"."last_post_user_id" AS t1_r8, "topics"."reply_count" AS t1_r9, "topics"."featured_user1_id" AS t1_r10, "topics"."featured_user2_id" AS t1_r11, "topics"."featured_user3_id" AS t1_r12, "topics"."deleted_at" AS t1_r13, "topics"."highest_post_number" AS t1_r14, "topics"."like_count" AS t1_r15, "topics"."incoming_link_count" AS t1_r16, "topics"."category_id" AS t1_r17, "topics"."visible" AS t1_r18, "topics"."moderator_posts_count" AS t1_r19, "topics"."closed" AS t1_r20, "topics"."archived" AS t1_r21, "topics"."bumped_at" AS t1_r22, "topics"."has_summary" AS t1_r23, "topics"."archetype" AS t1_r24, "topics"."featured_user4_id" AS t1_r25, "topics"."notify_moderators_count" AS t1_r26, "topics"."spam_count" AS t1_r27, "topics"."pinned_at" AS t1_r28, "topics"."score" AS t1_r29, "topics"."percent_rank" AS t1_r30, "topics"."subtype" AS t1_r31, "topics"."slug" AS t1_r32, "topics"."deleted_by_id" AS t1_r33, "topics"."participant_count" AS t1_r34, "topics"."word_count" AS t1_r35, "topics"."excerpt" AS t1_r36, "topics"."pinned_globally" AS t1_r37, "topics"."pinned_until" AS t1_r38, "topics"."fancy_title" AS t1_r39, "topics"."highest_staff_post_number" AS t1_r40, "topics"."featured_link" AS t1_r41, "topics"."reviewable_score" AS t1_r42, "topics"."image_upload_id" AS t1_r43, "posts"."id" AS t2_r0, "posts"."user_id" AS t2_r1, "posts"."topic_id" AS t2_r2, "posts"."post_number" AS t2_r3, "posts"."raw" AS t2_r4, "posts"."cooked" AS t2_r5, "posts"."created_at" AS t2_r6, "posts"."updated_at" AS t2_r7, "posts"."reply_to_post_number" AS t2_r8, "posts"."reply_count" AS t2_r9, "posts"."quote_count" AS t2_r10, "posts"."deleted_at" AS t2_r11, "posts"."off_topic_count" AS t2_r12, "posts"."like_count" AS t2_r13, "posts"."incoming_link_count" AS t2_r14, "posts"."bookmark_count" AS t2_r15, "posts"."score" AS t2_r16, "posts"."reads" AS t2_r17, "posts"."post_type" AS t2_r18, "posts"."sort_order" AS t2_r19, "posts"."last_editor_id" AS t2_r20, "posts"."hidden" AS t2_r21, "posts"."hidden_reason_id" AS t2_r22, "posts"."notify_moderators_count" AS t2_r23, "posts"."spam_count" AS t2_r24, "posts"."illegal_count" AS t2_r25, "posts"."inappropriate_count" AS t2_r26, "posts"."last_version_at" AS t2_r27, "posts"."user_deleted" AS t2_r28, "posts"."reply_to_user_id" AS t2_r29, "posts"."percent_rank" AS t2_r30, "posts"."notify_user_count" AS t2_r31, "posts"."like_score" AS t2_r32, "posts"."deleted_by_id" AS t2_r33, "posts"."edit_reason" AS t2_r34, "posts"."word_count" AS t2_r35, "posts"."version" AS t2_r36, "posts"."cook_method" AS t2_r37, "posts"."wiki" AS t2_r38, "posts"."baked_at" AS t2_r39, "posts"."baked_version" AS t2_r40, "posts"."hidden_at" AS t2_r41, "posts"."self_edits" AS t2_r42, "posts"."reply_quoted" AS t2_r43, "posts"."via_email" AS t2_r44, "posts"."raw_email" AS t2_r45, "posts"."public_version" AS t2_r46, "posts"."action_code" AS t2_r47, "posts"."locked_by_id" AS t2_r48, "posts"."image_upload_id" AS t2_r49 FROM "topic_links" INNER JOIN "topics" ON "topics"."id" = "topic_links"."topic_id" AND ("topics"."deleted_at" IS NULL) INNER JOIN "posts" ON "posts"."id" = "topic_links"."post_id" AND ("posts"."deleted_at" IS NULL) WHERE (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (3,4))) AND "topic_links"."user_id" = 7237 AND "topic_links"."internal" = FALSE AND "topic_links"."reflection" = FALSE AND "topic_links"."quote" = FALSE ORDER BY clicks DESC, topic_links.created_at DESC LIMIT 6

عند مراقبة الخادم أثناء تحميل صفحة مستخدم، يبدو أن استخدام وحدة المعالجة المركزية يرتفع بشكل حاد في عمليات postgresql أثناء تنفيذ الاستعلام. لم أقم بإجراء أي تغييرات في الإعدادات على التثبيت باستثناء إضافة دعم MySQL لعملية الاستيراد. أي نصيحة حول الخطوات التالية ستكون موضع تقدير!

7 إعجابات

أضع هذا هنا للرجوع إليه. لقد أضفت 4 فهارس إلى قاعدة البيانات، وقد حسّن ذلك الأداء بشكل كبير، رغم أنني أعتقد أنه يمكن أن يكون أفضل. إليك ما أضفته:

CREATE INDEX index_topic_links_on_clicks_and_created ON public.topic_links USING btree (clicks, created_at);
CREATE INDEX index_posts_on_like_count_and_created ON public.posts USING btree (like_count, created_at);
CREATE INDEX index_topic_links_on_clicks_and_created_desc ON public.topic_links USING btree (clicks DESC, created_at DESC);
CREATE INDEX index_posts_on_like_count_and_created_desc ON public.posts USING btree (like_count DESC, created_at DESC, user_id) WHERE deleted_at IS NULL AND post_number > 1 AND (post_type = ANY ('{1,2,3,4}'::integer[]));
6 إعجابات

هناك احتمال أن نغفل بعض الفهارس هنا @tgxworld؟

إعجابَين (2)

مرحبًا @Ghan،

شكرًا جزيلاً لك على هذه الفهارس. لقد واجهنا نفس المشكلة منذ فترة ولم نتمكن من حلها.
بعد تطبيق هذه الفهارس، تحسنت سرعة تحميل ملفات المستخدمين بشكل كبير. كانت هناك بعض ملفات المستخدمين في “أسوأ الحالات” والتي كانت تستغرق حتى 30 ثانية للتحميل. الآن تظهر في حوالي 5 ثوانٍ.

لذا، شكرًا مرة أخرى، وأتمنى أن يكون هناك مجال أكبر لتسريع هذه العملية.

مع خالص التحية،
ساسشا

ملاحظة: حتى صفحة /about تستغرق حتى حوالي 20 ثانية للتحميل. ربما يمكن تسريعها أيضًا.

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

السؤال مرة أخرى. هل نفتقر إلى هذه الفهارس @tgxworld @sam؟

إعجابَين (2)

دعني أتحقق من الخطط لتلك الاستعلامات في Meta.

الاستعلامات سريعة جدًا على Meta، لكنني أستطيع أن أرى كيف يمكن أن تؤدي بشكل سيء على قواعد البيانات ذات الطاقة المحدودة. نحن نستخدم ActiveRecord والكود نظيف إلى حد كبير، لكن SQL المولد غير مرتب إلى حد كبير.

عند كتابة SQL يدويًا، يمكننا جعل الاستعلام يستخدم الفهارس الموجودة وتحسينه بشكل كبير:

قبل

بعد

في الاستعلام المحسّن، نسترجع منشورات المستخدم فقط، مما يحد من حلقة المنشورات قبل أن يحدث الدمج في وقت متأخر جدًا.

لذلك أعتقد أننا يمكننا تحقيق أداء أفضل دون إضافة عبء الفهارس.

7 إعجابات

هل يمكن تنفيذ النسخة المُعاد صياغتها ضمن كود ActiveRecord، أم أنك ستحتاج إلى تمرير نص استعلام يدوي هنا؟

أتمنى ذلك لأن كود Ruby الحالي جميل. ولكن إذا لم يكن الأمر كذلك، فلدينا نمط بسيط جدًا لتنفيذ SQL في التطبيق وهو مستخدم على نطاق واسع بالفعل.

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

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

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

3 إعجابات

أضفت التزامن مسودة لاختبار مخرجات السيرياليزر هنا حتى لا نعمل في الظلام:

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

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

أعتقد أننا يمكننا على الأقل إضافة فترة تخزين مؤقت أطول لهذه النقطة الطرفية؟ في رأيي، أن تكون فترة ساعة واحدة كافية، لأن المعلومات في الملخص لا تتغير في كثير من الأحيان.

4 إعجابات

يبدو ذلك جيدًا.. لنفعل ذلك!

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

لقد دفعت ذاكرة التخزين المؤقت إلى فرع طلب السحب الخاص بي:

أثناء التصحيح الإضافي، لاحظت أن لدينا أيضًا مشكلة N+1 هناك. تُحفّظ الصور المصغرة والحالة «مُحلَّلة» مشكلة N+1 لقوائم المواضيع. هل لديكم أي أفكار بشأن ذلك @david @tgxworld؟

إعجابَين (2)

تم دمج عمل التخزين المؤقت.

لا يزال العمل جارٍ على مشكلة N+1.

7 إعجابات

أي مُسلسل (serializer) تستخدمه صفحة ملخص المستخدم لقوائم المواضيع؟ أما بالنسبة للبحث في الصفحة الكاملة، فقد قمت مؤخرًا بإصلاح مشكلة N+1 بإسقاط سمات thumbnails من مُسلسل البحث لأنها غير مستخدمة على الإطلاق.

3 إعجابات

تم الإصلاح في PERF: Preload topic thumbnails for all topic lists by davidtaylorhq · Pull Request #11238 · discourse/discourse · GitHub و PERF: Simplify topic serialization for user summary page by davidtaylorhq · Pull Request #11236 · discourse/discourse · GitHub

4 إعجابات