Pré-carregando dados e lidando com problemas de consulta N+1

Ao trabalhar com o aplicativo Discourse Rails, seja criando um plugin ou fazendo um pull request para discourse/discourse, há uma série de contextos em que você encontrará problemas de consulta N+1. Este tópico explica como lidar com o pré-carregamento de dados para resolver esses problemas e manter o Discourse com bom desempenho.

Problemas de Consulta N+1 no Rails

Primeiro, se você ainda não está familiarizado com os problemas de consulta N+1 e como o Rails os aborda, confira esta seção dos guias.

Tópicos

As questões N+1 a serem observadas ao carregar um tópico são ao carregar tabelas associadas a uma postagem. Agora, os dados serializados para um tópico individual são preparados e pré-carregados na classe TopicView. Essa classe tem um hook on_preload que permite pré-carregar tabelas associadas antes que ela execute sua consulta principal. Há um bom exemplo de uso de TopicView.on_preload no Plugin ActivityPub

Este uso do hook está pré-carregando campos personalizados, que abordaremos abaixo, e carregando associações adicionais para as postagens no tópico. Você notará que ele está usando ActiveRecord::Associations::Preloader para fazer isso. Você pode ler sobre essa classe na documentação:

Listas de Tópicos

As questões N+1 a serem observadas ao carregar uma lista de tópicos são ao carregar tabelas associadas a um tópico. Semelhante à classe TopicView para tópicos individuais, existe uma classe TopicList para listas de tópicos. E assim como TopicView, TopicList também tem um método on_preload que você pode usar para se conectar à consulta principal da lista de tópicos para carregar tabelas associadas antes que a consulta atinja o banco de dados. Há um bom exemplo disso no Plugin Discourse Assign:

register_topic_preloader_associations

A classe TopicList tem um método da API do Plugin do Servidor register_topic_preloader_associations, que essencialmente aplica o mesmo padrão que vimos no Plugin ActivityPub, usando ``ActiveRecord::Associations::Preloader`. Ele fará esse trabalho para você se você passar um array de associações.

register_topic_list_preload_user_ids

Além de executar uma grande consulta para obter todos os tópicos necessários, a classe TopicList também executa uma grande consulta para obter todos os usuários necessários para a lista, incluindo cada:

  • usuário do tópico
  • último usuário da postagem
  • usuário em destaque do tópico
  • usuários permitidos do tópico

Se você estiver carregando outros usuários para cada tópico, este método de API permite que você inclua seus IDs de usuário na grande consulta de usuários para que seus dados sejam pré-carregados junto com o restante.

Categorias

Há uma lista principal de categorias usada em vários lugares do site que é carregada e armazenada em cache no modelo Site. Embora tecnicamente não seja pré-carregamento no sentido estrito, está fazendo algo semelhante, então pensei em incluí-lo aqui.

site_all_categories_cache_query modifier

O método all_categories_cache tem um modificador da API do Plugin do Servidor que permite modificar a grande consulta de categorias: site_all_categories_cache_query. Você usa esse modificador assim em seu plugin.rb:

register_modifier(:site_all_categories_cache_query) do |query|
   query.where("categories.name LIKE 'Cool%'")
end

Pesquisa

O pré-carregamento de pesquisa também não é tecnicamente descrito como tal no código, mas também está fazendo algo semelhante com suas consultas, ou seja, carregamento antecipado.

register_search_topic_eager_load

O método da API do Plugin do Servidor register_search_topic_eager_load permite carregar antecipadamente tabelas adicionais na Pesquisa. Por exemplo:

register_search_topic_eager_load do |opts|
    %i(example_table)
end

Campos Personalizados

Campos Personalizados são alimentados pela preocupação HasCustomFields. Essa preocupação é incluída nos modelos que têm campos personalizados associados, ou seja, Post, Tópico, Categoria e Grupo. O módulo HasCustomFields tem seu próprio sistema de pré-carregamento que pode ser usado de várias maneiras. A melhor maneira de usar o pré-carregamento HasCustomFields é através dos métodos da API do Plugin do Servidor, que devem ser relativamente autoexplicativos, dado tudo o que disse até agora.

register_preloaded_category_custom_fields

add_preloaded_group_custom_field

add_preloaded_topic_list_custom_field

Você também pode usar alguns dos métodos de nível inferior em HasCustomFields para fazer seu próprio pré-carregamento quando necessário, como vimos no exemplo do Plugin ActivityPub. Nesse exemplo, a preocupação HasCustomFields preload_custom_fields é usada para pré-carregar campos personalizados de postagens em TopicView.on_preload.

TopicView.on_preload do |topic_view|
  ##
  Post.preload_custom_fields(topic_view.posts, activity_pub_post_custom_field_names)
  ##
end
4 curtidas