查询
查询主线(old man,取前 10)从打开 IndexReader 开始,到拿回文档原文为止。核心代码在 src/query/(0.27.0 下 22174 行,含单元测试),读者和快照在 src/reader/(663 行)和 src/core/(1562 行,Searcher、Executor),收集器在 src/collector/(7755 行),查询语法在工作区的 query-grammar crate(2957 行),高亮在 src/snippet/(946 行),聚合在 src/aggregation/(36320 行,是主 crate 里最大的模块)。
这一部分回答几个问题:一次查询看到的是哪一组段,提交以后多久能看到新文档;一个查询对象怎么变成每个段上的游标,分数从哪些统计算出来;a b、+a +b、a -b 各走哪种合并算法,哪些形状能用上 Block-Max WAND 剪枝;用户输入的字符串怎么变成查询对象;结果怎么收集、排序、高亮、聚合。
- IndexReader 与 Searcher:
Searcher是一组SegmentReader的不可变快照,reload用ArcSwap换代;默认的OnCommitWithDelay在磁盘目录上靠 500 ms 轮询,实测提交后 24 到 481 ms 才可见;Executor默认单线程,而且要在建 reader 之前设置才生效。 - Query、Weight、Scorer:
Query描述查什么,Weight绑定一次查询的统计,Scorer是一个段上的DocSet游标;TermQuery走一遍;段里有删除时,TermQuery的计数从读词典里的doc_freq退回逐篇数,实测从 0.2 µs 变成 31 µs。 - BM25:公式和
Bm25Weight的实现,k1、b是常量;N 和 avgdl 按整个Searcher的max_doc算,不看文档有没有这个字段;用explain逐项手算一篇维基文档的 5.736597。 - 布尔查询:
BooleanWeight按子句形状挑Intersection、BufferedUnionScorer、Exclude、RequiredOptionalScorer;Block-Max WAND 在 0.27.0 扩到交集和单个词;实测剪枝的收益和失灵的形状,以及块上界在多段索引里会偏低。 - 短语与其他查询:短语查询怎么在位置上求交、
slop怎么算,以及前缀短语、模糊、正则、范围、exists、TermSetQuery这些查询各自走倒排表还是快速字段。 - 查询解析器:
query-grammar把字符串解析成UserInputAst,QueryParser结合 schema 变成LogicalAst和查询对象,默认 OR、字段、boost、模糊和容错解析都在这一步决定。 - 收集器:
Collector和SegmentCollector两层,TopDocs的堆和阈值怎么喂给剪枝,Count、MultiCollector、FacetCollector和按快速字段排序。 - 高亮:
SnippetGenerator怎么从原文里挑出得分最高的片段、标出命中的词、输出 HTML。 - 聚合:Elasticsearch 风格的聚合请求怎么变成每个段上的中间结果、再跨段合并成最终结果。
flowchart TB reader["IndexReader"] --> searcher["Searcher 快照"] qp["QueryParser"] --> query["Query"] query --> weight["Weight 全局统计"] searcher --> weight weight --> scorer["每段 Scorer"] scorer --> coll["Collector"] coll --> merge["merge_fruits"] merge --> doc["Searcher doc"] merge --> snip["Snippet"] coll --> agg["聚合"]
整条查询主线的时序和一次 explain 的逐项拆解在一次查询。查询读的都是段文件:词典在词典,倒排表和块上界在倒排表,字段长度在fieldnorm 与删除位图,取原文在文档存储。
💬 关联讨论
发起讨论加载中...