多模态数据湖的最后一公里:Curvine 让 LanceDB 向量检索快 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 画像
在湖上做向量检索,一次查询大致要走这几步:
- 读索引元数据(IVF 的中心点、分区偏移等);
- 按
nprobes命中的分区,读取若干个索引分片; - 拿到候选 row ID 后,回表读取原始列(向量、
title、text等),取回真实值做精排; - 全文检索还要额外读倒排结构,混合检索则是两条链路都走一遍,再做 RRF 重排。
这条链路的特征是小粒度、高扇出、强随机。对象存储在吞吐上很能打,但每次 range read 都要付一次网络往返加服务端寻址的固定开销,通常在几毫秒到十几毫秒量级。一次查询串起几十个这样的读,延迟就被固定开销主导了——这时候再加机器、加带宽都没用,因为瓶颈不是带宽,而是每次 I/O 的固有延迟。
Curvine 的定位正好切在这里:它是一层分布式缓存文件系统,把热数据下沉到 Worker 节点的内存和本地 NVMe / ESSD 上,客户端通过原生 RPC、FUSE 或 Hadoop 兼容接口访问。数据依然由对象存储做长期归属,但读路径上的每一次随机读,从“跨网络访问对象存储”变成“访问同可用区的缓存节点 + 本地盘”,单次 I/O 延迟直接降低一个数量级。
二、测试环境与方法
为了避免“自己写 benchmark 自己赢”的嫌疑,我们直接用了 LanceDB 官方的 benchmark 套件。
| 项 | 配置 |
|---|---|
| LanceDB 版本 | 0.34.0 |
| Benchmark | LanceDB 官方 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:纯向量检索
| 存储路径 | QPS | p50 | p90 | p95 | p99 |
|---|---|---|---|---|---|
oss | 69.4 | 11 | 26 | 34 | 62 |
curvine-index | 76.9 | 9 | 23 | 32 | 51 |
curvine | 333.3 | 2 | 3 | 4 | 10 |
全程 Curvine 后,p50 降到 OSS 的 1/5.5,QPS 提升约 4.8 倍。更关键的是,p90 从 26 ms 降到 3 ms(约 8.7 倍),p99 从 62 ms 降到 10 ms——平均值变好只是提升,尾延迟被压平才是能上线的信号。
3.2 FTS:全文检索
| 存储路径 | QPS | p50 | p90 | p95 | p99 |
|---|---|---|---|---|---|
oss | 31.4 | 26 | 53 | 61 | 88 |
curvine-index | 71.4 | 12 | 20 | 25 | 37 |
curvine | 144.9 | 5 | 8 | 13 | 15 |
全文检索是这次收益结构最有意思的一组:只把索引放进 Curvine,QPS 就翻了 2.3 倍,p50 直接砍半。原因不难理解,倒排索引的访问模式比向量索引更碎,对单次 I/O 延迟更敏感,所以“只加速索引”这一档就能吃到大部分收益。全程 Curvine 则进一步把 p50 打到 5 ms,p99 从 88 ms 降到 15 ms。
3.3 Hybrid:向量 + 文本混合检索(RRF 重排)
| 存储路径 | QPS | p50 | p90 | p95 | p99 |
|---|---|---|---|---|---|
oss | 25.2 | 36 | 58 | 66 | 90 |
curvine-index | 37.9 | 24 | 35 | 40 | 52 |
curvine | 73.5 | 13 | 14 | 15 | 21 |
混合检索是多模态场景里最贴近真实业务的一类查询——它同时承担向量和全文两条链路的 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:带过滤的向量检索
| 存储路径 | QPS | p50 | p90 | p95 | p99 |
|---|---|---|---|---|---|
oss | 2.6 | 377 | 401 | 415 | 456 |
curvine-index | 2.6 | 378 | 401 | 412 | 446 |
curvine | 3.9 | 249 | 257 | 265 | 450 |
这组我们如实放出来:curvine-index 相比 OSS 几乎没有收益,全程 Curvine 也只有约 1.5 倍改善。
原因是这类查询的耗时结构完全不同。prefilter=True 加 text LIKE '%词%' 意味着要先对文本列做全量扫描式匹配,再把结果喂给向量检索。整体 p50 在 250~380 ms 量级,瓶颈落在过滤与扫描的计算路径上,而不是索引 I/O——缓存能优化的部分只占总耗时的一小块,自然优化不出 5 倍。
这个结果反而印证了前三组的可信度:Curvine 加速的是 I/O 延迟,凡是 I/O 主导的场景收益就大,凡是 CPU / 扫描主导的场景收益就有限。想解决这一类查询,方向应该是给过滤字段建标量索引,或者用倒排替代 LIKE,而不是继续加缓存。
四、汇总:相对 OSS 的提升
| 查询类型 | curvine-index vs. oss | curvine vs. oss |
|---|---|---|
| Vector | p50:11 → 9 ms(约 1.1x);QPS:69 → 77 | p50:11 → 2 ms(约 5.5x);QPS:69 → 333 |
| FTS | p50:26 → 12 ms(约 2.2x);QPS:31 → 71 | p50:26 → 5 ms(约 5.2x);QPS:31 → 145 |
| Hybrid | p50:36 → 24 ms(约 1.5x);QPS:25 → 38 | p50:36 → 13 ms(约 2.8x);QPS:25 → 74 |
| Vector with filter | 性能相当 | p50:377 → 249 ms(约 1.5x);QPS:2.6 → 3.9 |
四条结论:
- 全程 Curvine 在所有查询类型上整体最优。 Vector 与 FTS 收益最明显,p50 约降至 OSS 的 1/5,QPS 提升 4~5 倍;
- 只把索引放进 Curvine 就有中等收益。 FTS、Hybrid 尤为明显(1.5~2.2 倍),Vector 有小幅改善;
- Vector with filter 是唯一的例外。 瓶颈在过滤 / 扫描路径而非索引 I/O,需要用索引设计而非缓存来解决;
- 延迟稳定性的改善比平均值更显著。 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、贡献代码:
- 项目地址: github.com/CurvineIO/curvine
- 官方文档: curvineio.github.io
- 快速入门: 部署 Curvine
测试说明: 本文数据来自阿里云环境实测,LanceDB 0.34.0,
dbpedia-entities-openai-1M数据集,每组 10000 次查询,单进程。测试为单 Master + 单 Worker 最小集群。不同硬件规格、数据规模与并发条件下结果会有差异,建议以自身业务负载复测为准。