eul*_*rfx 5 nosql faceted-search ravendb
这是该问题中概述的项目的延续.
我有以下型号:
class Product {
public string Id { get; set; }
public string[] Specs { get; set; }
public int CategoryId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
"Specs"数组存储由特殊字符连接的产品规范名称值对.例如,如果产品的颜色为蓝色,则spec字符串将为"Color~Blue".以这种方式表示规范允许查询具有由查询指定的多个规范值的产品.我想支持两个主要查询:
这适用于RavenDB.但是,除了满足给定查询的产品之外,我还想返回一个结果集,其中包含查询指定的产品集的所有规范名称 - 值对.规范名称 - 值对应按规范的名称和值进行分组,并包含具有给定规范名称 - 值对的产品计数.对于查询#1,我创建了以下map reduce index:
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)
};
}
}
Run Code Online (Sandbox Code Playgroud)
然后,我可以查询此索引并获取给定类别中的所有规范名称 - 值对.我遇到的问题是获得相同的结果集,但是对于一个按类别和一组规范名称 - 值对过滤的查询.使用SQL时,可以通过按类别和规范筛选的一组产品进行分组来获得此结果集.一般来说,这种类型的查询很昂贵,但是当按类别和规格进行过滤时,产品集通常很小,但不足以容纳单个页面 - 它们可能包含多达1000个产品.作为参考,MongoDB支持可用于实现相同结果集的组方法.这将执行ad hoc分组服务器端,并且性能可以接受.
如何使用RavenDB获取此类结果集?
一种可能的解决方案是获取查询的所有产品并在内存中执行分组,另一种选择是如上所述创建mapreduce索引,尽管这样做的挑战是推断出可以为给定类别做出的所有可能的规范选择此外,这种类型的索引可能会爆炸.
例如,请查看此紧固件类别页面.用户可以通过选择属性来过滤他们的选择.选择属性后,它会缩小产品选择范围并在新产品集中显示属性.这种类型的交互通常称为分面搜索.
编辑
与此同时,我将使用Solr尝试解决方案,因为它们支持开箱即用的分面搜索.
编辑2
似乎RavenDB也支持分面搜索(当然这很有意义,索引由Lucene存储,就像Solr一样).我将探索这个并发布更新.
编辑3
RavenDB分面搜索功能按预期工作.我为每个类别ID存储一个构面设置文档,用于计算给定类别中查询的构面.我现在面临的问题是表现.对于包含4500个不同类别的500k产品的集合,产生4500个构面设置文档,按类别ID查询时查询构面时大约需要16秒,而不查询构面时大约需要0.05秒.测试的特定类别包含约6k产品,23个不同的面和2k个不同的面名称范围组合.在查看FacetedQueryRunner中的代码之后,看起来facet查询将导致每个facet名称 - 值组合的Lucene查询以获取计数,以及查询每个构面名称以获取术语.该实现的一个问题是它将检索给定构面名称的所有不同术语而不管查询,这在大多数情况下将显着减少构面的术语数量并因此减少Lucene查询的数量.在这里提高性能的一种方法是为每个构面设置文档存储MapReduce计算结果集(如上所示),然后可以在进一步按构面过滤时查询以获得所有不同的术语.然而,整体表现可能仍然太慢.
我已经使用RavenDB 分面搜索实现了此功能,但是我对FacetedQueryRunner进行了一些更改以支持启发式优化。启发式是,在我的例子中,构面仅显示在叶类别中。这是一个合理的约束,因为根类别和内部类别之间的导航可以由搜索或子类别列表驱动。
现在给定约束,我为每个叶类别存储一个 FacetSetup 文档,其 ID 类似于“facets/category_123”。存储构面设置文档时,我可以访问该类别中包含的构面名称以及构面值(或范围)。因此,我可以将所有可用的facet值存储在FacetSetup文档中每个Facet的Ranges集合中,但是facet模式仍然是FacetMode.Default。
以下是FacetedQueryRunner 的更改。具体来说,优化会检查给定方面是否存储范围,在这种情况下,它会返回这些值以用于搜索,而不是获取与给定方面关联的索引中的所有术语。在大多数情况下,这将显着减少所需的 Lucene 搜索数量,因为给定类别中的可用构面值是整个索引中构面值的子集。
下一个可以进行的优化是,如果原始查询仅按类别 id 进行过滤,那么 FacetSetup 文档实际上也可以存储计数。实现此目的的一种方法(尽管很老套)是将计数附加到 Ranges 集合中的每个方面值,然后向 FacetSetup 文档添加一个布尔值以指示附加计数。现在这个facet查询基本上会返回FacetSetup文档中的值——不需要查询。
现在要考虑的是使 FacetSetup 文档保持最新,但是无论哪种方式都需要这样做。除此之外,还可以利用缓存,我相信这是 Solr 分面搜索所采用的方法。
此外,如果 FacetSetup 文档能够自动与产品集合同步,那就太好了,因为实际上它们是对最初按类别 ID、然后是 Facet 的名称、然后是值分组的产品集进行聚合 MapReduce 操作的结果。