El SiteSerializer de Staff de category_types realiza una consulta a la BD por cada SiteSetting en los esquemas de configuración

Noté lo que parece ser un patrón de consultas N-por-configuración mientras investigaba el rendimiento de la carga inicial de la página para usuarios de personal.

En mi sitio, una cuenta normal no perteneciente al personal carga /latest notablemente más rápido que mi cuenta de administrador/personal. Mientras perfilaba la solicitud del personal con Rack Mini Profiler, descubrí que SiteSerializer#category_types parece construir los metadatos de configuración de tipos de categoría comprobando cada SiteSetting individualmente para determinar si ha sido sobrescrito.

La ruta relevante parece ser:

SiteSerializer#category_types
→ Categories::TypeRegistry.list
→ metadatos del tipo de categoría
→ resolved_configuration_schema
→ site_setting_overridden?
→ SiteSetting.provider.find

Código actual:

En particular, site_setting_overridden? llama a:

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

y resolved_configuration_schema llama a eso mientras itera sobre los SiteSettings en el esquema de configuración.

SiteSettings::DbProvider#find luego realiza una consulta individual para la configuración nombrada:

En la salida del perfilador vi consultas individuales como:

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

Hubo 16 consultas que coincidían con el patrón site_settings WHERE name = ... en la solicitud capturada. Estas eran para diferentes nombres de configuración, por lo que no estoy sugiriendo que la misma búsqueda se esté repitiendo simplemente; más bien, parece ser una búsqueda en la base de datos por cada SiteSetting representado en los esquemas de configuración de tipos de categoría.

Esto parece ser particularmente relevante para los usuarios de personal porque category_types solo se incluye para el personal:

Mi perfilado también mostró que la configuración de categorías proporcionada por plugins participa en esta ruta, incluyendo Discourse Solved, configuraciones relacionadas con calendario/eventos y configuraciones de Topic Voting.

Para comparar, en la misma instalación de Discourse y conexión, una carga caliente de /latest fue aproximadamente:

Cuenta de administrador/personal:
DOMContentLoaded: aproximadamente 0.77–1.03 s
HTML inicial: aproximadamente 2.08 MB decodificados

Cuenta normal:
DOMContentLoaded: aproximadamente 0.51 s
Recursos decodificados totales: aproximadamente 1.50 MB

La PWA de la cuenta normal también terminó cargando al menos tan rápido como la PWA de AMD Developer Community en pruebas posteriores, por lo que esto no parece ser un problema general de rendimiento del origen/CDN.

También tuve una solicitud inusual de Modo Seguro de personal donde layouts/application produjo 1,362 consultas SQL y tardó unos 2.9 segundos. No creo que las consultas por SiteSetting anteriores expliquen todas esas consultas, por lo que lo estoy tratando por separado en lugar de afirmar que es responsable de todo el valor atípico.

¿Tendría sentido que el estado de configuración sobrescrita utilizado por resolved_configuration_schema se obtuviera/agrupara una vez, en lugar de llamar a SiteSetting.provider.find por separado para cada SiteSetting?

Por ejemplo, DbProvider ya tiene un método all, aunque no estoy sugiriendo que usar all directamente sea necesariamente la implementación correcta; principalmente me pregunto si evitar las búsquedas en la base de datos N-por-configuración aquí valdría la pena.

Revisé la misma ruta en el main actual y parece que el comportamiento de provider.find por configuración sigue presente.

Ya he reproducido esto contra el main actual y he abierto un PR que agrupa estas consultas de sobrescritura de SiteSetting:

Mientras lo trabajaba, descubrí que el patrón de consulta tenía efectivamente dos niveles.

La implementación original llamaba a:

SiteSetting.provider.find(...)

una vez por cada SiteSetting representado en el esquema de configuración de un tipo de categoría.

Un primer enfoque de agrupación podría reducirlo a un provider.all por tipo de categoría, pero Categories::TypeRegistry.list llama a metadata por separado para cada tipo registrado, por lo que eso aún resultaría en múltiples consultas masivas cuando varios tipos de categorías proporcionan SiteSettings.

En su lugar, el PR carga las sobrescrituras de SiteSetting persistidas una sola vez en TypeRegistry.list y pasa esa información a todos los tipos de categorías que se están serializando. La resolución directa de metadatos/esquema conserva una única consulta masiva de proveedor como respaldo.

Por lo tanto, para la ruta de carga de datos del sitio de personal que reporté originalmente, la forma prevista cambia de aproximadamente:

una SELECT ... WHERE name = ? por cada SiteSetting configurado

a:

una SELECT masiva desde site_settings para TypeRegistry.list

Se preserva el comportamiento existente de sobrescrito/no sobrescrito.

Agregué cobertura de regresión para ambos casos:

  • múltiples SiteSettings dentro de un solo tipo de categoría;

  • múltiples tipos de categorías que comparten la misma consulta masiva de sobrescritura.

Localmente, las especificaciones relevantes de categorías y serializadores pasaron:

149 ejemplos, 0 fallos

El PR también ha completado con éxito la CI upstream: 14 comprobaciones exitosas, 0 fallos, 0 pendientes (con 2 comprobaciones omitidas).

Por lo tanto, esto parece abordar el comportamiento específico de N-por-configuración de la captura del perfilador en la publicación inicial, en lugar del outlier de consulta SQL mucho mayor y separado que mencioné cerca del final de la publicación inicial.