Поиск фасетов с помощью MongoDB
Я собираюсь использовать MongoDB для моего следующего проекта. Одним из основных требований для этого приложения является обеспечение поиска фасетов. Кто-нибудь пытался использовать MongoDB для поиска фасетов?
У меня есть модель продукта с различными атрибутами, такими как размер, цвет, бренд и т. д. При поиске продукта это приложение Rails должно отображать фильтры фасетов на боковой панели. Фасетные фильтры будут выглядеть примерно так:
Size:
XXS (34)
XS (22)
S (23)
M (37)
L (19)
XL (29)
Color:
Black (32)
Blue (87)
Green (14)
Red (21)
White (43)
Brand:
Brand 1 (43)
Brand 2 (27)
5 ответов
Я думаю, что с помощью Apache Solr или ElasticSearch вы получаете больше гибкости и производительности, но это поддерживается с помощью Основы Комплексирования.
основная проблема с использованием MongoDB заключается в том, что вы должны запросить его N раз: сначала для получения совпадающих результатов, а затем один раз в группе; при использовании полнотекстовой поисковой системы вы получаете все это в одном запросе.
пример
//'tags' filter simulates the search
//this query gets the products
db.products.find({tags: {$all: ["tag1", "tag2"]}})
//this query gets the size facet
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}},
{$group: {_id: "$size"}, count: {$sum:1}},
{$sort: {count:-1}}
)
//this query gets the color facet
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}},
{$group: {_id: "$color"}, count: {$sum:1}},
{$sort: {count:-1}}
)
//this query gets the brand facet
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}},
{$group: {_id: "$brand"}, count: {$sum:1}},
{$sort: {count:-1}}
)
когда пользователь фильтрует поиск по грани, нужно добавить этот фильтр для запроса предиката и сопоставления предиката выглядит следующим образом.
//user clicks on "Brand 1" facet
db.products.find({tags: {$all: ["tag1", "tag2"]}, brand: "Brand 1"})
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"},
{$group: {_id: "$size"}, count: {$sum:1}},
{$sort: {count:-1}}
)
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"},
{$group: {_id: "$color"}, count: {$sum:1}},
{$sort: {count:-1}}
)
db.products.aggregate(
{$match: {tags: {$all: ["tag1", "tag2"]}}, brand: "Brand 1"},
{$group: {_id: "$brand"}, count: {$sum:1}},
{$sort: {count:-1}}
)
Mongodb 3.4 вводит граненый поиск
этап $facet позволяет создавать многогранные агрегации, которые характеризуйте данные по нескольким измерениям или фасетам в пределах одиночный этап комплексирования. Многогранные агрегаты обеспечивают множественные фильтры и классификации для просмотра и анализа данных.
входные документы передаются на этап $ facet только один раз.
теперь, вам не нужно запрашивать N раз для получения агрегатов по N группам.
$facet включает различные агрегации в одном наборе входных документов, без необходимости извлекать входные документы несколько раз.
пример запроса для варианта использования OP будет чем-то вроде
db.products.aggregate( [
{
$facet: {
"categorizedByColor": [
{ $match: { color: { $exists: 1 } } },
{
$bucket: {
groupBy: "$color",
default: "Other",
output: {
"count": { $sum: 1 }
}
}
}
],
"categorizedBySize": [
{ $match: { size: { $exists: 1 } } },
{
$bucket: {
groupBy: "$size",
default: "Other",
output: {
"count": { $sum: 1 }
}
}
}
],
"categorizedByBrand": [
{ $match: { brand: { $exists: 1 } } },
{
$bucket: {
groupBy: "$brand",
default: "Other",
output: {
"count": { $sum: 1 }
}
}
}
]
}
}
])
популярным вариантом для более расширенного поиска с MongoDB является использование ElasticSearch в сочетании с сообществом поддерживается MongoDB Река Плагин. Плагин MongoDB River подает поток документов из MongoDB в ElasticSearch для индексирования.
ElasticSearch-это распределенная поисковая система, основанная на Apache Lucene, и имеет спокойный интерфейс JSON по http. Есть API поиска фасетов и ряд другие дополнительные функции, такие как процедить и "больше".
вы можете сделать запрос, вопрос будет это быстро или нет. то есть что-то вроде:
find( { size:'S', color:'Blue', Brand:{$in:[...]} } )
вопрос в том, как производительность. В продукте пока нет специального средства для фасетного поиска. Вниз по дороге могут быть некоторые планы запросов типа пересечения, которые хороши, но это tbd/future.
Если ваши свойства являются предопределенным набором, и вы знаете, что они такое, вы можете создать индекс на каждом из них. Только один из индексов будет использоваться в текущей реализации, поэтому это поможет, но только до сих пор: если набор данных средний плюс по размеру, это может быть хорошо.
вы можете использовать составные индексы, которые, возможно, составляют два или более свойств. Если у вас есть небольшое # свойств, это может работать довольно хорошо. Индекс не должен использовать все запросы переменных, но в приведенном выше составном индексе на любых двух из трех, вероятно, будет работать лучше, чем индекс по одному элементу.
-
Если у вас не слишком много skus грубой силы будет работать; например, если вы 1 мм skues сканирование таблицы в ОЗУ может быть достаточно быстрым. в этом случае я бы сделал таблицу только со значениями фасета и сделал ее как можно меньше и сохранил полные документы sku в отдельной коллекции. например:
facets_collection: {sz: 1, Бренд: 123, clr: 'b', _id:} ...
если # размеров фасета не слишком высоко, вы могли бы вместо этого сделайте очень сложный индекс измерений facit, и вы получите эквивалент выше без дополнительной работы.
Если вы создаете quit несколько индексов, вероятно, лучше не создавать так много, чтобы они больше не помещались в ОЗУ.
учитывая, что запрос выполняется, и это вопрос производительности, можно просто с mongo, и если он недостаточно быстр, то болт на solr.
граненое решение (на основе подсчета) зависит от вашего дизайна приложения.
db.product.insert(
{
tags :[ 'color:green','size:M']
}
)
однако, если можно передать данные в вышеуказанном формате, где грани и их значения объединены вместе, чтобы сформировать согласованный тег, то с помощью приведенного ниже запроса
db.productcolon.aggregate(
[
{ $unwind : "$tags" },
{
$group : {
_id : '$tags',
count: { $sum: 1 }
}
}
]
)
см. результат ниже
{
"_id" : "color:green",
"count" : NumberInt(1)
}
{
"_id" : "color:red",
"count" : NumberInt(1)
}
{
"_id" : "size:M",
"count" : NumberInt(3)
}
{
"_id" : "color:yellow",
"count" : NumberInt(1)
}
{
"_id" : "height:5",
"count" : NumberInt(1)
}
после этого шага сервер приложений может выполнить группировку цветов/размеров перед отправкой обратно клиенту.
Примечание - подход к объединение фасета и его значений дает вам все значения фасета agggregated, и вы можете избежать - " основная проблема с помощью MongoDB заключается в том, что вы должны запросить его N раз: сначала для получения совпадающих результатов, а затем один раз в группе; при использовании полнотекстовой поисковой системы вы получаете все это в одном запросе.- Смотри ответ Гарсии!--4-->