Поиск фасетов с помощью 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-->