Solr/Solarium统计唯一字段值计数的性能问题咨询
问题描述
我正在使用Solarium PHP库连接Solr实例,现有包含约350万条文档的企业信息索引,检索与过滤功能运行正常,但存在一项需求难以高效实现:索引文档存储企业信息,需统计指定查询条件下索引内的唯一电话号码总数量——部分关联企业共用同一电话号码,部分企业未登记电话号码。
此前尝试的多种统计方案均存在性能问题:
- 分面(Facets)方案不适用于该场景:单次分面请求最多返回100条结果,针对350万文档量级需要发起海量请求
- 调用
getStats()方法实现统计的执行速度同样较慢 - 最终采用GroupComponent分组查询实现该统计逻辑,但当匹配结果集规模超过10万条时,查询加载耗时极长,最终甚至会导致Solr服务崩溃。已调高内存限制避免服务崩溃,但查询响应时长仍无法满足正常使用要求,实现代码如下:
$groupComponent = $select->getGrouping(); $groupComponent->addField('phone'); $groupComponent->setNumberOfGroups(true); $groupComponent->setLimit(0); $groupComponent->setTruncate(true); $groupComponent->setFormat('simple'); $groupComponent->setFacet(true); $resultset = $this->client->execute($select); $groups = $resultset->getGrouping();
当前存在的疑问:
- 我仅需要统计得到的唯一值计数结果,不需要返回具体匹配文档。虽已将
limit参数设置为0,但不确定该参数在此配置下是代表返回0条结果还是无限制返回,即使将limit设为1,查询性能也没有明显提升,不确定Solr是否支持仅返回分组计数、不返回具体结果的配置。 - 尝试添加
$groupComponent->setMainresult(true);配置,该配置不仅没有提升查询速度,返回的电话号码计数结果始终为0。
需要可在Solarium侧或Solr侧优化该统计流程、提升执行效率的可行方案。
解决方案
方案1:使用Cardinality基数聚合(Solr 8.0+ 首选,性能最优)
你之前用的普通分面、stats、分组查询性能差的核心原因,是这些方案要么需要遍历全量匹配值做精确全量计算,要么会加载不必要的文档、分组元数据。Solr 8.0版本后原生支持的基数聚合专门面向大规模数据集唯一值计数场景,底层用HyperLogLog++算法实现,可在可控误差范围内实现毫秒级统计,全程不需要加载具体文档内容。
前置配置
首先确保phone字段配置符合聚合要求:字段类型设为string(不要用分词类型存电话号码),开启docValues,关闭不必要的存储属性:
<field name="phone" type="string" indexed="true" stored="false" docValues="true"/>
如果之前没开docValues,修改schema后需要全量重建索引才能生效。
Solarium侧调用代码
不需要使用Group组件,直接调用JSON聚合接口即可:
// 核心配置:设置rows=0,完全不返回匹配文档,砍掉无用数据传输和加载开销 $select->setRows(0); $agg = $select->createAggregation() ->setKey('unique_phone_count') ->setType('cardinality') ->setField('phone') // 精度阈值越高误差越小,性能损耗略有上升,设为10000时统计误差低于0.5%,完全满足企业业务统计要求 ->setOption('precisionThreshold', 10000); $result = $this->client->execute($select); $uniquePhoneCount = $result->getAggregation()['unique_phone_count'];
如果需要100%精确计数,把precisionThreshold设置为大于业务可能的最大匹配量即可(比如设为10000000),性能依然比分组查询高5~20倍。
方案2:低版本Solr兼容方案(Solr 7及以下)
如果使用的Solr版本不支持基数聚合,不要用Group组件做统计,改用优化配置的分面查询,性能比当前分组方案高3~10倍:
$select->setRows(0); // 关闭所有无关查询组件,减少额外计算 $select->getHighlighting()->setEnabled(false); $select->getSpellcheck()->setEnabled(false); $facetSet = $select->getFacetSet(); $facetSet->createFacetField('phone') ->setField('phone') ->setMinCount(1) // 用enum分面算法,避免全量值遍历 ->setMethod('enum') // 开启exists参数,仅统计存在值的数量,不返回具体分面值列表 ->setExists(true);
当前写法的问题说明
- Group组件的
setLimit(0)作用是每个分组内返回0条文档,不是不生成分组列表,Solr依然会遍历所有匹配的phone值构建全部分组元数据,这部分计算开销完全没有省掉,这是你当前查询慢的核心原因 setMainresult(true)返回的是未分组的主结果集匹配数,和分组唯一值计数没有关系,返回0属于配置逻辑错误,不是功能问题- 所有统计类查询必须加
$select->setRows(0),禁止返回任何匹配文档内容,这一步至少能减少30%以上的无用IO和内存占用。
额外优化点
- 所有用于过滤、聚合的字段都要开启
docValues,避免filter query、聚合操作走索引全表扫描 - 仅用于统计的字段统一设置
stored=false,不需要存储原始值,能大幅降低索引体积,提升查询速度 - 统计查询不要关联高亮、拼写检查、联想推荐等无关组件,减少不必要的性能损耗
内容的提问来源于stack exchange,提问作者Frank
相关产品推荐
相关产品推荐

