跳到主要内容

多模态数据湖的最后一公里:Curvine 让 LanceDB 向量检索快 5 倍

· 阅读需 11 分钟

LanceDB × Curvine:查询速度提升 5 倍

导语

多模态数据湖这两年成了 AI 基础设施的标准答案:图片、视频、文本、Embedding 全部落在对象存储上,用 Lance / Parquet 这类列式格式统一管理,再用 LanceDB 这样的引擎直接在湖上做向量检索、全文检索和混合检索。存算分离,容量无限,成本极低——听起来近乎完美。

但真正把在线检索业务搬上去的团队都踩过同一个坑:对象存储的容量和带宽都不缺,缺的是延迟。一次向量召回背后是几十次、几百次小粒度随机读,每一次都要付一遍对象存储的首字节延迟,链路越长,尾延迟越难看。

我们在阿里云上做了一组实测:同样的 LanceDB、同样的 100 万条 1536 维数据集、同样的 benchmark 脚本,只把底层存储从纯 OSS 换成 Curvine(Rust 编写的高性能分布式缓存系统),结果是向量检索 p50 从 11 ms 降到 2 ms,QPS 从 69 涨到 333,p99 从 62 ms 降到 10 ms。下面是完整的数据和分析,包括那个“提升不明显”的场景——我们也一并给出原因。

一、瓶颈到底在哪:向量检索的 I/O 画像

在湖上做向量检索,一次查询大致要走这几步:

  1. 读索引元数据(IVF 的中心点、分区偏移等);
  2. nprobes 命中的分区,读取若干个索引分片;
  3. 拿到候选 row ID 后,回表读取原始列(向量、titletext 等),取回真实值做精排;
  4. 全文检索还要额外读倒排结构,混合检索则是两条链路都走一遍,再做 RRF 重排。

这条链路的特征是小粒度、高扇出、强随机。对象存储在吞吐上很能打,但每次 range read 都要付一次网络往返加服务端寻址的固定开销,通常在几毫秒到十几毫秒量级。一次查询串起几十个这样的读,延迟就被固定开销主导了——这时候再加机器、加带宽都没用,因为瓶颈不是带宽,而是每次 I/O 的固有延迟。

Curvine 的定位正好切在这里:它是一层分布式缓存文件系统,把热数据下沉到 Worker 节点的内存和本地 NVMe / ESSD 上,客户端通过原生 RPC、FUSE 或 Hadoop 兼容接口访问。数据依然由对象存储做长期归属,但读路径上的每一次随机读,从“跨网络访问对象存储”变成“访问同可用区的缓存节点 + 本地盘”,单次 I/O 延迟直接降低一个数量级。

二、测试环境与方法

为了避免“自己写 benchmark 自己赢”的嫌疑,我们直接用了 LanceDB 官方的 benchmark 套件。

配置
LanceDB 版本0.34.0
BenchmarkLanceDB 官方 lancedb-cloud-benchmarks
数据集KShivendu/dbpedia-entities-openai-1M(约 100 万条,1536 维)
查询次数每组 10000 次
nprobes / limit均取默认值
并发单进程(query_processes=1

集群部署在阿里云:

  • Curvine 测试集群: 1 台 Master + 1 台 Worker,均为 ecs.r8a.8xlarge(32 vCPU / 247 GB 内存),Worker 额外挂载 1.3 TB ESSD 云盘;
  • 查询节点: ecs.r8a.4xlarge(16 vCPU / 123 GB 内存)。

注意,这是一个单 Worker 的最小集群,没有做任何多副本或多节点并行读的优化——也就是说,下面的数字是 Curvine 的下限而非上限。

四类查询覆盖了多模态检索的典型形态:

  • vector 随机 1536 维向量检索;
  • fts 从内置词表随机抽词,对 title 做全文检索;
  • hybrid 随机向量 + 随机文本,RRF 重排;
  • vector_with_filter 随机向量 + text LIKE '%词%',且 prefilter=True

三种存储路径是这次对比的核心变量:

  • oss 纯对象存储,数据和索引都在 OSS 上;
  • curvine-index 数据仍在 OSS URI,只把索引目录(_indices)放进 Curvine 缓存;
  • curvine 全程使用 curvine://,数据与索引都走缓存。

第二种模式值得单独强调:它只需要改索引的存放路径,对现有数据湖的组织方式几乎零侵入,是很多团队最容易落地的一档。

三、实测数据

延迟单位均为毫秒(ms),QPS 越高越好,延迟分位越低越好。

3.1 Vector:纯向量检索

存储路径QPSp50p90p95p99
oss69.411263462
curvine-index76.99233251
curvine333.323410

全程 Curvine 后,p50 降到 OSS 的 1/5.5,QPS 提升约 4.8 倍。更关键的是,p90 从 26 ms 降到 3 ms(约 8.7 倍),p99 从 62 ms 降到 10 ms——平均值变好只是提升,尾延迟被压平才是能上线的信号。

3.2 FTS:全文检索

存储路径QPSp50p90p95p99
oss31.426536188
curvine-index71.412202537
curvine144.9581315

全文检索是这次收益结构最有意思的一组:只把索引放进 Curvine,QPS 就翻了 2.3 倍,p50 直接砍半。原因不难理解,倒排索引的访问模式比向量索引更碎,对单次 I/O 延迟更敏感,所以“只加速索引”这一档就能吃到大部分收益。全程 Curvine 则进一步把 p50 打到 5 ms,p99 从 88 ms 降到 15 ms。

3.3 Hybrid:向量 + 文本混合检索(RRF 重排)

存储路径QPSp50p90p95p99
oss25.236586690
curvine-index37.924354052
curvine73.513141521

混合检索是多模态场景里最贴近真实业务的一类查询——它同时承担向量和全文两条链路的 I/O 成本,所以在 OSS 上的基线最差(p50 36 ms)。Curvine 把 p50 压到 13 ms(约 2.8 倍),QPS 提升 2.9 倍。

这里最漂亮的是延迟分布:p50 13 ms、p90 14 ms、p95 15 ms、p99 21 ms,几乎是一条平的曲线;而 OSS 上是 36 / 58 / 66 / 90 的持续上扬。对需要串在 RAG 链路里的检索服务来说,可预测的延迟比更低的均值更有价值。

3.4 Vector with Filter:带过滤的向量检索

存储路径QPSp50p90p95p99
oss2.6377401415456
curvine-index2.6378401412446
curvine3.9249257265450

这组我们如实放出来:curvine-index 相比 OSS 几乎没有收益,全程 Curvine 也只有约 1.5 倍改善。

原因是这类查询的耗时结构完全不同。prefilter=Truetext LIKE '%词%' 意味着要先对文本列做全量扫描式匹配,再把结果喂给向量检索。整体 p50 在 250~380 ms 量级,瓶颈落在过滤与扫描的计算路径上,而不是索引 I/O——缓存能优化的部分只占总耗时的一小块,自然优化不出 5 倍。

这个结果反而印证了前三组的可信度:Curvine 加速的是 I/O 延迟,凡是 I/O 主导的场景收益就大,凡是 CPU / 扫描主导的场景收益就有限。想解决这一类查询,方向应该是给过滤字段建标量索引,或者用倒排替代 LIKE,而不是继续加缓存。

四、汇总:相对 OSS 的提升

查询类型curvine-index vs. osscurvine vs. oss
Vectorp50:11 → 9 ms(约 1.1x);QPS:69 → 77p50:11 → 2 ms(约 5.5x);QPS:69 → 333
FTSp50:26 → 12 ms(约 2.2x);QPS:31 → 71p50:26 → 5 ms(约 5.2x);QPS:31 → 145
Hybridp50:36 → 24 ms(约 1.5x);QPS:25 → 38p50:36 → 13 ms(约 2.8x);QPS:25 → 74
Vector with filter性能相当p50:377 → 249 ms(约 1.5x);QPS:2.6 → 3.9

四条结论:

  1. 全程 Curvine 在所有查询类型上整体最优。 Vector 与 FTS 收益最明显,p50 约降至 OSS 的 1/5,QPS 提升 4~5 倍;
  2. 只把索引放进 Curvine 就有中等收益。 FTS、Hybrid 尤为明显(1.5~2.2 倍),Vector 有小幅改善;
  3. Vector with filter 是唯一的例外。 瓶颈在过滤 / 扫描路径而非索引 I/O,需要用索引设计而非缓存来解决;
  4. 延迟稳定性的改善比平均值更显著。 Curvine 路径下 p90 / p99 全面走低,混合检索几乎打平成一条直线。

五、怎么落地:两档接入策略

从上面的数据可以推出一条很实用的路径。

第一档:只缓存索引(低成本试水)

保持数据湖的 OSS 布局不动,只把 Lance 的 _indices 目录指向 Curvine。改动量极小,不需要迁移数据,也不影响原有的写入和归档流程,就能在全文与混合检索上拿到 1.5~2.2 倍收益。缓存容量只需覆盖索引体积,成本很低。

这种方式适合先验证效果,或者索引热数据远小于全量数据的场景。

第二档:全程 Curvine(收益最大化)

把数据集整体挂到 curvine:// 下,读路径完全走缓存。这一档能拿到 5 倍量级的延迟收益和平坦的尾延迟,代价是缓存容量要能装下工作集。

对在线检索服务、RAG 召回这类延迟敏感的链路,这是我们推荐的形态。

Curvine 本身提供多种接入方式,不需要改应用代码就能落地。例如通过 FUSE 挂载,应用侧当本地目录使用:

bin/curvine-fuse.sh start
ls /curvine-fuse

Curvine 也支持 Rust 原生 API 和 Hadoop 兼容接口(cv://),可以按现有技术栈选择:

Configuration conf = new Configuration();
conf.set("fs.cv.impl", "io.curvine.CurvineFileSystem");
FileSystem fs = FileSystem.get(URI.create("cv://master:8995"), conf);

六、写在最后

多模态数据湖解决了“数据放哪”的问题,但没有解决“读得多快”的问题。对象存储的经济性来自于把数据放远,而在线检索的体验来自于把数据放近——这两件事之间需要一层缓存来调和。

这次测试想说明的其实是一个很朴素的判断:在存算分离架构下,把随机小 I/O 的延迟降下来,往往比换更强的检索引擎、加更多的查询节点更有性价比。同样的 LanceDB、同样的数据、同样的查询节点规格,只换存储路径就能拿到 5 倍延迟收益,而且用的还是只有一个 Worker 的最小集群。

另一个值得记住的细节是那组“没提升”的数据。缓存不是万能药,它精确地作用在 I/O 延迟上;当瓶颈换成扫描和计算,就该换工具了。知道一项技术在哪里不起作用,和知道它在哪里起作用同样重要。

Curvine 已在 GitHub 开源(Apache License 2.0)。除了向量检索加速,它也用于训练数据加速、模型分发、热表加速、大数据 Shuffle 加速与多云缓存等场景。欢迎试用、提 Issue、贡献代码:

测试说明: 本文数据来自阿里云环境实测,LanceDB 0.34.0,dbpedia-entities-openai-1M 数据集,每组 10000 次查询,单进程。测试为单 Master + 单 Worker 最小集群。不同硬件规格、数据规模与并发条件下结果会有差异,建议以自身业务负载复测为准。