Tantivy 如何用 composite 聚合对大结果集做高效分页?
Tantivy 如何用 composite 聚合对大结果集做高效分页【免费下载链接】tantivyTantivy is a full-text search engine library inspired by Apache Lucene and written in Rust项目地址: https://gitcode.com/GitHub_Trending/ta/tantivyTantivy 是一个用 Rust 编写的全文检索库。当分词后的桶数量很多时terms 聚合的size参数只能截断结果无法继续翻页而 composite 聚合是为这个场景设计的源码注释将其描述为 A paginable aggregation that performs on multiple dimensions (sources)——桶按复合键有序排列因此可以通过对键的各维度做 range 过滤来高效地取下一页见 composite 聚合源码。composite 聚合是 Tantivy 较新加入的聚合类型CHANGELOG.md 中记录了 Add composite aggregation 这一条目其请求 JSON 与 Elasticsearch 的聚合请求格式兼容见 聚合模块说明。本文只覆盖用 composite 聚合对大结果集做分页这一条操作路径。分页机制sources、size 与 after 三个字段composite 聚合的请求包含三部分字段定义见 CompositeAggregation 结构体sources一个或多个分桶维度。每个 source 是一个具名条目支持terms、histogram、date_histogram三种类型size本页返回的桶数量即页大小after上一页最后一个桶的键用于翻到下一页。分页为什么高效桶按复合键排序after键之后引擎在各维度上用范围条件跳过已返回过的部分而不是把全部桶算出来再截断。每页结果里会带回after_key直接回填到下一次请求的after即可。after_key中每个维度值的编码格式为type:value类型前缀取值bool、str、i64、u64、f64、ip、dtAfterKey 实现。准备聚合字段需要 fast 字段Tantivy 的聚合查询依赖 fast field。被聚合的字段必须配置 fast 字段数值等类型schema_builder.add_f64_field(price, FAST)文本类型字段标志带上FAST例如schema_builder.add_text_field(string_id, STRING | FAST)composite 测试用例的建表方式。请求建议用 JSON API 构造。examples/aggregation.rs 中说明通过 JSON 反序列化构造聚合请求比直接构建请求结构体更稳定因此推荐。执行第一页请求下面的完整示例基于 examples/aggregation.rs 的工程模式建 schema、写索引、执行聚合与 composite 测试用例 的请求格式。依赖tantivy与serde_jsontantivy ...的具体版本号替换为你项目使用的版本。use serde_json::{json, Value}; use tantivy::aggregation::agg_req::Aggregations; use tantivy::aggregation::agg_result::AggregationResults; use tantivy::aggregation::AggregationCollector; use tantivy::query::AllQuery; use tantivy::schema::{Schema, STRING, FAST}; use tantivy::{Index, IndexWriter, TantivyDocument}; fn main() - tantivy::Result() { // 被聚合字段必须带 FAST let mut schema_builder Schema::builder(); schema_builder.add_text_field(string_id, STRING | FAST); let schema schema_builder.build(); let index Index::create_in_ram(schema.clone()); // 写入 8 条测试文档分两个 segment commit模拟多 segment 场景 { let mut index_writer: IndexWriter index.writer(20_000_000)?; for term in [apple, banana, cherry, dog] { let doc TantivyDocument::parse_json(schema, format!({{\string_id\: \{}\}}, term))?; index_writer.add_document(doc)?; } index_writer.commit()?; for term in [elephant, fox, grape, zebra] { let doc TantivyDocument::parse_json(schema, format!({{\string_id\: \{}\}}, term))?; index_writer.add_document(doc)?; } index_writer.commit()?; } let reader index.reader()?; let searcher reader.searcher(); // 第一页不传 aftersize 为页大小 let agg_req_str r# { my_composite: { composite: { sources: [ {myterm: {terms: {field: string_id}}} ], size: 3 } } }#; let agg_req: Aggregations serde_json::from_str(agg_req_str)?; let collector AggregationCollector::from_aggs(agg_req, Default::default()); let agg_res: AggregationResults searcher.search(AllQuery, collector)?; let res: Value serde_json::to_value(agg_res)?; // 结果里是 buckets 和 after_key 两个字段 println!({}, res[my_composite]); Ok(()) }第一页结果的文档示例取自 composite 测试用例的断言值size: 3、升序{ buckets: [ {key: {myterm: apple}, doc_count: 1}, {key: {myterm: banana}, doc_count: 1}, {key: {myterm: cherry}, doc_count: 1} ], after_key: {myterm: str:cherry} }每个非空页都会带after_keyafter_key的值是type:value字符串例如str:cherry表示字符串值cherry测试中desc排序最后一页断言的after_key为{myterm: str:apple}见 测试用例。翻页与判断终止把上一页after_key原样填入下一次请求的after即得到下一页。测试代码的翻页写法是// after 直接取上一页结果的 after_key let next_req json!({ my_composite: { composite: { sources: [{myterm: {terms: {field: string_id}}}], size: 3, after: res[my_composite][after_key] } } }); let agg_req: Aggregations serde_json::from_value(next_req)?;按上面 8 个词、size: 3的示例翻页顺序为第 1 页apple/banana/cherry第 2 页dog/elephant/fox第 3 页grape/zebra各页均为文档示例输出。终止条件以 测试中的断言 为准用最后一页的after_key再请求一次结果满足两点——buckets为空数组[]结果中不再出现after_key字段。这就是分页完成的判断方法不需要额外计算总页数。可选参数排序方向、缺失值处理每个 source 支持以下字段定义见 源码结构体orderasc默认或desc。降序翻页时after_key同样有效测试用例 composite_aggregation_term_ordering 验证了desc下的连续翻页missing_bucket默认false即字段缺失的文档被忽略设为true时会生成一个null桶missing_ordernull桶的位置取值default升序时排在最前、降序时排在最后默认值、first、last。date_histogram类型注意两点fixed_interval与calendar_interval二者必须且只能设置一个时区尚不支持所有 interval 都对齐 UTC。histogram的interval对 datetime 字段以毫秒为单位表达。限制与边界每个 source 必须是恰好一个具名条目否则反序列化报错each composite source must have exactly one named entry反序列化校验after_key不支持 NaN反序列化时直接报NaN is not supported in after key聚合过程中遇到 NaN 参与 F64 比较也会报NaN comparison is not supportedAfterKey 实现composite 聚合支持嵌套子聚合的写法本文不展开其嵌套行为以 聚合模块说明 中的 bucket/metric 分层为准。如果你需要更完整的可运行参照仓库内 examples/aggregation.rs 展示了从 schema 到聚合结果的完整链路其中为 range 与 terms 聚合composite 的请求 JSON 只需按本文格式替换即可。【免费下载链接】tantivyTantivy is a full-text search engine library inspired by Apache Lucene and written in Rust项目地址: https://gitcode.com/GitHub_Trending/ta/tantivy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考