O SiteSerializer da categoria_types executa uma consulta ao banco de dados para cada SiteSetting nos esquemas de configuração

Notei o que parece ser um padrão de consultas N-per-setting durante a investigação do desempenho do carregamento inicial da página para usuários do staff.

No meu site, uma conta comum, não do staff, carrega /latest visivelmente mais rápido do que minha conta de administrador/staff. Ao fazer o perfil da requisição do staff com o Rack Mini Profiler, descobri que SiteSerializer#category_types parece construir os metadados de configuração dos tipos de categoria verificando individualmente cada SiteSetting para determinar se ele foi sobrescrito.

O caminho relevante parece ser:

SiteSerializer#category_types
→ Categories::TypeRegistry.list
→ metadados do tipo de categoria
→ resolved_configuration_schema
→ site_setting_overridden?
→ SiteSetting.provider.find

Código atual:

Em particular, site_setting_overridden? chama:

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

e resolved_configuration_schema chama isso enquanto itera pelos SiteSettings no esquema de configuração.

SiteSettings::DbProvider#find então executa uma consulta individual para a configuração nomeada:

Na saída do perfil, vi consultas individuais 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';

Houve 16 consultas correspondendo a esse padrão site_settings WHERE name = ... na requisição capturada. Elas foram para diferentes nomes de configuração, então não estou sugerindo que a mesma busca esteja simplesmente sendo repetida; em vez disso, parece ser uma busca no banco de dados por SiteSetting representado nos esquemas de configuração dos tipos de categoria.

Isso parece particularmente relevante para usuários do staff, pois category_types é incluído apenas para o staff:

Meu perfil também mostrou que a configuração de categoria fornecida por plugins participa deste caminho, incluindo configurações do Discourse Solved, configurações relacionadas a calendário/eventos e configurações do Topic Voting.

Para comparação, na mesma instalação e conexão do Discourse, um carregamento quente de /latest foi aproximadamente:

Conta de admin/staff:
DOMContentLoaded: aproximadamente 0,77–1,03 s
HTML inicial: aproximadamente 2,08 MB decodificados

Conta comum:
DOMContentLoaded: aproximadamente 0,51 s
Total de recursos decodificados: aproximadamente 1,50 MB

A PWA da conta comum também terminou carregando pelo menos tão rápido quanto a PWA da AMD Developer Community em testes subsequentes, então isso não parece ser um problema geral de desempenho de origem/CDN.

Também tive uma requisição incomum do staff em Modo Seguro em que layouts/application gerou 1.362 consultas SQL e levou cerca de 2,9 segundos. Eu não acho que as consultas por SiteSetting acima explicam todas essas consultas, então estou tratando isso separadamente, em vez de afirmar que isso é responsável por todo o valor atípico.

Faria sentido que o estado de configuração sobrescrita usado por resolved_configuration_schema fosse buscado/agrupado uma vez, em vez de chamar SiteSetting.provider.find separadamente para cada SiteSetting?

Por exemplo, DbProvider já tem um método all, embora eu não esteja sugerindo que usar all diretamente seja necessariamente a implementação correta — estou principalmente me perguntando se evitar as buscas no banco de dados N-per-setting aqui valeria a pena.

Verifiquei o mesmo caminho no main atual e parece que o comportamento de provider.find por configuração ainda está presente.

Agora reproduzi isso no main atual e abri um PR que agrupa essas consultas de sobrescrita de SiteSetting:

Enquanto trabalhava nisso, percebi que o padrão de consulta tinha, na prática, dois níveis.

A implementação original chamava:

SiteSetting.provider.find(...)

uma vez para cada SiteSetting representado no esquema de configuração de um tipo de categoria.

Uma primeira abordagem de agrupamento poderia reduzir isso para uma única chamada provider.all por tipo de categoria, mas Categories::TypeRegistry.list chama metadata separadamente para cada tipo registrado, o que ainda resultaria em múltiplas consultas em lote quando vários tipos de categoria fornecem SiteSettings.

O PR, em vez disso, carrega as sobrescritas persistidas de SiteSetting uma única vez em TypeRegistry.list e passa essas informações para todos os tipos de categoria que estão sendo serializados. A resolução direta de metadados/esquema mantém um fallback de fornecedor em lote único.

Portanto, para o caminho de payload do site de staff que eu relatei originalmente, a forma pretendida muda de aproximadamente:

um SELECT ... WHERE name = ? por SiteSetting configurado

para:

um SELECT em lote de site_settings para TypeRegistry.list

O comportamento existente de sobrescrito/não sobrescrito é preservado.

Adicionei cobertura de regressão para ambos os casos:

  • múltiplos SiteSettings dentro de um único tipo de categoria;

  • múltiplos tipos de categoria compartilhando a mesma consulta de sobrescrita em lote.

Localmente, as especificações relevantes de categoria e serializador passaram:

149 exemplos, 0 falhas

O PR também agora completou com sucesso o CI upstream: 14 verificações bem-sucedidas, 0 falhas, 0 pendentes (com 2 verificações ignoradas).

Portanto, isso parece abordar o comportamento específico de N por configuração da captura do profiler na publicação inicial, em vez do outlier separado e muito maior de consulta SQL que mencionei perto do final da publicação inicial.