Очень распространенный случай оптимизации запросов с решением, которое также легко на глазах
Проблема запроса n + 1 - одно из наиболее распространенных узких мест масштабируемости. Он включает в себя выборку списка ресурсов из базы данных, которая включает в себя другие связанные с ними ресурсы. Это означает, что нам, возможно, придется отдельно запрашивать связанные ресурсы. Итак, если у вас есть список из n родительских объектов, необходимо будет выполнить еще n запросов для получения связанных ресурсов. Давайте попробуем решить эту загадку O (n).
Если вы хорошо знакомы с Rails, сериализаторами активных моделей и уже имеете хорошее представление о том, в чем будет заключаться наша проблема, то, возможно, вы можете сразу перейти к коду здесь.
Конкретный пример
Предположим, вы загружаете массив объектов Post в конечной точке GET. Вы также хотите загрузить соответствующих авторов сообщений, встраивая объект author в каждый из объектов сообщения. Вот наивный способ сделать это:
Для каждого из n обрабатываемых объектов Post будет запущен запрос для получения соответствующего объекта User. Следовательно, мы выполним в общей сложности n + 1 запрос. Это ужасно. И вот как это исправить, нетерпеливо загрузив объект User:
Когда простое соединение невозможно
До сих пор для опытных не было ничего нового.
Но давайте усложним это. Предположим, что пользователи сайта не хранятся в той же RDMS, что и сообщения. Скорее, пользователи - это документы, хранящиеся в MongoDB (по какой-либо причине). Как изменить наш сериализатор Post, чтобы оптимально получать информацию о пользователе сейчас? Это будет возвращение к исходной точке:
Трудное положение, в котором наши пользователи теперь находятся в базе данных Mongo, можно заменить, скажем, вызовом сторонней HTTP-службы для выборки пользователей или их сохранения в совершенно другой RDMS. Наша основная проблема по-прежнему заключается в том, что невозможно "присоединить" хранилище данных пользователей к таблице сообщений и получить желаемый ответ в одном запросе.
Конечно, мы можем лучше. Мы можем получить весь ответ за два запроса:
- Получить все сообщения без атрибута author (1 запрос SQL).
- Получите всех соответствующих авторов, выполнив запрос с указанием идентификаторов пользователей, взятых из массива сообщений (1 запрос Mongo с предложением IN).
Введите пакетный загрузчик
Таким образом, наша первоначальная проблема оптимизации была сведена к как сделать этот код читаемым и поддерживаемым. Ребята из Universe придумали изящную жемчужину. Batch Loader мне очень помог.
gem 'batch-loader'
bundle install
Если вы знакомы с обещаниями JavaScript, подумайте о методе get_author_lazily
как о возвращении обещания, которое оценивается позже. Думаю, это хорошая аналогия, поскольку BatchLoader
использует ленивые объекты Ruby. По умолчанию BatchLoader
кэширует загруженные значения, поэтому, чтобы ответы оставались актуальными, вы должны добавить это в свой config/application.rb
:
config.middleware.use BatchLoader::Middleware
Вот и все! Мы решили расширенную версию проблемы n + 1 запросов, сохранив при этом чистый код и правильно используя сериализаторы активных моделей.
Использование AMS для вложенных ресурсов
Но есть одна проблема. Если у вас есть сериализатор User (активные сериализаторы моделей также работают с Mongoid), он не будет вызываться для лениво загружаемых объектов author. , в отличие от раньше. Чтобы исправить это, мы можем использовать блок Ruby и сериализовать объекты author до того, как они будут «назначены» сообщениям.
Вот весь код. Наслаждаться!
Ресурсы
Https://github.com/rails-api/active_model_serializers
Https://gist.github.com/UsamaAshraf/95b0c8d0d64ee193148342a931c0a423