أداء Staff SiteSerializer category_types استعلام قاعدة بيانات واحد لكل SiteSetting في مخططات الإعدادات

لاحظت نمط استعلامات N-per-setting أثناء التحقيق في أداء تحميل الصفحة الأولي للمستخدمين ذوي الصلاحيات الإدارية (Staff).

في موقعي، يُحمّل حساب غير إداري عادي مسار /latest بشكل ملحوظ أسرع من حسابي الإداري/ذو الصلاحيات. أثناء تحليل طلبات المستخدمين ذوي الصلاحيات باستخدام Rack Mini Profiler، وجدت أن SiteSerializer#category_types يبدو أنه يبني بيانات وصفية (metadata) لإعدادات أنواع الفئات عن طريق التحقق من كل SiteSetting على حدة لتحديد ما إذا كانت قد تم تجاوزها (overridden).

يبدو أن المسار المعني هو:

SiteSerializer#category_types
Categories::TypeRegistry.list
metadata لنوع الفئة
resolved_configuration_schema
site_setting_overridden?
SiteSetting.provider.find

الكود الحالي:

على وجه الخصوص، تستدعي site_setting_overridden?:

SiteSetting.provider.find(setting_name.to_sym).present?

وتستدعي resolved_configuration_schema ذلك أثناء التكرار على SiteSettings الموجودة في مخطط الإعدادات (configuration schema).

ثم تقوم SiteSettings::DbProvider#find بتنفيذ استعلام فردي للإعداد المسمّى:

في مخرجات أداة التحليل (profiler)، رأيت استعلامات فردية مثل:

SELECT name, data_type, value
FROM site_settings
WHERE name = 'discourse_post_event_allowed_on_groups';

SELECT name, data_type, value
FROM site_settings
WHERE name = 'use_local_event_date';

SELECT name, data_type, value
FROM site_settings
WHERE name = 'show_filter_by_solved_status';

SELECT name, data_type, value
FROM site_settings
WHERE name = 'topic_voting_show_who_voted';

كان هناك 16 استعلامًا يطابق نمط site_settings WHERE name = ... في الطلب الملتقط. كانت هذه الاستعلامات لأسماء إعدادات مختلفة، لذا أنا لا أقترح أن نفس البحث يتم ببساطة تكراره؛ بل يبدو أن هناك بحثًا في قاعدة البيانات واحدًا لكل SiteSetting ممثلة في مخططات إعدادات أنواع الفئات.

يبدو أن هذا ذات صلة خاصة بالمستخدمين ذوي الصلاحيات لأن category_types يُدرج فقط لهم:

أظهر تحليلي أيضًا أن إعدادات الفئات المقدمة من الإضافات تشارك في هذا المسار، بما في ذلك إعدادات Discourse Solved، والإعدادات المتعلقة بالتقويم/الأحداث، وإعدادات تصويت المواضيع (Topic Voting).

للمقارنة، على نفس تثبيت Discourse والاتصال، كان تحميل /latest الدافئ (warm) تقريبًا:

حساب إداري/ذو صلاحيات:
DOMContentLoaded: حوالي 0.77–1.03 ثانية
HTML الأولي: حوالي 2.08 ميغابايت مفككة

حساب عادي:
DOMContentLoaded: حوالي 0.51 ثانية
إجمالي الموارد المفككة: حوالي 1.50 ميغابايت

كما انتهى الأمر بتحميل PWA للحساب العادي بنفس السرعة على الأقل مقارنة بـ AMD Developer Community PWA في الاختبارات اللاحقة، لذا لا يبدو أن هذه مشكلة عامة في أداء الأصل (origin) أو CDN.

كما كان لدي طلب غير معتاد في وضع الأمان (Safe Mode) للمستخدمين ذوي الصلاحيات أنتج فيه layouts/application 1,362 استعلام SQL واستغرق حوالي 2.9 ثانية. أنا لا أعتقد أن استعلامات SiteSetting الفردية المذكورة أعلاه تفسر جميع تلك الاستعلامات، لذا أنا أتعامل مع ذلك بشكل منفصل بدلاً من الادعاء بأن هذا المسؤول عن الشذوذ بأكمله.

هل من المنطقي أن يتم جلب/تجميع حالة الإعدادات المتجاوزة المستخدمة في resolved_configuration_schema مرة واحدة، بدلاً من استدعاء SiteSetting.provider.find بشكل منفصل لكل SiteSetting؟

على سبيل المثال، تمتلك DbProvider بالفعل طريقة all، على الرغم من أنني لا أقترح أن استخدام all مباشرة هو بالضرورة التنفيذ الصحيح - فأنا أسأل بشكل رئيسي عما إذا كان تجنّب عمليات البحث في قاعدة البيانات لكل إعداد هنا يستحق الجهد.

تحققت من نفس المسار في فرع main الحالي ويبدو أن سلوك provider.find لكل إعداد لا يزال موجودًا.

لقد استطعت الآن إعادة إنتاج هذه المشكلة على فرع main الحالي، وفتحت طلب سحب (PR) يقوم بدمج عمليات البحث عن تجاوزات SiteSetting هذه في دفعة واحدة:

أثناء العمل على هذا الأمر، اكتشفت أن هناك فعلياً مستويين لنمط الاستعلام.

كانت النسخة الأصلية تستدعي:

SiteSetting.provider.find(...)

مرة واحدة لكل إعداد موقع (SiteSetting) ممثل في مخطط تكوين نوع الفئة.

كان بإمكان نهج الدمج الأولي تقليل ذلك إلى استدعاء واحد لـ provider.all لكل نوع فئة، لكن Categories::TypeRegistry.list يستدعي metadata بشكل منفصل لكل نوع مسجل، مما كان سيؤدي لا يزال إلى عدة استعلامات جماعية عندما توفر عدة أنواع فئات إعدادات موقع.

بدلاً من ذلك، يقوم طلب السحب (PR) بتحميل تجاوزات SiteSetting المخزنة مرة واحدة في TypeRegistry.list ويمرر هذه المعلومات إلى جميع أنواع الفئات التي يتم تسلسلها. يحتفظ الحل المباشر لبيانات الوصف/المخطط باستعلام جماعي واحد كمسار بديل.

لذا، بالنسبة لمسار حمولة موقع الموظفين التي أبلغت عنها في الأصل، يتغير الشكل المقصود من تقريباً:

استعلام SELECT واحد ... WHERE name = ? لكل إعداد موقع مُهيأ

إلى:

استعلام SELECT جماعي واحد من site_settings لـ TypeRegistry.list

يتم الحفاظ على السلوك الحالي المتعلق بالتجاوز/عدم التجاوز.

أضفت تغطية انحدار (Regression Coverage) لكلتا الحالتين:

  • عدة إعدادات موقع ضمن نوع فئة واحد؛
  • عدة أنواع فئات تشارك نفس البحث الجماعي عن التجاوزات.

محلياً، نجحت مواصفات الفئات والمسلسلات ذات الصلة:

149 مثالاً، 0 إخفاقات

كما أكمل طلب السحب (PR) الآن فحص CI الخاص بالمصدر بنجاح: 14 فحصاً ناجحاً، 0 إخفاقات، 0 معلقة (مع تخطي فحصين).

لذا، يبدو أن هذا يعالج سلوك N لكل إعداد محدد الذي تم التقاطه من ملف المراقبة (Profiler) في المنشور الافتتاحي، بدلاً من الشذوذ الأكبر بكثير في استعلامات SQL الذي أشرت إليه في نهاية المنشور الافتتاحي.