索引功能:https://github.com/lancedb/lance/pull/4578
第一章:
为什么Lance
需要分布式全文索引?
Lance作为一种现代化的列式数据格式,专门针对机器学习和AI应用进行了优化。
它不仅支持高性能的随机访问,还原生支持向量搜索、全文搜索等多种索引类型。然而,随着数据规模持续增长,单机的全文索引构建成为了性能瓶颈。
在Lance的全文搜索生产业务场景中,面临以下挑战:
1. 数据规模爆炸性增长:现代应用所需处理的文本数据量已从GB级别增长至TB甚至PB级别。
2. 单机性能限制:传统的单机索引构建无法充分利用现代分布式计算资源。
3. 索引构建时间过长:大规模数据集的索引构建可能耗时数小时甚至数天。
4. 内存资源不足:单机内存限制导致大规模数据集无法一次性加载和处理。
这便是分布式FTS索引功能诞生的背景。
一、全文索引介绍
Lance全文搜索将复杂的索引结构巧妙地组织成三个核心文件,就像一个高效的图书馆管理系统:
从上图可以清晰地看到三个核心文件的分工协作:
📖 tokens.lance-词汇表文件:存储所有出现过的词汇,就像字典的索引页,支持快速词汇查找。
🔍 inverted.lance-倒排索引文件:记录每个词在哪些文档中,就像书籍的主题索引,核心的搜索引擎。
📊 docs.lance-文档元数据:记录每个文档的基本信息,就像图书馆的登记册,支持相关性评分。
二、构建与查询流程
1. 索引构建流程
• 输入文本数据 → 分词 → 构建倒排列表 → 压缩存储
• 多线程并行处理,每个线程独立构建部分索引
• 最终合并为完整的倒排索引
2. 搜索查询流程
• 查询文本 → 分词 → 获取倒排列表 → Block Max WAND搜索 → 返回结果
• 利用块级别的上界分数进行高效的文档跳过
• 动态调整阈值,进一步优化搜索效率
BM25算法就像一个智能的"相关性评委",它会综合考虑多个因素来判断文档与查询的匹配程度:
BM25的评分逻辑
🎯 词频重要性:一个词在文档中出现得越多,文档越相关。
例如,查询 “人工智能”,如果一篇文档多次提及该词,其相关性得分会相对较高。
📏 文档长度标准化:长文档不应该仅仅因为长就得高分。这是为了避免因文档长度因素干扰相关性判断。
例如,一篇长篇报告可能包含更多词汇,但并非与查询紧密相关,通过标准化处理,确保长文档和短文档在相关性评估上处于公平竞争环境。
📈 逆文档频率:罕见词汇比常见词汇更有区分度。常见词汇在许多文档中都可能出现,对区分文档相关性的作用较小;而罕见词汇出现频率低,一旦在某文档中出现,可能表明该文档与查询具有较高相关性。
例如,特定领域的专业术语在普通文档中很少出现,若某文档包含该术语,在相关领域的搜索中就更具相关性。
⚖️ 平衡策略:综合多个因素,给出公平的相关性分数。BM25 算法给出公平的相关性分数,从而为用户提供最符合需求的搜索结果排序。
3. 索引维护流程
• 新数据到达 → 构建新分区 → 基于大小合并分区 → 更新索引元数据
• 控制分区大小,优化查询性能和内存使用
索引的初次构建时间,往往是耗时最长。我们本次PR重点关注的是分布式索引构建流程,致力于将单机构建全文索引分布式化。从而大大缩小时间,提升海量规模场景下,实现全文索引的生产可用。
第二章:
分布式全文索引核心原理
在本章节中,我们将介绍分布式全文索引的几个重要的核心原理。
一、前置工作
原本的单机全文搜索就像一个人搬家,而分布式全文索引则像一支专业搬家团队。
什么是分布式全文索引?简单来说,就是把一个巨大的"图书馆目录"分成很多小册子,让多个"图书管理员"同时工作,最后把结果合并起来。这样找书的速度就快了很多倍!
来自LanceDB的 Bubblecal (https://github.com/bubblecal),给 Lance的 Inverted Index 加上了 partition 的能力,这个是本次分布式全文索引实现的基础。
每个partition有大小控制,使不同节点可以在各自partition上并行构建。利用partition的能力,我们可以实现各个partition在不同的机器上处理,从而达到水平可扩展性的目的。
二、化整为零
分布式 FTS 索引基于 MapReduce 思想,采用 “化整为零” 策略:
分片与并行:将数据集分割为多个片段(fragment),多 Worker 并行处理,调用 execute_uncommitted() 方法独立构建索引元数据(不立即提交),避免资源冲突。
原理图如下:
从上图可以看出,整个过程就像一个高效的流水线工厂:
• 输入阶段:海量文档等待处理
• 分片阶段:智能切分,让每个Worker都有合适的工作量
• 并行阶段:多个Worker同时工作,效率倍增
• 合并阶段:将所有成果统一整合
• 完成阶段:得到完整的分布式索引
根据上述原理图,我们在lance的底层中,对应设计了核心的 execute_uncommitted() 和 merge_index_metadata() 方法,实现了"先构建,后合并"的策略。整个分布式FTS索引构建,分为四个关键阶段:
四个阶段详解:
1. Split阶段:将大数据集智能分割成多个fragment,每个fragment可以独立处理
2. Parallel阶段:多个worker并行调用execute_uncommitted()在各自的fragment上构建索引
3. Merge阶段:使用merge_index_metadata()合并所有worker的结果,处理词汇表合并和统计信息聚合
4. Commit阶段:将最终的索引元数据原子性地提交到数据集

三、分区 ID 唯一性
原本的单机建全文索引时,所有worker都在1个节点,worker之间用一个 atomicint 来维护 partition id, 靠多线程运行,但这样其实不好分布式化。这在多机环境会引入跨节点竞争与冲突。
为此引入“高位掩码 + 本地计数”的生成策略:高 32 位写入 fragment 标识,低 32 位使用本地自增计数,天然消除跨 worker 冲突;合并阶段再将分区重排为连续的 0..N-1,与单机构建保持一致。
- 掩码思路:运行时让分区 ID 携带 fragment 的高位掩码,无需中心协调,可跨 worker 并行落盘;代价是对上层暴露的分区临时 ID 不连续,需要后续“归一化”。
- 合并重排:协调端收集临时分区并排序去重,映射为连续 ID,并对 part_*.lance 两阶段安全改名,最终结构与单机一致。
ID 生成公式非常直接:
• 高 32 位 = fragment_id << 32;
• 低 32 位 = 本地原子计数器 fetch_add(1) 的结果。
• 最终 ID = 高位 | 低位。
这带来三点重要性质:
• 不同partition的分区id必然高位不同,因此跨分片的冲突被彻底消除;
• 同一partition内,同一 worker 的 flush 次序天然有不同的低位,避免自冲突;
合并阶段会把所有分区重新映射为 0..N-1,彻底去掉带分片戳的高位,使最终索引与单机产物结构一致。
fragment_ids的语义:
• None或[]:单节点模式,处理所有fragments
• [0,1,2]:分布式模式,当前worker处理指定的fragments
fragment掩码的计算工作原理:
orker标识机制
通过将fragment_id左移32位作为fragment_mask,我们实现了:
• 唯一性:每个worker都有唯一的标识符
• 分区隔离:不同worker的分区ID不会冲突
• 简单高效:计算开销极小,逻辑清晰
示例:
- Worker 1处理fragment_ids=[0] → id_offset = 0 << 32 = 0x0000000000000000
- Worker 2处理fragment_ids=[1] → id_offset = 1 << 32 = 0x0000000100000000
- Worker 3处理fragment_ids=[2] → id_offset = 2 << 32 = 0x0000000200000000
生成示例(每个 worker 各生成 2 个临时分区,若处理多个 fragment,取首个 fragment 计算掩码):
除了 metadata.lance 本身,在生产过程中,还会生成对应的 part_i_tokens.lance、part_i_inverted.lance、part_i_docs.lance 等分区级别的索引文件。
由此可见,在该过程中,不同的 partition 处理不同的 fragmentids,处理过程中不会相互干扰。
当分区本身需要合并时,同一 node 上的 partition 可根据 FTS 目标大小进行小分区合并。不同节点的分区不会合并,以确保生成分区的过程尽可能按 node 节点并行进行。
四、核心接口实现总结
从实现层面看,Lance通过两个巧妙设计的方法实现了分布式索引构建。通过精心设计的 execute_uncommitted()和merge_index_metadata() 方法,实现了"先构建,后合并"的策略
1. execute_uncommitted():在本地 fragment 上构建索引,返回未提交的索引元数据,便于后续集中合并。
execute_uncommitted()方法的设计体现了"分离关注点"的原则:
• 构建阶段专注于索引质量和性能
• 提交阶段专注于一致性和持久化
• 两个阶段可以独立优化和测试
这个方法是分布式索引构建的核心,它允许在不立即提交到数据集的情况下创建索引元数据:
这个方法的设计哲学是"分离构建和提交",使得:
• 多个worker可以并行在不同的数据片段上构建索引
• 每个worker独立工作,不会相互干扰
• 索引元数据可以在后续步骤中进行合并
2. merge_index_metadata():合并所有 worker 的临时元数据,统一词汇表与统计信息,并输出标准化的 metadata。
新增 merge_metadata_files:收集所有临时元数据里的分区 ID,排序去重后映射为 0..N-1,并对 part_*.lance 做两阶段安全改名,最终写出标准的 metadata.lance。
Python 端暴露 LanceDataset.merge_index_metadata(),帮助协调端在事务外汇合各 worker 的产物;同时补充了分布式与单机一致性的测试套件。
3. commit提交:通过前两步合并后的各 Worker 的元数据,整合词汇表、统计信息,最终调用commit接口原子化提交至数据集,保障一致性。
第三章:
Daft + Lance分布式构建
直接使用上述的接口,创建分布式索引,需要人为处理分片和分布式到各个节点运行,使用门槛比较高。借助Daft引擎,我们可以简化分布式构建索引的步骤。
Daft是全球首个原生支持多模态数据处理的分布式计算引擎。它提供“统一多模态数据抽象 + Python/Rust原生API + 分布式执行引擎”三位一体的解决方案,旨在成为多模态数据处理领域的统一标准,如同SQL之于结构化数据。
Daft初衷是为了解决自动驾驶研发中处理海量非结构化数据(如3D点云、视频)的难题。
Daft提供SQL和DataFrame两种API接口,支持单机或分布式执行。其架构围绕“高效、灵活、Python原生”三大目标设计,核心组件包括:
1. 多语言协同层:底层采用Rust实现高性能计算,上层通过Python绑定提供友好的API。
2. 内存计算引擎:基于Apache Arrow内存格式,支持零拷贝数据传输,减少序列化/反序列化开销。
3. 分布式执行框架:集成Ray分布式计算引擎,支持弹性扩缩容,可处理PB级数据。
4. 智能查询优化器:内置优化器,通过谓词下推等技术动态调整执行计划,提升效率。
在分布式索引创建工作中,Daft 可以负责资源调度与任务分发,通过 UDF 并行调用 Lance 接口,用户仅需调用create_scalar_index即可完成分布式索引构建,降低使用门槛。
一、为什么是 Lance + Daft
1. Lance 负责“存与索引”:数据以列式组织,索引作为数据集的版本化变更提交;借助 version和 commit 语义实现快照与回滚。lance在内部实现中使用了 pyarrow 类型系统做列类型校验,并通过 Lance 原生 API 完成索引元数据合并与原子提交。
2. Daft 负责“分布式执行”:通过 UDF 与 with_concurrency(num_workers) 把索引构建任务分发到多个 worker,并统一收集结果与错误,形成端到端的流水线节点。
3. 存算解耦:计算侧用 Daft 组织并发批次,存储侧用 LanceDataset 提供索引分片构建与索引提交能力;两者边界清晰、责任单一,单机与分布式切换只需调整入口函数参数而不改核心存储格式。
二、Daft connector架构图

在上面的架构中,Daft承担了资源于调度的作用,在daft中,通过UDF并行执行上述的lance底层接口,成功实现了接口的高效调用。
用户层面仅需使用一个daft的create_scalar_index接口,剩下的事情即可交给daft自动完成,最终实现了如下收益:
1. 水平扩展能力:可以利用多台机器的计算资源并行构建索引,理论上支持无限扩展
2. 内存优化:每个worker只需要处理数据集的一部分,大大降低内存需求
3. 容错机制:单个worker失败不会影响整个索引构建过程,支持自动重试
4. 灵活的资源配置:可以根据数据规模动态调整worker数量,优化成本效益
5. 开发友好:API 简洁,学习成本低。
根据实际测试,分布式FTS索引在大规模数据集上表现出色,在实际测试一个5亿条长文本的数据时(总计7TB的lance数据大小),使用分布式全文索引并发构建时,整体构建时间能够根据资源的横向扩展,线性提升。
在资源水平提升8倍的情况下,在未应用任何参数优化的情况下,可以把对象存储的分布式索引的时间从19小时降低到2小时,在优化CPU和内存利用率后,时间还可以进一步优化。
第四章:
总结与展望
分布式FTS索引功能的实现标志着Lance和LanceDB在大规模数据处理领域的重要进步。通过Lance PR 4578引入的核心分布式索引能力,结合ray/daft提供的集成,我们实现了从单机到集群的平滑扩展。
这一功能的核心价值在于:
1. 突破性能瓶颈:将索引构建时间从小时级降低到分钟级
2. 降低资源门槛:通过分布式处理,大幅降低内存和计算资源需求
3. 提供企业级能力:支持TB/PB级数据的全文搜索,满足企业级应用需求
4. 保持开发友好:API简洁易用,学习成本低
随着 AI 与机器学习应用的持续深化,大规模文本数据处理的需求愈发迫切。分布式 FTS 索引为开发者提供了强大灵活的工具,助力轻松应对 TB 乃至 PB 级文本数据挑战。
未来,我们期待见证更多基于这一功能的创新实践 —— 从智能客服到知识图谱构建,从文档检索到多模态搜索,分布式 FTS 索引或将成为下一代 AI 应用的重要基础设施。
为持续提升能力,我们已规划以下方向:
性能优化:
1. 进一步优化内存使用,支持更大规模的数据集
2. 分布式搜索加速,并发提高分片索引的查询效率
3. 改进负载均衡算法,提高资源利用率
4. 支持分布式增量索引更新
技术的进步离不开开发者社区的共创。我们诚挚邀请更多开发者加入 Lance 与 Daft 的中文社区,分享实践经验、提出需求建议,共同推动分布式 FTS 索引功能的完善,携手探索 AI 时代数据处理的更多可能!
推荐阅读
点击阅读原文,跳转LanceDB GitHub


