Al trabajar con la aplicación Discourse de Rails, ya sea creando un plugin o realizando una solicitud de extracción a discourse/discourse, hay varios contextos en los que te encontrarás con problemas de consulta N+1. Este tema explica cómo manejar la precarga de datos para abordar tales problemas y mantener el rendimiento de Discourse.
Problemas de consulta N+1 en Rails
Primero, si aún no estás familiarizado con los problemas de consulta N+1 y cómo Rails los aborda, consulta esta sección de las guías.
Temas
Los problemas N+1 a tener en cuenta al cargar un tema son cuando cargas tablas asociadas con una publicación. Ahora, los datos serializados para un tema individual se preparan y precargan en la clase TopicView. Esa clase tiene un hook on_preload que te permite precargar tablas asociadas antes de que ejecute su consulta principal. Hay un buen ejemplo de uso de TopicView.on_preload en el Plugin ActivityPub
Este uso del hook precarga campos personalizados, que cubriremos a continuación, y carga asociaciones adicionales para las publicaciones del tema. Notarás que utiliza ActiveRecord::Associations::Preloader para hacerlo. Puedes leer sobre esa clase en la documentación:
Listas de Temas
Los problemas N+1 a tener en cuenta al cargar una lista de temas son cuando se cargan tablas asociadas con un tema. Similar a la clase TopicView para temas individuales, existe una clase TopicList para listas de temas. Y al igual que TopicView, TopicList también tiene un método on_preload que puedes usar para conectarte a la consulta principal de la lista de temas para cargar tablas asociadas antes de que la consulta llegue a la base de datos. Hay un buen ejemplo de eso en el Plugin Discourse Assign:
register_topic_preloader_associations
La clase TopicList tiene un método de API de plugin del servidor register_topic_preloader_associations, que esencialmente aplica el mismo patrón que vimos en el Plugin ActivityPub, utilizando ``ActiveRecord::Associations::Preloader`. Hará ese trabajo por ti si le pasas una matriz de asociaciones.
register_topic_list_preload_user_ids
Además de ejecutar una gran consulta para obtener todos los temas necesarios, la clase TopicList también ejecuta una gran consulta para obtener todos los usuarios necesarios para la lista, incluyendo cada:
- usuario del tema
- último usuario de la publicación
- usuario destacado del tema
- usuarios permitidos del tema
Si estás cargando otros usuarios para cada tema, este método de API te permite incluir sus IDs de usuario en la gran consulta de usuarios para que sus datos se precarguen junto con el resto.
Categorías
Hay una lista principal de categorías utilizada en varios lugares del sitio que se carga y se almacena en caché en el modelo Site. Si bien esto técnicamente no es precarga en el sentido estricto, hace algo similar, así que pensé en incluirlo aquí.
site_all_categories_cache_query modifier
El método all_categories_cache tiene un modificador de API de plugin del servidor que te permite modificar la gran consulta de categorías: site_all_categories_cache_query. Usas este modificador de la siguiente manera en tu plugin.rb:
register_modifier(:site_all_categories_cache_query) do |query|
query.where("categories.name LIKE 'Cool%'")
end
Búsqueda
La precarga de búsqueda tampoco se describe técnicamente como tal en el código, pero también hace algo similar con sus consultas, es decir, carga anticipada.
register_search_topic_eager_load
El método de API de plugin del servidor register_search_topic_eager_load te permite precargar tablas adicionales en la Búsqueda. Por ejemplo:
register_search_topic_eager_load do |opts|
%i(example_table)
end
Campos Personalizados
Los Campos Personalizados están impulsados por la preocupación HasCustomFields. Esa preocupación se incluye en los modelos que tienen campos personalizados asociados, es decir, Post, Topic, Category y Group. El módulo HasCustomFields tiene su propio sistema de precarga que se puede utilizar de varias maneras. La mejor manera de utilizar la precarga de HasCustomFields es a través de los métodos de API de plugin del servidor, que deberían ser relativamente autoexplicativos dados todo lo que he dicho hasta ahora.
register_preloaded_category_custom_fields
add_preloaded_group_custom_field
add_preloaded_topic_list_custom_field
También puedes usar algunos de los métodos de nivel inferior en HasCustomFields para hacer tu propia precarga cuando sea necesario, como vimos en el ejemplo del Plugin ActivityPub. En ese ejemplo, se utiliza la preocupación HasCustomFields preload_custom_fields para precargar campos personalizados de publicaciones en TopicView.on_preload.
TopicView.on_preload do |topic_view|
##
Post.preload_custom_fields(topic_view.posts, activity_pub_post_custom_field_names)
##
end