Staff SiteSerializer category_types führt eine DB-Abfrage pro SiteSetting in Konfigurationsschemata aus

Ich habe ein Muster festgestellt, das wie eine N-Queries-pro-Einstellung-Anfrage aussieht, während ich die Ladezeit der Startseite für Benutzer mit Personal-Berechtigungen untersuchte.

Auf meiner Website lädt ein normales Konto ohne Personal-Berechtigungen /latest deutlich schneller als mein Admin-/Personal-Konto. Beim Profiling der Personal-Anfrage mit Rack Mini Profiler stellte ich fest, dass SiteSerializer#category_types scheinbar die Konfigurations-Metadaten für Kategorietypen erstellt, indem jede SiteSetting einzeln daraufhin überprüft wird, ob sie überschrieben wurde.

Der relevante Pfad scheint wie folgt zu sein:

SiteSerializer#category_types
→ Categories::TypeRegistry.list
→ Kategorie-Typ metadata
→ resolved_configuration_schema
→ site_setting_overridden?
→ SiteSetting.provider.find

Aktueller Code:

Insbesondere ruft site_setting_overridden? Folgendes auf:

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

und resolved_configuration_schema ruft dies auf, während es die SiteSettings im Konfigurationsschema iteriert.

SiteSettings::DbProvider#find führt dann eine individuelle Abfrage für die benannte Einstellung aus:

In der Profiler-Ausgabe sah ich individuelle Abfragen wie:

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';

In der erfassten Anfrage gab es 16 Abfragen, die diesem Muster site_settings WHERE name = ... entsprachen. Diese betrafen verschiedene Einstellungsnamen, daher schlage ich nicht vor, dass derselbe Lookup einfach nur wiederholt wird; vielmehr scheint es sich um einen Datenbank-Lookup pro SiteSetting zu handeln, die in den Konfigurationsschemas der Kategorietypen vertreten ist.

Dies scheint besonders für Benutzer mit Personal-Berechtigungen relevant zu sein, da category_types nur für Personal enthalten ist:

Mein Profiling zeigte auch, dass von Plugins bereitgestellte Kategoriekonfigurationen an diesem Pfad beteiligt waren, einschließlich Einstellungen zu Discourse Solved, kalender-/ereignisbezogenen Einstellungen und Topic Voting-Einstellungen.

Zum Vergleich war das Laden eines warmen /latest auf derselben Discourse-Installation und derselben Verbindung ungefähr:

Admin/Personal-Konto:
DOMContentLoaded: ca. 0,77–1,03 s
Initiales HTML: ca. 2,08 MB dekodiert

Normales Konto:
DOMContentLoaded: ca. 0,51 s
Gesamt dekodierte Ressourcen: ca. 1,50 MB

Die PWA des normalen Kontos lud in anschließenden Tests mindestens genauso schnell wie die PWA der AMD Developer Community, daher scheint dies kein allgemeines Problem mit der Leistung des Origins/CDN zu sein.

Ich hatte auch eine ungewöhnliche Personal-Safe-Mode-Anfrage, bei der layouts/application 1.362 SQL-Abfragen erzeugte und etwa 2,9 Sekunden dauerte. Ich glaube nicht, dass die oben genannten Abfragen pro SiteSetting für alle diese Abfragen verantwortlich sind, daher behandle ich dies getrennt und behaupte nicht, dass dies für den gesamten Ausreißer verantwortlich ist.

Würte es Sinn ergeben, den Status der überschriebenen Einstellungen, der von resolved_configuration_schema verwendet wird, einmalig abzurufen/zu bündeln, anstatt SiteSetting.provider.find für jede SiteSetting separat aufzurufen?

Zum Beispiel hat DbProvider bereits eine all-Methode, obwohl ich nicht vorschlage, dass die direkte Verwendung von all unbedingt die richtige Implementierung ist – ich frage mich hauptsächlich, ob es sich lohnen würde, die Datenbank-Lookups pro Einstellung hier zu vermeiden.

Ich habe denselben Pfad im aktuellen main überprüft und es scheint, dass das Verhalten des pro-Einstellung-provider.find weiterhin vorhanden ist.

Ich habe das Problem inzwischen gegen den aktuellen main-Branch reproduziert und einen PR eröffnet, der diese SiteSetting-Override-Aufrufe bündelt:

Beim Durcharbeiten des Themas stellte ich fest, dass das Abfragemuster effektiv zwei Ebenen aufweist.

Die ursprüngliche Implementierung rief:

SiteSetting.provider.find(...)

für jede in der Konfigurationsschema einer Kategorieart vertretene SiteSetting auf.

Ein erster Ansatz zur Bündelung könnte dies auf einen provider.all-Aufruf pro Kategorieart reduzieren. Da Categories::TypeRegistry.list jedoch metadata separat für jeden registrierten Typ aufruft, würde dies bei mehreren Kategoriearten, die SiteSettings bereitstellen, immer noch zu mehreren Massenabfragen führen.

Der PR lädt die persistierten SiteSetting-Overrides stattdessen einmalig in TypeRegistry.list und gibt diese Informationen an alle serialisierten Kategoriearten weiter. Die direkte Metadaten-/Schema-Auflösung behält einen einzelnen Massenanbieter-Fallback bei.

Für den von mir ursprünglich gemeldeten Pfad der Mitarbeiter-Website-Payload ändert sich die beabsichtigte Struktur also von ungefähr:

eine SELECT ... WHERE name = ? pro konfiguriertem SiteSetting

zu:

eine Massen-SELECT aus site_settings für TypeRegistry.list

Das bestehende Verhalten für überschriebene/nicht überschriebene Werte bleibt erhalten.

Ich habe Regressionstests für beide Fälle hinzugefügt:

  • mehrere SiteSettings innerhalb einer einzelnen Kategorieart;

  • mehrere Kategoriearten, die dieselbe Massen-Override-Auflösung teilen.

Lokal bestanden die relevanten Kategorie- und Serialisierer-Specs:

149 Beispiele, 0 Fehler

Der PR hat auch die Upstream-CI erfolgreich abgeschlossen: 14 erfolgreiche Checks, 0 Fehler, 0 ausstehend (mit 2 übersprungenen Checks).

Dies scheint also das spezifische N-pro-Einstellung-Verhalten aus der Profiler-Aufnahme im Eröffnungspost anzugehen, und nicht den separaten, viel größeren SQL-Abfrage-Ausreißer, den ich am Ende des Eröffnungsposts erwähnt habe.