N + 1 проблема в монгоиде - PullRequest
10 голосов
/ 12 октября 2010

Я использую Mongoid для работы с MongoDB в Rails.

Я ищу что-то вроде активной записи include. В настоящее время мне не удалось найти такой метод в mongoid orm.

Кто-нибудь знает, как решить эту проблему в mongoid или, возможно, в mongomapper, который известен как еще одна хорошая альтернатива.

Ответы [ 8 ]

16 голосов
/ 12 октября 2011

Теперь, когда прошло некоторое время, Mongoid действительно добавил поддержку для этого.Смотрите раздел «Стремительная загрузка» здесь:
http://docs.mongodb.org/ecosystem/tutorial/ruby-mongoid-tutorial/#eager-loading

Band.includes(:albums).each do |band|
  p band.albums.first.name # Does not hit the database again.
end

Я бы хотел отметить:

  1. Rails * :include не делаетjoin
  2. SQL и Mongo оба требуют активной загрузки.
  3. Проблема N + 1 возникает в сценарии такого типа (запрос, сгенерированный внутри цикла):

.

<% @posts.each do |post| %>
  <% post.comments.each do |comment| %>
    <%= comment.title %>
  <% end %>
<% end %>

Похоже, ссылка, опубликованная @amrnt, была объединена с Mongoid.

13 голосов
/ 12 октября 2010

Обновление: Прошло два года с тех пор, как я опубликовал этот ответ, и все изменилось. Подробнее см. ответ tybro0103 .


Старый ответ

Судя по документации обоих драйверов, ни один из них не поддерживает то, что вы ищете. Возможно, потому что это ничего не решит .

Функциональность :include ActiveRecord решает проблему N + 1 для баз данных SQL . Сообщая ActiveRecord, какие связанные таблицы следует включить, он может создать отдельный запрос SQL с помощью операторов JOIN. Это приведет к одному вызову базы данных, независимо от количества таблиц, которые вы хотите запросить.

MongoDB позволяет запрашивать только одну коллекцию за раз . Он не поддерживает ничего вроде JOIN. Поэтому, даже если бы вы могли указать Mongoid, какие другие коллекции он должен включить, ему все равно придется выполнять отдельный запрос для каждой дополнительной коллекции.

6 голосов
/ 23 октября 2012

Хотя другие ответы верны, в текущих версиях Mongoid метод include - лучший способ достичь желаемых результатов.В предыдущих версиях, где include не был доступен, я нашел способ избавиться от проблемы n + 1 и думал, что о ней стоит упомянуть.

В моем случае это была проблема n + 2.

class Judge
  include Mongoid::Document

  belongs_to :user
  belongs_to :photo

  def as_json(options={})
    {
      id: _id,
      photo: photo,
      user: user
    }
  end
end

class User
  include Mongoid::Document

  has_one :judge
end

class Photo
  include Mongoid::Document

  has_one :judge
end

действие контроллера:

def index
  @judges = Judge.where(:user_id.exists => true)
  respond_with @judges
end

Этот ответ as_json приводит к ошибке n + 2 запроса из записи судьи.в моем случае предоставление серверу разработки времени ответа:

Завершено 200 OK за 816 мс (Просмотры: 785,2 мс)

Ключом к решению этой проблемы является загрузкаПользователи и фотографии в одном запросе вместо 1 на 1 для каждого судьи.

Вы можете сделать это с помощью Mongoids IdentityMap Mongoid 2 и Mongoid 3 поддерживают эту функцию.

Сначала включите карту идентификации в файле конфигурации mongoid.yml:

development:
  host: localhost
  database: awesome_app
  identity_map_enabled: true

Теперь измените действие контроллера для ручной загрузки пользователей и фотографий.Примечание: запись Mongoid :: Relation будет лениво оценивать запрос, поэтому вы должны вызвать to_a, чтобы фактически запросить записи и сохранить их в IdentityMap.

def index
  @judges ||= Awards::Api::Judge.where(:user_id.exists => true)
  @users = User.where(:_id.in => @judges.map(&:user_id)).to_a
  @photos = Awards::Api::Judges::Photo.where(:_id.in => @judges.map(&:photo_id)).to_a
  respond_with @judges
end

В результате получается всего 3 запроса.1 для судей, 1 для пользователей и 1 для фотографий.

Выполнено 200 ОК за 559 мс (просмотров: 87,7 мс)

Как этоРабота?Что такое IdentityMap?

IdentityMap помогает отслеживать, какие объекты или записи уже были загружены.Поэтому, если вы получите первую запись о пользователе, IdentityMap сохранит ее.Затем, если вы попытаетесь получить того же пользователя снова, Mongoid запросит IdentityMap для пользователя, прежде чем он снова запросит базу данных.Это сохранит 1 запрос к базе данных.

Таким образом, загружая всех пользователей и фотографии, которые, как мы знаем, нам нужны для судей json, в ручных запросах, мы предварительно загружаем данные в IdentityMap одновременно.,Затем, когда судья требует, чтобы он был пользователем и фотографией, он проверяет IdentityMap и не должен запрашивать базу данных.

5 голосов
/ 03 декабря 2010

ActiveRecord :include обычно не выполняет полное объединение для заполнения объектов Ruby.Это делает два звонка.Сначала нужно получить родительский объект (скажем, сообщение), затем второй вызов, чтобы получить связанные объекты (комментарии, принадлежащие сообщению).

Mongoid работает по существу так же, как и для ссылочных ассоциаций.*

В контроллере вы получаете сообщение:

@post = Post.find(params[:id])

По вашему мнению вы перебираете комментарии:

<%- @post.comments.each do |comment| -%>
    VIEW CODE
<%- end -%>

Mongoid найдет сообщение в коллекции.Когда вы нажимаете итератор комментариев, он делает один запрос, чтобы получить комментарии.Mongoid оборачивает запрос в курсор, поэтому он является истинным итератором и не перегружает память.

Mongoid lazy загружает все запросы, чтобы разрешить такое поведение по умолчанию.Тег :include не нужен.

1 голос
/ 29 июня 2011
0 голосов
/ 30 июля 2014

В моем случае у меня была не вся коллекция, а объект, который вызвал n + 1 (пуля говорит, что).

Таким образом, вместо записи ниже, что вызывает n + 1

quote.providers.officialname

Я написал

Quote.includes(:provider).find(quote._id).provider.officialname

Это не вызвало проблемы, но заставило меня задуматься, повторюсь ли я, или проверка n + 1 не нужна для монгоида.

0 голосов
/ 13 октября 2010

Внедрение подробных записей / документов в основную запись / документ.

0 голосов
/ 12 октября 2010

Вам необходимо обновить свою схему, чтобы избежать этого N + 1, в MongoDB нет решения для какого-либо присоединения.

...