При работе с Rails-приложением Discourse, будь то создание плагина или внесение изменений в репозиторий discourse/discourse, вы столкнетесь с проблемами N+1 запросов в ряде ситуаций. В этой теме объясняется, как обрабатывать предварительную загрузку данных для решения таких проблем и поддержания высокой производительности Discourse.
Проблемы N+1 запросов в Rails
Во-первых, если вы еще не знакомы с проблемами N+1 запросов и тем, как Rails их решает, ознакомьтесь с этим разделом руководства.
Темы
Проблемы N+1, на которые следует обращать внимание при загрузке темы, возникают при загрузке таблиц, связанных с публикацией. Данные, сериализованные для отдельной темы, подготавливаются и предварительно загружаются в классе TopicView. Этот класс имеет хук on_preload, который позволяет предварительно загружать связанные таблицы перед выполнением основного запроса. Хороший пример использования TopicView.on_preload можно найти в плагине ActivityPub:
Это использование хука позволяет как загружать пользовательские поля (о которых мы поговорим ниже), так и загружать дополнительные ассоциации для публикаций в теме. Обратите внимание, что для этого используется ActiveRecord::Associations::Preloader. Ознакомьтесь с документацией по этому классу:
Списки тем
Проблемы N+1, на которые следует обращать внимание при загрузке списка тем, возникают при загрузке таблиц, связанных с темой. Аналогично классу TopicView для отдельных тем, для списков тем существует класс TopicList. Как и TopicView, класс TopicList также имеет метод on_preload, который можно использовать для подключения к основному запросу списка тем, чтобы загрузить связанные таблицы до того, как запрос будет отправлен в базу данных. Хороший пример этого можно найти в плагине Discourse Assign:
register_topic_preloader_associations
Класс TopicList имеет метод API серверного плагина register_topic_preloader_associations, который по сути применяет тот же паттерн, что и в плагине ActivityPub, используя ActiveRecord::Associations::Preloader. Он выполнит эту работу за вас, если вы передадите ему массив ассоциаций.
register_topic_list_preload_user_ids
Помимо выполнения одного большого запроса для получения всех необходимых тем, класс TopicList также выполняет один большой запрос для получения всех необходимых пользователей для списка, включая:
- пользователя темы
- пользователя последней публикации
- пользователя, выделенного в теме
- разрешенных пользователей темы
Если вы загружаете других пользователей для каждой темы, этот метод API позволяет включить их идентификаторы пользователей в большой запрос на пользователей, чтобы их данные были предварительно загружены вместе с остальными.
Категории
Существует основной список категорий, используемый в различных местах сайта, который загружается и кэшируется в модели Site. Хотя технически это не является предварительной загрузкой в строгом смысле, это выполняет схожую задачу, поэтому я решил включить это здесь.
модификатор site_all_categories_cache_query
Метод all_categories_cache имеет модификатор API серверного плагина, который позволяет изменять большой запрос категорий: site_all_categories_cache_query. Используйте этот модификатор в вашем plugin.rb следующим образом:
register_modifier(:site_all_categories_cache_query) do |query|
query.where("categories.name LIKE 'Cool%'")
end
Поиск
Предварительная загрузка при поиске также не описывается как таковая в коде, но она выполняет схожую задачу с запросами, то есть eager loading (предварительную загрузку ассоциаций).
register_search_topic_eager_load
Метод API серверного плагина register_search_topic_eager_load позволяет выполнять eager loading дополнительных таблиц в поиске. Например:
register_search_topic_eager_load do |opts|
%i(example_table)
end
Пользовательские поля
Пользовательские поля работают на основеConcern HasCustomFields. Этот Concern включен в модели, имеющие связанные пользовательские поля, то есть Post, Topic, Category и Group. Модуль HasCustomFields имеет собственную систему предварительной загрузки, которую можно использовать различными способами. Лучший способ использования предварительной загрузки HasCustomFields — через методы API серверного плагина, которые должны быть относительно понятны с учетом всего сказанного выше.
register_preloaded_category_custom_fields
add_preloaded_group_custom_field
add_preloaded_topic_list_custom_field
Вы также можете использовать некоторые методы более низкого уровня в HasCustomFields для собственной предварительной загрузки при необходимости, как мы видели в примере плагина ActivityPub. В этом примере preload_custom_fields Concern HasCustomFields используется для предварительной загрузки пользовательских полей публикаций в TopicView.on_preload.
TopicView.on_preload do |topic_view|
##
Post.preload_custom_fields(topic_view.posts, activity_pub_post_custom_field_names)
##
end