Rust in Everything
基于 tantivy 0.27.0

取回来的文档里没有这个字段

主线位置:查询主线最后一步,searcher.doc(addr) 从文档存储解压一个块取出原文。STORED、INDEXED、FAST 三个 flag 各管什么见 Schema 与字段,doc store 的格式见文档存储。

flowchart TB
  doc["add_document"] --> inv["倒排 INDEXED"]
  doc --> st{"STORED 吗"}
  st -->|是| store[".store 原文"]
  st -->|否| skip["不写"]
  inv --> hit["能搜到"]
  hit --> get["searcher doc"]
  store --> get
  get --> miss["没有 body"]

实验 crate:exp-tantivy/pita-lab,src/bin/p05_stored.rs。M2 Max、macOS 27、rustc 1.99.0,release。

现象

schema 照着 tantivy 的 basic_search 例子写:title 是 TEXT | STORED,body 是 TEXT。写一篇,查一个只在 body 里出现的词 skiff:

hits for `skiff`: 1
to_json: {"title":["The Old Man and the Sea"]}
get_first(body): None
snippet(body): ""
  • 能搜到,说明 body 进了倒排索引。
  • 取回来的文档里只有 title,get_first(body) 是 None,不是空串。
  • 拿 body 做高亮,SnippetGenerator 不报错,给出一个空片段。

原因

TEXT 只有「索引」这一个属性,STORED 是另一个 flag,要显式或上。写文档存储时,BinaryDocumentSerializer::serialize_doc 先过滤字段:

        let stored_field_values = || {
            doc.iter_fields_and_values()
                .filter(|(field, _)| self.schema.get_field_entry(*field).is_stored())
        };

没设 STORED 的值根本没写进 .store,searcher.doc 解出来的文档自然没有它。倒排表里也找不回原文:那里存的是分词、小写之后的词和位置,标点、大小写、被过滤掉的词都没了。

高亮那一行同理。SnippetGenerator::snippet_from_doc 从传入的文档里拼出这个字段的文本,文档里没有这个字段,文本就是空串,空串上选不出片段。它不检查字段是否 STORED,所以没有错误。

修法

给需要取回或高亮的字段加上 STORED:

let body = sb.add_text_field("body", TEXT | STORED);

schema 建好以后不能改(设计取舍),已有索引里的文档也没有原文可补,只能用新 schema 建一个新索引,从你自己的数据源重新写一遍。

不是每个字段都该存。STORED 的代价是 .store 变大、取文档时要解压更多字节。常见的做法是:

  • 只用来搜索、从不展示的字段,只 TEXT;
  • 要展示或高亮的字段,TEXT | STORED;
  • 结果页只需要一个 id、正文另有存储的,id 字段 STRING | STORED,取到 id 再回自己的库里查。

怎么检查

启动时(或写 schema 的单元测试里)把每个字段的属性打出来,对照你的取回和高亮代码:

for (_, entry) in schema.fields() {
  println!("check {:<5} indexed={} stored={}", entry.name(), entry.is_indexed(), entry.is_stored());
}
check title indexed=true stored=true
check body  indexed=true stored=false

代码里凡是 get_first(field) 或 SnippetGenerator::create(.., field) 用到的字段,FieldEntry::is_stored 都应该是 true;可以写成一条断言。对已经上线的索引,index.schema() 读的就是 meta.json 里的 schema,同样能查。

小结

  • TEXT 只负责能搜到,不负责能取回;取回和高亮要 STORED。
  • 没存的字段不写进 .store,searcher.doc 取回的文档里没有它,get_first 是 None,snippet_from_doc 给出空串,全程没有错误。
  • schema 不能改,补 STORED 要重建索引;启动时或测试里用 is_stored() 核对要取回的字段。

💬 关联讨论

发起讨论
加载中...