Как бороться с отношениями «многие ко многим» в MongoDB, когда встраивание не является ответом? - PullRequest
6 голосов
/ 09 августа 2011

Вот сделка.Предположим, у нас есть следующая схема данных в MongoDB:

  • items: коллекция с большими документами, которые содержат некоторые данные (абсолютно неважно, что это на самом деле).
  • item_groups: коллекция с документами, которые содержат список items._id с именем item_groups.items плюс некоторые дополнительные данные.

Итак, эти два связаны между собой отношением «многие ко многим».Но есть одна хитрая вещь: по какой-то причине я не могу хранить элементы в группах элементов, поэтому - как и говорит заголовок - встраивание - это не ответ.

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

Единственное жизнеспособное решение, которое я нашел, это использовать Map / Reduce.подход с фиктивной функцией уменьшения:

function map () {
    // imagine that item_criteria came from the scope.
    // it's a mongodb query object.
    item_criteria._id = {$in: this.items};
    var group_size = db.items.count(item_criteria);
    // this group holds no relevant items, skip it
    if (group_size == 0) return;

    var key = this._id.str;
    var value = {size: group_size, ...};

    emit(key, value);
}

function reduce (key, values) {
    // since the map function emits each group just once,
    // values will always be a list with length=1
    return values[0];
}

db.runCommand({
    mapreduce: item_groups,
    map: map,
    reduce: reduce,
    query: item_groups_criteria,
    scope: {item_criteria: item_criteria},
});

Строка проблемы:

item_criteria._id = {$in: this.items};

Что если this.items.length == 5000 или даже больше?Мой опыт работы с СУРБД громко кричит:

SELECT ... FROM ... WHERE whatever_id IN (over 9000 comma-separated IDs)

- определенно не самый лучший способ пойти .

Спасибо, ооочень большое за ваше время, ребята!

Я надеюсь, что лучшим ответом будет что-то вроде: "ты глуп, прекрати думать в стиле RDBMS, используй $ its_a_kind_of_magicSphere из последней версии MongoDB":)

Ответы [ 2 ]

4 голосов
/ 31 декабря 2011

Я думаю, что вы боретесь с отделением моделирования предметной области от моделирования базы данных. Я тоже боролся с этим, когда пробовал MongoDb.

Ради семантики и ясности я собираюсь заменить Groups словом Categories

По сути, ваша теоретическая модель является отношением «многие ко многим» в том смысле, что каждый Item может принадлежать Categories, а каждый Category может иметь много Items.

Это лучше всего обрабатывается при моделировании объектов вашего домена, а не в схеме БД, особенно при реализации базы данных документов (NoSQL). В вашей схеме MongoDb вы «подделываете» отношение «многие ко многим», используя комбинацию моделей документов верхнего уровня и встраивание.

Встраивание трудно проглотить для людей, пришедших из бэкэндов персистентности SQL, но является существенной частью ответа. Хитрость заключается в том, чтобы решить, является ли он мелким или глубоким, односторонним или двусторонним и т. Д.


Модели документов верхнего уровня

Поскольку ваши Category документы содержат некоторые собственные данные и на них ссылается огромное количество Items, я согласен с вами, что полностью встраивать их в каждый Item неразумно.

Вместо этого обрабатывайте объекты Item и Category как документы верхнего уровня. Убедитесь, что ваша схема MongoDb выделяет таблицу для каждого из них, чтобы у каждого документа был свой ObjectId.

Следующий шаг - решить, куда и сколько встраивать ... правильного ответа нет, все зависит от того, как вы его используете и каковы ваши амбиции по масштабированию ...

Решения по встраиванию

1. Предметы

Как минимум, ваши Item объекты должны иметь свойство коллекции для своих категорий. По крайней мере, эта коллекция должна содержать ObjectId для каждого Category.

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

Например, если я хочу перечислить группу элементов на моей веб-странице в сетке и показать названия категорий, частью которых они являются. Очевидно, что мне не нужно знать все о Category, но если у меня есть только встроенный ObjectId, потребуется второй запрос, чтобы получить какие-либо подробности об этом.

Вместо этого, что было бы наиболее целесообразно, это встроить свойство Name категории в коллекцию вместе с ObjectId, чтобы при возврате Item теперь можно было отображать имена категорий без другого запроса.

Самое важное, что нужно помнить, это то, что объекты ключа / значения, встроенные в Item, которые «представляют» Category, не обязательно должны соответствовать реальной модели документа Category ... Это не ООП или реляционный моделирование базы данных.

2. Категории

В обратном порядке вы можете оставить вложение односторонним и не иметь никакой Item информации в своих Category документах ... или вы можете добавить коллекцию для данных элементов, как описано выше (ObjectId или ObjectId + Name) ...

В этом направлении я лично склонялся бы к тому, чтобы ничего не вставлять ... более чем вероятно, если бы я хотел Item информацию для своей категории, я хочу ее много, больше, чем просто имя ... и глубокое встраивание документ верхнего уровня (Item) не имеет смысла. Я просто смирился бы с запросом базы данных для коллекции Предметов, где каждый из них обладал ObjectId моей Категории в своей коллекции Категорий.

Фу ... смущает наверняка. Суть в том, что у вас будет некоторое дублирование данных, и вам придется настроить ваши модели для обеспечения максимальной производительности. Хорошая новость заключается в том, что это то, что хорошо умеют MongoDb и другие базы документов ...

1 голос
/ 04 сентября 2011

Почему бы не использовать противоположный дизайн?

Вы храните товары и группы предметов. Если ваша первая идея хранить элементы в записях item_group, то, возможно, обратная идея неплохая: -)

Позвольте мне объяснить:

в каждом предмете вы храните группы, к которым он принадлежит. (Вы находитесь в NOSql, дублирование данных в порядке!) например, предположим, что вы сохраняете в элементах записей список, называемый группами, и ваши элементы выглядят так: { _Я бы : .... , название : .... , группы: [ObjectId (...), ObjectId (...), ObjectId (...)] }

Тогда идея уменьшения карты отнимает много сил:

map = function()  {
    this.groups.forEach( function(groupKey) {
        emit(groupKey, new Array(this))
    }
}


reduce = function(key,values) {
   return Array.concat(values);
}


db.runCommand({
   mapreduce : items,
   map : map,
   reduce : reduce,
   query : {_id :  {$in : [...,....,.....] }}//put here you item ids
})

Вы можете добавить некоторые параметры (например, завершить, чтобы изменить вывод карты), но это может помочь вам.

Конечно, вам нужна другая коллекция, в которой вы храните детали item_groups, если она вам нужна, но в некоторых случаях (если эта информация о item_groups не существует или не изменяется, или вас это не волнует) что у вас нет самой последней его версии) они вам вообще не нужны!

Это дает вам подсказку о решении вашей проблемы?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...