免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

Milvus 3.0 开源解读|如何借助原生 TEXT 类型和 LOB 高效管理原始文本

发布日期:2026-08-17 20:45:08 浏览次数: 1661
作者:Zilliz

微信搜一搜,关注“Zilliz”

推荐语

Milvus 3.0 通过原生 TEXT 与 LOB 存储,终结向量数据库+外部存储的解耦架构痛点,让原始文本与索引协同高效管理。
核心内容:
1. 早期解耦架构的问题:数据不一致与链路冗长
2. Milvus 3.0 原生 TEXT 与 LOB 的整合存储方案
3. TEXT 类型支持的混合检索与原子操作优势

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家


在 AI 检索系统中,Embedding 可以帮助我们快速找到语义相近的内容。但从 TEXT_MATCH、BM25 等词法检索,到 reranking、答案生成、高亮和审计,系统仍然需要保存并访问原始文本。

也是因此,如何让原始文本可以和索引一起被存储、检索和管理,也是现代数据库建设中的关键命题。
早期,行业普遍采用向量数据库 + 外部存储的解耦架构来解决这一问题:向量数据库仅存放 Embedding 和简单的 Metadata,而长文档正文、代码段或日志则存放在 S3、MongoDB 或 Elasticsearch 中。
在这种架构中,一旦我们需要更新或删除文档,就必须同时操作外部存储与向量数据库。然而,在分布式网络下,这种方式极易引发数据不一致,导致向量检索命中但取不到正文(或者取错版本)。与此同时,混合检索又进一步放大了这个问题:如果原始文本不在向量数据库内,BM25 倒排索引的构建与全文本过滤就必须依赖外部组件,链路冗长且难以保证原子性。
既然如此,为什么不直接把正文放进数据库中?
原因在于,如果只是简单地将巨量原始文本作为普通字符串列塞进向量数据库,却仍然按照普通内联字符串处理,大 Payload 又会进入内存、flush、compaction 和 I/O 的每一个环节:百 KB 到数MB的长文本会迅速挤爆内存缓冲区,并在数据合并重构(Compaction)时引发极其严重的磁盘与网络写放大。
Milvus 3.0 引入 DataType.TEXT 和 LOB 存储路径,所要解决的,就是这个问题:让长文本像稠密向量、稀疏向量和标量字段一样,成为 Milvus 中的一等公民,并纳入对象存储的完整生命周期管理。
具体来说
  • TEXT 可以用来存储文档正文、RAG chunk、日志、源代码、对话等较长的文本内容。
  • 同一个 TEXT 字段可以与 analyzer、text_match、BM25、稠密向量以及混合搜索配合使用。
  • 较短的文本仍然直接存放在 Segment 中;较大的文本则转为 LOB 文件,并在 Segment 中保存引用。
  • Compaction 时,如果现有 LOB 文件仍然值得保留,可以直接复用,而不必重新写入全部正文。
  • DataCoord 中的 LOB GC 会根据 Manifest 的引用关系和安全时间窗口,清理已经失去引用的孤儿文件。

01 

为什么不能把TEXT 当成一个更大的 VARCHAR

先区分两个容易混淆的概念。
对于标签、状态、名称、类别、ID 等短元数据,VARCHAR 依然是更合适的选择。
TEXT 则适合面向更长的内容,例如:RAG chunk 文本;文档正文;源代码;日志;客服或支持对话;多语言文本;需要参与全文检索或混合检索的字段。
例如,我们可以这样定义 Schema:
schema.add_field(    field_name="content",    datatype=DataType.TEXT,    nullable=True,    enable_analyzer=True,    enable_match=True,    analyzer_params={"tokenizer": "standard"},)
这里的 content 不仅可以正常写入和返回,还可以参与 analyzer、text_match、BM25 等文本检索能力。
这意味着从数据模型来看,正文终于可以和向量、标量字段放在同一个 Collection 里。
但这马上带来了下一个问题:
为什么 Milvus 还需要为TEXT单独设计 LOB,而不是继续像普通 Segment 列一样保存它?原因主要有四个:
1、Growing Segment 的内存压力会迅速放大
Milvus 要让刚写入的数据立即可查,QueryNode 中的 Growing Segment 就必须能够访问最近写入的数据。
对于普通标量字段,这部分数据通常不大。
但如果一行数据携带几百 KB 甚至数 MB 的正文,情况就完全不同了。此时决定内存占用的可能不再是向量,而是文本 Payload 本身。
2、写入链路可能重复保存同一份大文本
传统写入链路可以简化为:
WAL -> StreamingNode write buffer -> object storage
与此同时,为了查询 Growing Data,QueryNode 本身也需要访问刚写入的数据。
于是,对于大文本来说,一个明显的问题出现了:如果 StreamingNode 为了等待 flush 再保留一份完整 Payload,而查询侧已经持有一份,相当于同一份正文在内存中被重复保存。
3、Compaction 带来的写放大
Compaction 的目的通常是整理 Segment、处理删除记录并重组数据。
但如果长文本和其他字段始终捆绑在同一个 Segment 文件中,哪怕真正发生变化的只有少数几行,Compaction 仍然可能不得不重新搬运大量完全没有变化的正文。
例如,一个 Segment 中有几 GB 的原始文本,而这次 Compaction 实际只删除了其中很少一部分记录。
理想状态下,我们只需要更新这些记录的引用关系。如果正文完全内联,则可能不得不把几 GB 数据重新写一遍。
4、与文本无关的查询也承担了额外I/O
并不是每一次向量搜索都需要返回全文。很多查询只需要向量、主键、标量过滤字段,或者少量指定的输出字段。如果大文本始终内联在 Segment 列文件中,这些文件会因为正文而变得更大,即便一次查询根本不会访问这些内容。
基于以上背景,如何让大文本进入 Milvus,又尽量不干扰原有的向量和标量数据路径,正是我们引入LOB 存储的原因。

02 

混合存储:短文本内联,大文本走 LOB

为了在“读取性能”与“存储开销”之间取得最佳平衡,Milvus 3.0 并没有把所有 TEXT 都无条件拆成独立文件,而是在存储底层通过混合存储布局引入了动态的分层处理机制。
系统会根据文本 Payload 体积自动切换保存策略:如果一段文本本身很短,把它放在 Segment 里直接读取通常更简单;只有当 Payload 足够大时,才把它从主数据路径中拆出去。
(当前默认阈值为64 KiB,具体参数可配置)
small TEXT value -> inline byteslarge TEXT value -> LOB file + reference
也就是说,TEXT 是应用层的数据类型,而 inline 和 LOB 是底层根据 Payload 大小采用的两种存储方式。
从概念上可以理解成:
segment manifest  normal column groups:    pk, vector, scalar fields  TEXT reference column:    row 1 -> inline bytes    row 2 -> LOB ref(file_id, row_offset)    row 3 -> LOB ref(file_id, row_offset)partition-level lobs/  {field_id}/_data/{file_id}.vx
如此一来,小文本继续保持 Inline 状态,直接保存在 Segment 中,因此常规读取仍然轻量;大文本则移到独立的 LOB 文件中,Segment 只保留对应引用。
内存中的 Growing Segment 和写缓冲区不必再常驻巨量 Payload,显著降低了内存压力。同时也为后续的 LOB 文件复用和生命周期管理提供了基础。

03 

Compaction:能复用正文,就不要重新写一遍

数据落盘以后,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_bytes
它描述的是一个 LOB 文件中已经不再使用的数据比例。
根据实际情况,Compaction 可以采取不同策略:
  • REUSE_ALL:如果 hole ratio 较低,继续使用已有 LOB 文件,只复制或更新引用。
  • REWRITE_ALL:如果 hole ratio 较高,只把仍然有效的文本重新写入新的 LOB 文件。
  • SKIP:对于仅处理删除数据的 L0 compaction,不需要移动 LOB 文件。
这样就避免了一个典型的写放大场景:明明只删除了少数几行,却不得不重新写入数 MB,甚至数 GB 的文档内容。

04 

Read Path:对查询层保持透明

把 TEXT 分成 inline 和 LOB 两种布局,会让存储层复杂一些。
但这种复杂度不应该暴露给应用。
在Milvus3.0中,查询执行层仍然是按照行读取字段。至于这一行文本究竟直接存在 Segment 中,还是要从 LOB 中获取,由底层存储层处理:
if reference is inline:    return inline byteselse:    decode file_id + row_offset    read from LOB Vortex file
当一次查询需要读取多条 LOB 数据时,Milvus 还可以先按照 file_id 对引用进行分组。这样,同一个 LOB 文件对应的请求可以复用 Reader,再根据不同的 row_offset 找到对应文本,而不是每读取一行都重新打开文件。
从用户和应用程序的角度看,TEXT 依然可以像普通字段一样出现在搜索结果中,不需要修改原有业务逻辑。

05 

GC:文件要能复用,也要能够安全删除

一旦 LOB 文件可以脱离单个 Segment 独立存在,新的问题也随之出现:
什么时候可以确定一个 LOB 文件已经没有任何人需要,可以安全删除?
这里不能简单地看“最近有没有 Segment 使用它”。这与分布式系统中的提交顺序有关。LOB 文件会先写入对象存储,随后对应的 Segment Manifest 才会提交并变得可见。
因此,如果一个节点恰好在 LOB 文件写入之后、Manifest 提交之前崩溃,对象存储中就可能残留没有任何引用的孤儿文件。
Milvus 在 DataCoord 中提供了专门的 LOB GC 来处理这类情况:
  1. 扫描仍然有效的 Segment Manifest 和引用 Binlog。
  2. 构建当前可达的 LOB file_id 集合。
  3. 枚举对象存储中的 LOB 文件。
  4. 删除已经没有引用、并且早于安全时间窗口的文件。
安全时间窗口的作用,是避免误删仍处于 flush 或 compaction 过程中的文件——这些文件的数据可能已经写入,但元数据尚未来得及提交。
这样,即使发生节点崩溃、重试或部分写入,LOB 文件的生命周期仍然可以被安全管理。

06 

对用户来说,TEXT 仍然是普通字段

前面讨论了很多底层机制,但这些机制有一个共同目标:不要把存储复杂度传递给使用 Milvus 的应用。
应用不需要判断某条 TEXT 是 inline 还是 LOB,也不需要自己创建、维护或者删除 LOB 文件。
正常定义字段即可。
存储并返回 TEXT
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"],)
使用 text_match
如果字段启用了对应的 analyzer 和 match 能力,还可以直接进行文本匹配:
client.query(    collection_name="docs",    filter='text_match(content, "vector database")',    output_fields=["id", "content"],)
使用 BM25 全文搜索
同一个 TEXT 字段也可以作为 BM25 Function 的输入:
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"],)
总而言之,TEXT 是用户的数据类型,LOB 是 Milvus 的存储策略。用户管理的是文本字段,而不是 LOB 文件。

07 

从内联字符串到 TEXT + LOB,到底改变了什么?

回到文章开头的问题。Milvus 过去并非完全不能保存字符串。只不过,我们还需要考虑,当字符串变成长文档之后,如何避免它一路放大 Growing Data、写缓冲区、Compaction 和对象存储的成本。
TEXT + LOB 的设计,本质上是把大文本 Payload从 Segment 主数据路径中拆出来,同时又不改变上层的数据模型。
对很多需要混合检索的系统来说,它意味着应用不再需要为了不同的检索方式,把同一份文本拆到多套存储和索引系统中分别维护。这样一来,向量、文本和 Metadata 可以围绕同一条数据完成写入、更新、查询和删除,整个检索链路也会更简单。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅