Самый эффективный способ хранения вложенных категорий (или иерархических данных) в Монго?

У нас есть вложенные категории для нескольких продуктов (например, Спорт → Баскетбол → Мужчины, Спорт → Теннис → Женщины) и мы используем Mongo вместо MySQL.

Мы знаем, как хранить вложенные категории в базе данных SQL, такой как MySQL, но будем благодарны за любые советы о том, что делать для Mongo. Операция, которую мы должны оптимизировать, - это быстрый поиск всех товаров в одной категории или подкатегории, которые могут быть вложены на несколько уровней ниже корневой категории (например, все товары в категории " Баскетбол для мужчин " или все товары в категории " Женский теннис ").

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

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

Ответ 1

Первое, что вы хотите решить, это то, какое именно дерево вы будете использовать.

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

Итак, вам нужна схема, которая дает вам возможность быстро запрашивать информацию о ребенке по дорожке, а именно: Спорт → Баскетбол → Мужчины, Спорт → Теннис → Женщины, и на самом деле не нужно действительно масштабировать ее до обновлений.

Как вы правильно заметили, у MongoDB есть хорошая страница документации для этого: https://docs.mongodb.com/manual/applications/data-models-tree-structures/, где 10gen на самом деле устанавливает различные модели и методы схемы для деревьев и описывает основные взлеты и падения из них.

Тот, который должен бросаться в глаза, если вы ищете простой запрос, это материализованные пути: https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-materialized-paths/

Это очень интересный метод построения деревьев, поскольку для запроса на приведенном выше примере в "Womens" в "Tennis" вы можете просто выполнить предопределенное регулярное выражение (которое может использовать индекс: http://docs.mongodb.org/manual/reference/operator/regex/) примерно так:

db.products.find({category: /^Sports,Tennis,Womens[,]/})

найти все продукты, перечисленные под определенным путем вашего дерева.

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

Лучшим способом было бы разместить cat_id на продукте, а затем разделить категории в отдельную коллекцию со схемой:

{
    _id: ObjectId(),
    name: 'Women\'s',
    path: 'Sports,Tennis,Womens',
    normed_name: 'all_special_chars_and_spaces_and_case_senstive_letters_taken_out_like_this'
}

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

Итак, пример изменения "Теннис" на "Бадмин":

db.categories.update({path:/^Sports,Tennis[,]/}).forEach(function(doc){
    doc.path = doc.path.replace(/,Tennis/, ",Badmin");
    db.categories.save(doc);
});

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

И это в основном, как это работает на самом деле. Обновление немного проблематично, но, я полагаю, возможность мгновенного запроса по любому пути с использованием индекса больше подходит для вашего сценария.

Конечно, дополнительное преимущество заключается в том, что эта схема совместима с моделями вложенных множеств: http://en.wikipedia.org/wiki/Nested_set_model, которые, как я обнаружил снова и снова, просто великолепны для сайтов электронной коммерции, например, для тенниса. может быть под "Спорт" и "Досуг", и вы хотите несколько путей в зависимости от того, откуда пользователь пришел.

Схема для материализованных путей легко поддерживает это, просто добавляя другой простой path.

Надеюсь, что это имеет смысл, довольно долго там.

Ответ 2

Если все категории различны, то считайте их тегами. Иерархия не требуется для кодирования элементов, потому что они не нужны, когда вы запрашиваете элементы. Иерархия - это презентационная вещь. Пометьте каждый элемент всеми категориями на этом пути, поэтому "Спорт > Бейсбul > Обувь" можно сохранить как {..., categories: ["sport", "baseball", "shoes"], ...}. Если вы хотите все предметы в категории "Спорт", найдите {categories: "sport"}, если вы хотите только туфли, найдите {tags: "shoes"}.

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

Мое предложение зависит от разных категорий, и я думаю, что они не в вашей нынешней модели. Однако нет причин, по которым вы не можете сделать их отличными. Вероятно, вы решили использовать строки, отображаемые на странице, как имена категорий в базе данных. Если вы вместо этого используете символические имена, такие как "спорт" или "womens_shoes", и используйте таблицу поиска, чтобы найти строку для отображения на странице (это также сэкономит вам часы работы, если название категории когда-либо изменится), и это будет сделать перевод сайта проще, если вам когда-нибудь понадобится это сделать), вы можете легко убедиться, что они разные, потому что они не имеют никакого отношения к тому, что отображается на странице. Итак, если в иерархии есть две "Обувь" (например, "Тенниs > Женщины > Обувь" и "Тенниs > Мужчины > Обувь" ), вы можете просто добавить квалификатор, чтобы сделать их отличными (например, "womens_shoes" и "mens_shoes" ), или "tennis_womens_shoes" ). Символьные имена произвольны и могут быть любыми, вы даже можете использовать числа и просто использовать следующее число в последовательности каждый раз, когда добавляете категорию.