5

这是问题中概述的项目的延续。

我有以下模型:

class Product {
  public string Id { get; set; }
  public string[] Specs { get; set; }
  public int CategoryId { get; set; }
}

“规格”数组存储由特殊字符连接的产品规格名称值对。例如,如果产品颜色为蓝色,则规格字符串将为“Color~Blue”。以这种方式表示规格允许查询具有由查询指定的多个规格值的产品。我想支持两个主要查询:

  1. 获取给定类别中的所有产品。
  2. 获取给定类别中具有一组指定规格的所有产品。

这适用于 RavenDB。但是,除了满足给定查询的产品之外,我还想返回一个结果集,其中包含查询指定的产品集的所有规范名称-值对。规范名称-值对应按规范的名称和值分组,并包含具有给定规范名称-值对的产品的计数。对于查询 #1,我创建了以下 map reduce 索引:

class CategorySpecGroups {
    public int CategoryId { get; set; }
    public string Spec { get; set; }
    public int Count { get; set; }
}


public class SpecGroups_ByCategoryId : AbstractIndexCreationTask<Product, CategorySpecGroups>
{
    public SpecGroups_ByCategoryId()
    {
        this.Map = products => from product in products
                               where product.Specs != null
                               from spec in product.Specs
                               select new
                               {
                                   CategoryId = product.CategoryId,
                                   Spec = spec,
                                   Count = 1
                               };

        this.Reduce = results => from result in results
                                 group result by new { result.CategoryId, result.Spec } into g
                                 select new
                                 {
                                     CategoryId = g.Key.CategoryId,
                                     Spec = g.Key.Spec,
                                     Count = g.Sum(x => x.Count)
                                 };
    }
}

然后我可以查询该索引并获取给定类别中的所有规范名称-值对。我遇到的问题是要获得相同的结果集,但要获得同时按类别和一组规范名称-值对过滤的查询。使用 SQL 时,将通过对按类别和规格过滤的一组产品进行分组来获得此结果集。一般来说,这种类型的查询很昂贵,但是当同时按类别和规格进行过滤时,产品集通常很小,尽管不足以放入单个页面 - 它们可能包含多达 1000 个产品。作为参考,MongoDB 支持方法,可用于实现相同的结果集。这将执行 ad hoc 分组服务器端并且性能是可以接受的。

如何使用 RavenDB 获得这种类型的结果集?

一种可能的解决方案是获取查询的所有产品并在内存中执行分组,另一种选择是如上所述创建 mapreduce 索引,尽管这样做的挑战是推导出可以为给定类别进行的所有可能的规范选择此外,此类索引的大小可能会爆炸式增长。

例如,看看这个紧固件类别页面。用户可以通过选择属性来过滤他们的选择。选择属性时,它会缩小产品的选择并显示新产品集中的属性。这种类型的交互通常称为分面搜索

编辑

同时,我将尝试使用Solr的解决方案,因为它们支持开箱即用的分面搜索。

编辑 2

RavenDB 似乎也支持分面搜索(这当然是有道理的,索引由 Lucene 存储,就像 Solr 一样)。我将对此进行探索并发布更新。

编辑 3

RavenDB 分面搜索功能按预期工作。我为每个类别 ID 存储一个构面设置文档,用于计算给定类别中查询的构面。我现在遇到的问题是性能。对于具有 4500 个不同类别的 500k 产品的集合,产生 4500 个方面设置文档,按类别 id 的查询在查询方面时大约需要 16 秒,而在不查询方面时大约需要 0.05 秒。测试的特定类别包含大约 6k 个产品、23 个不同的 facet 和 2k 个不同的 facet name-range 组合。在查看FacetedQueryRunner中的代码后似乎一个方面查询将导致对每个方面名称-值组合的 Lucene 查询以获取计数,以及对每个方面名称的查询以获取术语。实现的一个问题是,无论查询如何,它都会检索给定构面名称的所有不同术语,这在大多数情况下会显着减少构面的术语数量,从而减少 Lucene 查询的数量。这里提高性能的一种方法是为每个构面设置文档存储一个 MapReduce 计算结果集(如上所示),然后可以在进一步按构面过滤时查询该结果集以获取所有不同的术语。然而,整体性能可能仍然太慢。

4

1 回答 1

3

我已经使用RavenDB faceted search实现了这个功能,但是我对FacetedQueryRunner进行了一些更改以支持启发式优化。启发式是,在我的例子中,构面只显示在叶子类别中。这是一个合理的约束,因为根类别和内部类别之间的导航可以由搜索或子类别列表驱动。

现在给定约束,我为每个叶类别存储一个 FacetSetup 文档,其 ID 类似于“facets/category_123”。当存储构面设置文档时,我可以访问构面名称以及类别中包含的构面值(或范围)。因此,我可以将所有可用的 facet 值存储在 FacetSetup 文档中每个 Facet 的 Ranges 集合中,但是 facet 模式仍然是 FacetMode.Default。

以下是对 FacetedQueryRunner 的更改。具体来说,优化会检查给定构面是否存储范围,在这种情况下,它会返回这些值以用于搜索,而不是获取与给定构面关联的索引中的所有术语。在大多数情况下,这将显着减少所需的 Lucene 搜索次数,因为给定类别中的可用分面值是整个索引中分面值的子集。

可以进行的下一个优化是,如果原始查询仅按类别 id 过滤,则 FacetSetup 文档实际上也可以存储计数。一种方法虽然很老套,但可以将计数附加到 Ranges 集合中的每个构面值,然后将布尔值添加到 FacetSetup 文档以指示附加了计数。现在这个方面查询基本上会返回 FacetSetup 文档中的值——无需查询。

现在需要考虑的是使 FacetSetup 文档保持最新,但无论哪种方式都需要这样做。除此之外,还可以使用优化缓存,我相信这是 Solr 分面搜索所采用的方法。

此外,如果 FacetSetup 文档与产品集合自动同步会很好,因为它们实际上是对最初按类别 id、分面名称和值分组的产品集的聚合 MapReduce 操作的结果。

于 2011-10-11T19:25:29.100 回答