
在 AI 检索系统中,Embedding 可以帮助我们快速找到语义相近的内容。但从 TEXT_MATCH、BM25 等词法检索,到 reranking、答案生成、高亮和审计,系统仍然需要保存并访问原始文本。
TEXT 可以用来存储文档正文、RAG chunk、日志、源代码、对话等较长的文本内容。 同一个 TEXT 字段可以与 analyzer、text_match、BM25、稠密向量以及混合搜索配合使用。 较短的文本仍然直接存放在 Segment 中;较大的文本则转为 LOB 文件,并在 Segment 中保存引用。 Compaction 时,如果现有 LOB 文件仍然值得保留,可以直接复用,而不必重新写入全部正文。 DataCoord 中的 LOB GC 会根据 Manifest 的引用关系和安全时间窗口,清理已经失去引用的孤儿文件。
schema.add_field(field_name="content",datatype=DataType.TEXT,nullable=True,enable_analyzer=True,enable_match=True,analyzer_params={"tokenizer": "standard"},)
WAL -> StreamingNode write buffer -> object storagesmall TEXT value -> inline byteslarge TEXT value -> LOB file + reference
segment manifestnormal column groups:pk, vector, scalar fieldsTEXT reference column:row 1 -> inline bytesrow 2 -> LOB ref(file_id, row_offset)row 3 -> LOB ref(file_id, row_offset)partition-level lobs/{field_id}/_data/{file_id}.vx
数据落盘以后,Segment 仍然会发生删除和 Compaction。如果每次 Compaction 又把所有 LOB 重写一遍,会造成较大的I/O放大。
假设一个 Segment 中保存了大量文档。现在其中 5% 的记录被删除,需要进行 Compaction。
对于主键、标量和向量数据,生成一个新的 Segment 很正常。但对于几百 MB 甚至几 GB 的正文来说,如果剩下 95% 的内容实际上完全没有变化,再复制一次就没有太大意义。
LOB 将文本 Payload 从普通 Segment 文件中拆出去之后,Milvus 就可以实现让新的 Segment 可以被重新组织引用,而原来的 LOB 文件继续使用。
当然,并不是所有情况下都值得复用。如果一个 LOB 文件中绝大多数记录已经被删除,继续保留整个文件反而会浪费空间。
因此, Milvus 引入了一个重要指标:hole ratio。
hole_ratio = unused_lob_rows_or_bytes / total_lob_rows_or_bytesREUSE_ALL:如果 hole ratio 较低,继续使用已有 LOB 文件,只复制或更新引用。 REWRITE_ALL:如果 hole ratio 较高,只把仍然有效的文本重新写入新的 LOB 文件。 SKIP:对于仅处理删除数据的 L0 compaction,不需要移动 LOB 文件。
if reference is inline:return inline byteselse:decode file_id + row_offsetread from LOB Vortex file
扫描仍然有效的 Segment Manifest 和引用 Binlog。 构建当前可达的 LOB file_id 集合。 枚举对象存储中的 LOB 文件。 删除已经没有引用、并且早于安全时间窗口的文件。
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)schema.add_field(field_name="content", datatype=DataType.TEXT, nullable=True)schema.add_field(field_name="embedding", datatype=DataType.FLOAT_VECTOR, dim=768)
client.insert(collection_name="docs",data=[{"id": 1,"content": "A long document body ...","embedding": embedding,}],)
client.search(collection_name="docs",data=[query_embedding],anns_field="embedding",limit=10,output_fields=["content"],)
client.query(collection_name="docs",filter='text_match(content, "vector database")',output_fields=["id", "content"],)
schema.add_field(field_name="content_sparse", datatype=DataType.SPARSE_FLOAT_VECTOR)schema.add_function(Function(name="content_bm25",function_type=FunctionType.BM25,input_field_names=["content"],output_field_names=["content_sparse"],))
index_params.add_index(field_name="content_sparse",index_type="SPARSE_INVERTED_INDEX",metric_type="BM25",)
client.search(collection_name="docs",data=["vector database full text search"],anns_field="content_sparse",search_params={"metric_type": "BM25"},limit=10,output_fields=["content"],)


张露
Staff Software Engineer at Zilliz
阅读推荐官宣开源|Milvus 3.0 正式发布Milvus 3.0 开源解读之backfill|亿级 AI 数据,如何做高效特征回填Milvus 3.0 开源解读|从Kafka、Pulsar到Woodpecker,数据库如何低延迟、低成本的写入Milvus 3.0 开源解读之Manifest|AI 数据管理,应该彻底放弃文件中心架构Milvus 3.0 开源解读之,数据库原生聚合排序如何取代应用侧Pandas胶水代码Milvus 3.0开源解读之Regex |从=~到NGRAM,如何选择最优性价比的正则过滤Milvus 3.0 开源解读之Snapshot|无需复制embedding数据的Milvus Collection视图Milvus 3.0 开源解读|自定义词典如何优化BM25 与Text Match 的专业词理解能力


