8GB 内存建 500GB 向量索引,DuckDB 新扩展 DuckIR
在 DuckDB 里做向量搜索的人,多半被官方 vss 扩展的三件事折磨过:索引建得慢、删几行数据就得手动压缩、重启后第一查询要等十秒。上周,DuckDB 诞生的那个实验室交出了自己的答案:DuckIR,一个把向量索引当成数据库一等公民来做的扩展。
一句话说清它的位置:向量数据插进表,索引跟着长,不用重建,内存不够也能跑。我本机实测,20 万条向量建索引 0.15 秒,官方 HNSW 同数据要 11.3 秒。
● ● ●
DuckIR 来自 CWI(荷兰国家数学与计算机科学研究中心)的数据库架构组,GitHub 组织是 cwida。DuckDB 本身就是 Hannes Mühleisen 和 Peter Boncz 在这个组做出来的,DuckIR 的维护者名单里就有 Peter Boncz。
也就是说,这不是某个爱好者顺手写的扩展,是 DuckDB 作者团队所在实验室的正式作品。2026 年 11 月 13 日,它作为社区扩展发布首个版本 v0.1.0,MIT 协议,支持 DuckDB v2.0 及以上。
● ● ●
三分钟跑通
安装就是一行:
INSTALL duckir FROM community;
LOAD duckir;
建表、建索引、插数据、查询,全程 SQL:
CREATE TABLE items (
id INTEGER,
cat INTEGER,
embedding FLOAT[64]
);
-- 索引先建好,空表也行
CREATE INDEX items_idx ON items
USING DUCKIR_VSS (embedding)
WITH (metric = 'l2sq');
-- 数据直接插入,索引自动跟着更新
INSERT INTO items SELECT ...;
-- 普通的 ORDER BY + LIMIT,索引自动接管
SELECT id FROM items
WHERE cat = 8
ORDER BY array_distance(embedding, getvariable('q'))
LIMIT 10;
注意查询写法没有任何特殊函数,就是普通的 ORDER BY 距离 LIMIT。优化器认出这个形状后自动换成索引扫描。想确认走没走索引,EXPLAIN 一下看计划里有没有 DUCKIR_VSS 算子。
● ● ●
本机 A/B:同机同数据对比官方 vss
我在 WSL 环境(4 线程)用 20 万条 64 维随机向量,同一份数据分别建 DuckIR 索引和官方 vss 的 HNSW 索引:
全部数字来自我本机实测,测试脚本和数据库文件都在本地可复现。
本机 A/B 实测:DuckIR 对比官方 vss 四项指标
官方在 100 万条 1536 维 OpenAI embedding 上的自报数字是:建索引 11 秒对 250 秒,索引体积 1.78 GB 对 6.29 GB,过滤搜索快 10 到 104 倍,重启后首查询 0.1 秒对 10.6 秒。
两个最实用的差别在维护上。vss 删数据后需要手动跑 PRAGMA hnsw_compact_index,每次 checkpoint 都可能重写整个索引;DuckIR 的索引随表自动更新,插入删除都自维护,我插完 1 万行再删 1000 行后立刻查询,召回没有掉。重启这块差距更明显:vss 的 HNSW 索引重启后要重新加载,DuckIR 常驻内存的只有聚类中心等元数据,查询时按需分页。
● ● ●
索引怎么运作
DuckIR 用的是 IVF 类方案,思路可以拆成三层:
第一层是聚类。向量按行组分给本地索引,每个本地索引跑 k-means 把向量聚成簇(默认每个行组约 480 个簇)。查询时先算查询向量到各簇中心的距离,只探最近的 n_probe 个簇(默认 24)。这是它建索引快的根本原因——HNSW 要为每个点维护图连接,IVF 只要做一次聚类。
第二层是量化。默认 u8 把每维从 4 字节压到 1 字节,官方口径约 1% 质量损失;u4 再压一半,8 倍压缩。这就是 217 MB 变 60 MB 的来源。
第三层是 out-of-core 设计,这是它和大多数向量扩展分道扬镳的地方。常驻内存的只有量化后的聚类中心和行号映射,真正的簇数据放磁盘,查询时按需分页加载,再用一层 1 位 RaBitQ sketch 在内存压力下先排除掉大概率不命中的簇。官方文档的实测口径是 8 GB 内存处理 1.13 亿条 1024 维 embedding,约 450 GB 向量数据;PR 描述里写的是 500 GB 数据集配 8 GB 内存上限。
有个设计细节值得单独说:DuckDB 从磁盘读数据的最小粒度是 256 KB,主流向量系统常用的「RaBitQ 粗筛 + 全精度重排」两段式方案在这里会造成 40 到 150 倍的读放大。所以 DuckIR 干脆不做全精度重排,靠聚类局部性和 sketch 保证召回。这是工程约束倒逼算法选择的典型例子。
DuckIR 的内存/磁盘分层设计与查询流程
● ● ●
召回的诚实账
近似索引的账要算清楚。我在两种数据上测了 recall@10:
均匀随机向量上,召回只有 0.50。这不是 bug,是 IVF 的本质:随机数据没有簇结构,聚类探不准。换成有 50 个簇中心的模拟数据(更接近真实 embedding 的分布),召回回升到 0.90,过滤搜索(按分类列过滤后再排距离)召回 1.00。
官方口径是 u8 量化约 1% 质量损失,这是在真实 embedding 数据上的数字。如果你拿不准自己的数据长什么样,把 n_probe 调大(比如 48)换召回,或者干脆 SET duckir_vss_n_probe = 0 退回暴力扫描做对照——这个开关本身很好用。
● ● ●
边界在哪
Alpha 版本,官方明确说扩展升级后索引可能需要重建,生产环境慎用。除此之外还有几条硬边界:只支持 FLOAT[d] 列(DOUBLE 不行,可以在写入时 ::FLOAT[d] 转换);维度上限 16384;不支持内积距离;NaN、Inf、NULL 向量直接排除在索引外。查询形状也有讲究,比如标量子查询当查询向量不行,得用 SET VARIABLE 加 getvariable 的写法;JOIN 写法的过滤不会走索引,改成 IN 子查询就行。
这些细节都在仓库的 LIMITATIONS 文档里写得很清楚,透明度值得表扬。
● ● ●
回到开头
在 DuckDB 里做向量搜索的老痛点——建索引慢、维护麻烦、数据一大就爆内存——DuckIR 交出的答卷是把向量索引塞回数据库自身的存储和事务体系里:跟表走、跟 checkpoint 走、跟内存限制走。我的判断:等它出了 Alpha、FTS 和混合搜索上了路线图,官方 vss 的存在理由会变得很尴尬。在那之前,500 GB 向量配 8 GB 内存这一条,就够很多人把它装上试一试了。
你打算用它替换现有的向量方案,还是先在侧数据集上验证召回?欢迎在评论区聊聊你的数据规模和召回要求。
● ● ●
参考来源
- 01
DuckIR 收录 PR(2026-11-13 合并) https://github.com/duckdb/community-extensions/pull/3589 - 02
DuckIR 仓库 README(含官方对比表与测试配置) https://github.com/cwida/DuckIR - 03
Out-of-Core 文档(113M 条 1024 维实测、256 KB 读放大分析) https://github.com/cwida/DuckIR/blob/main/docs/OUT_OF_CORE.md - 04
已知限制文档 https://github.com/cwida/DuckIR/blob/main/docs/LIMITATIONS.md - 05
官方 duckdb-vss 扩展 https://github.com/duckdb/duckdb-vss - 06
本机实测:DuckDB 2.0.0 (Python),WSL 4 线程,20 万条 64 维向量,脚本与数据库文件本地留存