Préchargement des données et gestion des problèmes de requêtes N+1

Lorsque vous travaillez avec l’application Rails Discourse, que ce soit pour créer un plugin ou pour faire une pull request à discourse/discourse, vous rencontrerez des problèmes de requêtes N+1 dans plusieurs contextes. Ce sujet explique comment gérer le préchargement des données pour résoudre ces problèmes et maintenir les performances de Discourse.

Problèmes de requêtes N+1 dans Rails

Tout d’abord, si vous n’êtes pas déjà familier avec les problèmes de requêtes N+1 et la manière dont Rails les résout, consultez cette section des guides.

Sujets

Les problèmes N+1 à surveiller lors du chargement d’un sujet surviennent lorsque vous chargez des tables associées à un message. Désormais, les données sérialisées pour un sujet individuel sont préparées et préchargées dans la classe TopicView. Cette classe possède un hook on_preload qui vous permet de précharger les tables associées avant qu’elle n’exécute sa requête principale. Il existe un bon exemple d’utilisation de TopicView.on_preload dans le plugin ActivityPub

Cette utilisation du hook précharge à la fois les champs personnalisés, que nous aborderons ci-dessous, et charge des associations supplémentaires pour les messages du sujet. Vous remarquerez qu’il utilise ActiveRecord::Associations::Preloader pour ce faire. Vous pouvez en savoir plus sur cette classe dans la documentation :

Listes de sujets

Les problèmes N+1 à surveiller lors du chargement d’une liste de sujets surviennent lors du chargement des tables associées à un sujet. Similaire à la classe TopicView pour les sujets individuels, il existe une classe TopicList pour les listes de sujets. Et tout comme TopicView, TopicList possède également une méthode on_preload que vous pouvez utiliser pour vous connecter à la requête principale de la liste de sujets afin de charger les tables associées avant que la requête n’atteigne la base de données. Il existe un bon exemple dans le plugin Discourse Assign :

register_topic_preloader_associations

La classe TopicList possède une méthode d’API de plugin serveur register_topic_preloader_associations, qui applique essentiellement le même modèle que nous avons vu dans le plugin ActivityPub, en utilisant ``ActiveRecord::Associations::Preloader`. Elle effectuera ce travail pour vous si vous lui passez un tableau d’associations.

register_topic_list_preload_user_ids

En plus d’exécuter une seule grosse requête pour obtenir tous les sujets nécessaires, la classe TopicList exécute également une seule grosse requête pour obtenir tous les utilisateurs nécessaires à la liste, y compris chaque :

  • utilisateur du sujet
  • dernier utilisateur du message
  • utilisateur mis en avant du sujet
  • utilisateurs autorisés du sujet

Si vous chargez d’autres utilisateurs pour chaque sujet, cette méthode d’API vous permet d’inclure leurs identifiants d’utilisateur dans la grosse requête utilisateur afin que leurs données soient préchargées avec le reste.

Catégories

Il existe une liste principale de catégories utilisée dans divers endroits du site qui est chargée et mise en cache dans le modèle Site. Bien que techniquement, il ne s’agisse pas d’un préchargement au sens strict, cela fait quelque chose de similaire, j’ai donc pensé à l’inclure ici.

site_all_categories_cache_query modifier

La méthode all_categories_cache possède un modificateur d’API de plugin serveur qui vous permet de modifier la grosse requête des catégories : site_all_categories_cache_query. Vous utilisez ce modificateur comme ceci dans votre plugin.rb :

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

Recherche

Le préchargement de la recherche n’est pas non plus techniquement décrit comme tel dans le code, mais il fait également quelque chose de similaire avec ses requêtes, c’est-à-dire le chargement anticipé.

register_search_topic_eager_load

La méthode d’API de plugin serveur register_search_topic_eager_load vous permet de charger anticipativement des tables supplémentaires dans la recherche. Par exemple :

register_search_topic_eager_load do |opts|
    %i(example_table)
end

Champs personnalisés

Les champs personnalisés sont alimentés par le module HasCustomFields. Ce module est inclus dans les modèles qui ont des champs personnalisés associés, c’est-à-dire Post, Topic, Category et Group. Le module HasCustomFields possède son propre système de préchargement qui peut être utilisé de diverses manières. La meilleure façon d’utiliser le préchargement HasCustomFields est via les méthodes d’API de plugin serveur, qui devraient être relativement explicites compte tenu de tout ce que j’ai dit jusqu’à présent.

register_preloaded_category_custom_fields

add_preloaded_group_custom_field

add_preloaded_topic_list_custom_field

Vous pouvez également utiliser certaines des méthodes de niveau inférieur dans HasCustomFields pour effectuer votre propre préchargement lorsque nécessaire, comme nous l’avons vu dans l’exemple du plugin ActivityPub. Dans cet exemple, preload_custom_fields du module HasCustomFields est utilisé pour précharger les champs personnalisés des messages dans TopicView.on_preload.

TopicView.on_preload do |topic_view|
  ##
  Post.preload_custom_fields(topic_view.posts, activity_pub_post_custom_field_names)
  ##
end
4 « J'aime »