【问题标题】:Facet search using MongoDB使用 MongoDB 进行构面搜索
【发布时间】:2012-09-14 17:05:23
【问题描述】:

我正在考虑在我的下一个项目中使用 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)

【问题讨论】:

    标签: mongodb faceted-search


    【解决方案1】:

    我认为使用 Apache Solr 或 ElasticSearch 可以获得更大的灵活性和性能,但使用 Aggregation Framework 支持这一点。

    使用 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}}
    )
    

    【讨论】:

    • 聚合框架似乎很有希望。我认为每个方面组执行额外的查询没有任何问题。让我创建一个 POC 应用程序来验证这个实现。
    • 是的,它真的很强大,它给了我们很多可能性。这个框架的主要问题是查询优化。使用分片缺乏查询优化。我正在努力修补这个问题并将其拉入 github。
    【解决方案2】:

    MongoDB 3.4 引入faceted search

    $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 中有没有办法只运行一个查询来获取文档和相关的方面?
    【解决方案3】:

    使用 MongoDB 进行更高级搜索的一个流行选项是将 ElasticSearch 与社区支持的 MongoDB River Plugin 结合使用。 MongoDB River 插件将来自 MongoDB 的文档流馈送到 ElasticSearch 中进行索引。

    ElasticSearch 是一个基于Apache Lucene 的分布式搜索引擎,具有基于http 的RESTful JSON 接口。有一个Facet Search API 和许多其他高级功能,例如Percolate"More like this"

    【讨论】:

      【解决方案4】:

      您可以进行查询,问题是它是否快。即类似:

      find( { size:'S', color:'Blue', Brand:{$in:[...]} } )
      

      问题是性能如何。产品中还没有任何用于分面搜索的特殊功能。未来可能会有一些类似交叉路口的查询计划,这些计划很好,但那是待定/未来。

      • 如果您的属性是预定义的集合,并且您知道它们是什么,则可以在每个属性上创建一个索引。当前实现中只会使用其中一个索引,因此这将有所帮助,但仅能帮助您到目前为止:如果数据集中等大小,可能没问题。

      • 您可以使用复合索引来复合两个或多个属性。如果你有少量的属性,这可能会很好。索引不需要使用所有变量查询,但在上面的一个中,对三个中任意两个的复合索引可能比对单个项目的索引执行得更好。

      • 如果您没有太多 sku 蛮力将起作用;例如如果您是 1MM skues,则在 ram 中进行表扫描可能足够快。在这种情况下,我将制作一个仅包含方面值的表格,并使其尽可能小,并将完整的 sku 文档保存在单独的集合中。例如:

        facets_collection: {sz:1,brand:123,clr:'b',_id:} ...

      如果构面维度的# 不是太高,您可以改为对构面维度进行高度复合索引,并且无需额外工作即可获得与上述相同的索引。

      如果你创建了一些索引,最好不要创建太多以至于它们不再适合 ram。

      考虑到查询运行,这是一个性能问题,可能只使用 mongo,如果速度不够快,请使用 solr。

      【讨论】:

        【解决方案5】:

        分面解决方案(基于计数)取决于您的应用程序设计。

        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)
        }
        

        除此步骤外,您的应用程序服务器可以在发送回客户端之前进行颜色/尺寸分组。

        注意 - 组合 facet 及其值的方法为您提供聚合的所有 facet 值,您可以避免 - “使用 MongoDB 的主要问题是您必须查询 N 次:首先获取匹配结果,然后每组一次;在使用全文搜索引擎时,您可以在一个查询中获得所有信息。”见加西亚的回答

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-10
          • 2016-11-20
          • 2021-06-11
          • 2013-04-06
          • 1970-01-01
          相关资源
          最近更新 更多