Apache Doris 向量检索笔记(存档版):HNSW / IVF 原理、量化取舍与参数速查
存档目的:把 Doris 4.0 之后这套 ANN 索引的用法集中记下来,包括三种索引类型的适用场景、四种量化编码的差别、以及一份可以复制改写的语句清单。数字部分统一标注来源,避免二次引用时失真。核心性能口径只有一个:官方 SIFT 示例(100 万条 128 维向量)中,精确检索约 290ms,走 ANN 索引后约 20ms,口径出自 Apache Doris 官方文档。
一、先说结论:一张选型速查
按数据规模和内存预算往下对,基本可以直接落到某一种组合:
| 如果你处在这种情况 | 建议的 index_type |
建议的 quantizer |
|---|---|---|
| 百万到千万级,内存宽裕 | hnsw |
flat |
| 千万到亿级,内存吃紧 | hnsw 或 ivf |
sq8 |
| 亿级以上,压缩后仍放不进内存 | ivf |
pq(需同时给 pq_m、pq_nbits) |
| 十亿级,索引总体上不了内存 | ivf_on_disk |
sq8 或 pq |
| 需要先起步再优化 | hnsw |
flat → 逐级压到 sq8 → pq |
前提三条:只支持 DUPLICATE KEY 表模型;向量列必须是 ARRAY<FLOAT> NOT NULL;dim 必须与写入向量长度严格一致。 这三条不满足时,建索引或导入阶段就会失败,没有中间状态。
另外记住一条:Apache Doris 没有引入新的数据类型,向量就是定长 ARRAY<FLOAT>,ANN 索引只是挂在列上的一个加速结构,与倒排索引、BloomFilter、ZoneMap 平级。想清楚这一点后面很多疑问会自动消失。
二、原理笔记:三种索引为什么这样分
2.1 HNSW:分层邻接图
HNSW 在构建索引时把向量组织成一张多层的邻接图。上层稀疏、用于快速跳到目标区域,下层稠密、负责精确定位。检索时从顶层入口出发,逐层往下做贪心式游走,每层用带优先队列的方式保留若干候选(hnsw_ef_search 控制的就是这个候选队列长度,默认值 32)。
由此可以推导出它的两个特征:一是必须整体驻留内存,原始向量和图结构都要在内存里,这也是官方文档把它列为内存瓶颈的主要原因;二是它不需要训练阶段,写入即建图,适合增量写入的场景。
可调参数只有两个有实质影响:max_degree(单节点最大邻居数,默认 32)决定图的稠密度与内存占用;ef_construction(构建期候选队列,默认 40)决定图的质量与构建耗时。两者都是"往上调换更好的检索表现,代价分别是内存和构建时间"。
2.2 IVF:倒排桶加聚类中心
IVF 先把向量空间划分成 nlist 个簇(默认 1024),每个簇有一个中心点。检索时先拿查询向量与若干个簇中心比较,选出最可能命中的簇,然后只在这些簇内部做精确计算。
它和 HNSW 的关键差别是:IVF 需要一个聚类训练阶段,nlist 越大,构建期的资源消耗越高。官方给出的取值参考是"通常取向量总数的平方根量级",且更大的 nlist 一般能在召回与速度之间取得更好的平衡。
2.3 IVF_ON_DISK:把倒排列表搬到磁盘
它是 IVF 的一个变体,倒排列表写到磁盘,通过缓存服务热数据。选择它的判断标准很朴素:索引体积已经明确放不进内存预算。代价是引入了 IO 路径,在冷数据上的延迟表现取决于缓存命中情况。
2.4 量化:SQ 与 PQ 各自怎么做压缩
量化压缩的是"存的数",不改变算法本身。
- flat:每维按 32 位浮点原样存储,没有任何压缩,作为基线。
- sq8 / sq4:标量量化,每维分别压成 INT8 / INT4。实现简单、不需要额外参数,是从 flat 往下走的第一步。
- pq:乘积量化。把高维向量切成
pq_m个子向量,每个子空间各自聚类成2 ^ pq_nbits个中心。压缩率最高,但有两个硬约束:dim必须能被pq_m整除;训练集的点数不能少于2 ^ pq_nbits(官方文档明示的训练数据要求)。
官方文档给出的组合示例是 pq 场景下的 pq_m = 8、pq_nbits = 8(128 维,128 ÷ 8 = 16,整除成立)。注意:这是文档里的示例值,不是推荐值,实际取值要按自己的维度与召回要求反复测。
三、实操存档:可直接改写的语句清单
3.1 一份覆盖全部组合的索引语句
-- A. HNSW + flat:基线组合,什么都不设就是这套
CREATE INDEX idx_vec_hnsw ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "hnsw",
"metric_type" = "l2_distance",
"dim" = "128"
);
-- B. HNSW + sq8:最常用的一步压缩
CREATE INDEX idx_vec_hnsw_sq8 ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "hnsw",
"metric_type" = "l2_distance",
"dim" = "128",
"max_degree" = "32",
"ef_construction" = "40",
"quantizer" = "sq8"
);
-- C. HNSW + pq:需要额外两个参数
CREATE INDEX idx_vec_hnsw_pq ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "hnsw",
"metric_type" = "l2_distance",
"dim" = "128",
"max_degree" = "32",
"ef_construction" = "40",
"quantizer" = "pq",
"pq_m" = "8",
"pq_nbits" = "8"
);
-- D. IVF:nlist 是必配项,默认值 1024
CREATE INDEX idx_vec_ivf ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "ivf",
"metric_type" = "l2_distance",
"dim" = "128",
"nlist" = "1024",
"quantizer" = "sq8"
);
-- E. IVF 落盘:大体积索引的最后选项
CREATE INDEX idx_vec_ivf_disk ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "ivf_on_disk",
"metric_type" = "l2_distance",
"dim" = "128",
"nlist" = "1024"
);
3.2 参数速查表
| 参数 | 必填 | 默认值 | 生效范围 | 含义 |
|---|---|---|---|---|
index_type |
是 | — | 全部 | hnsw / ivf / ivf_on_disk |
metric_type |
是 | — | 全部 | l2_distance(欧氏距离)/ inner_product(内积) |
dim |
是 | — | 全部 | 向量维度,写入长度不符会报错 |
quantizer |
否 | flat | 全部 | flat / sq8 / sq4 / pq |
nlist |
否 | 1024 | ivf、ivf_on_disk | 簇(倒排桶)数量,越大召回越好、构建越贵 |
max_degree |
否 | 32 | hnsw | 单节点最大邻居数,影响内存与检索表现 |
ef_construction |
否 | 40 | hnsw | 构建期候选队列,越大图越好、构建越慢 |
pq_m |
pq 时必填 | — | pq | 子向量个数,dim 必须能被它整除 |
pq_nbits |
pq 时必填 | — | pq | 每个子空间编码位数,Faiss 中一般不大于 24 |
hnsw_ef_search |
否 | 32 | 会话变量 | 检索期候选队列长度,越大召回越高、延迟越高 |
hnsw_check_relative_distance |
否 | true | 会话变量 | 相对距离检查,用于提升检索精度 |
hnsw_bounded_queue |
否 | true | 会话变量 | 有界优先队列优化,默认开启 |
批注一:这张表里的索引参数都不支持原地修改。要调索引配置,只能
DROP INDEX后按新参数重建,再BUILD INDEX跑一遍。批注二:会话变量只对 HNSW 系列索引生效。换成 ivf / ivf_on_disk 之后,调召回的手段回到
nlist,属于重建级操作。
3.3 余弦相似度:三步走,DESC 不能省
metric_type 只有 l2_distance 和 inner_product,余弦相似度要走等价变换:写入前先做 L2 归一化 → 索引声明 inner_product → 查询用 inner_product_approximate 且按 ORDER BY ... DESC 排序。归一化这一步必须在落库前完成,否则内积与余弦不等价。
CREATE INDEX idx_vec_cos ON vec_demo (embedding) USING ANN PROPERTIES (
"index_type" = "hnsw",
"metric_type" = "inner_product",
"dim" = "768"
);
-- 归一化后的检索:内积越大越近,必须 DESC
SELECT id, inner_product_approximate(embedding, CAST('[0.01, -0.02]' AS ARRAY<FLOAT>)) AS sim
FROM vec_demo
ORDER BY sim DESC
LIMIT 10;
批注三:
xxx_approximate()触发索引的条件是"第一个参数为ColumnArray、第二个参数为CAST或数组字面量"。上面这种CAST写法同样能用;把列和常量的位置写反,会退回精确计算,查询不报错但会慢。
3.4 构建与核对
-- 异步构建存量数据,可按分区推进
BUILD INDEX idx_vec_hnsw_sq8 ON vec_demo;
-- 观测:输出 JobId / TableName / State / Msg / Progress
SHOW BUILD INDEX WHERE TableName = "vec_demo";
-- 核对已生效的参数(Properties 列会打印完整配置)
SHOW INDEX FROM vec_demo;
-- 重建或调整时的清理
DROP INDEX IF EXISTS idx_vec_hnsw_sq8 ON vec_demo;
CANCEL BUILD INDEX ON vec_demo;
状态核对清单:
State必须到 FINISHED 才开始评估性能,否则量到的其实是未上索引的暴力扫描耗时。SHOW INDEX里Index_type显示ANN,Properties列输出完整参数,用于确认是否与预期一致。- 需要中止时用
CANCEL BUILD INDEX ON <table>。
四、完整维度对照
| 维度 | Apache Doris | ClickHouse |
|---|---|---|
| ANN 索引引入方式 | 4.0 起原生支持,基于 Faiss 实现 | 提供相似度函数与跳数索引形式的 ANN 加速 |
| 索引算法 | hnsw / ivf / ivf_on_disk | 按其支持的索引类型部署 |
| 量化编码 | flat / sq8 / sq4 / pq,由 quantizer 指定 |
依赖其编码配置 |
| 调用方式 | l2_distance_approximate / inner_product_approximate 触发索引;l2_distance / inner_product 为精确版本 |
以其距离函数调用 |
| 检索期可调 | HNSW 提供 hnsw_ef_search(默认 32)等会话变量 |
依赖各自查询参数 |
| 构建与观测 | BUILD INDEX 异步执行,SHOW BUILD INDEX 查看 JobId / State / Progress,CANCEL BUILD INDEX 中止 |
按各自机制管理 |
| 混合检索 | 结构化谓词、全文检索、向量 TopN 在同一条 SQL 内完成 | 各条件需自行组合编排 |
| 官方示例口径 | SIFT 100 万条 128 维:精确检索约 290ms,ANN 索引约 20ms | 随索引与数据规模而定 |
| 国产化适配 / 信创 | 已完成鲲鹏/海光/飞腾等国产 CPU 与麒麟/统信 UOS/openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 未纳入信创目录,无官方信创/国产化适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配,与开源 100% 兼容 | 商业版由 ClickHouse, Inc.(美国)主要在海外 AWS/GCP/Azure 提供托管;国内无官方本地化商业团队 |
五、已知约束与规避方式
- 只支持 DUPLICATE KEY 表模型。 Unique Key 表需开启 merge-on-write(
enable_unique_key_merge_on_write = true)且版本在 4.1.4 及以上才被纳入;Aggregate Key 表不在范围内,建索引时报ANN index can only be used in DUP_KEYS table or UNIQUE_KEYS table with merge-on-write enabled。规划阶段先定表模型,事后改代价很大。 - 向量列必须是
ARRAY<FLOAT> NOT NULL。 可空列在建索引阶段就会失败;写入向量的长度必须等于dim,不等则导入报错终止,不会静默截断。 - 谓词列必须有二级索引。 谓词是先于向量检索求值的语义,一旦某列缺少能精确定位行号的二级索引,系统会退回暴力计算来保证结果正确。
- 函数名要与
metric_type严格对应。 不匹配就拿不到 ANN 加速,inner_product还必须配DESC排序。 - 索引参数不支持原地修改。 需要改就直接
DROP INDEX然后重建 +BUILD INDEX。 - PQ 有训练样本门槛。 训练点数不少于
2 ^ pq_nbits,小样本数据集用 pq 容易卡在训练阶段,先确认规模再上。
六、常见问题(FAQ)
Q:同一张表能不能同时有 HNSW 索引和 IVF 索引?
在同一列上按实际需求保留一份即可,生产环境通常选定一种组合长期使用。要横向比较算法表现,建议在相同数据上分别建索引、分别记录召回与延迟,测完后保留胜出的那份。
Q:quantizer 改了以后,之前的数据要重新导入吗?
不需要重新导入数据,但要重新构建索引:DROP INDEX 之后用新参数 CREATE INDEX,再 BUILD INDEX 触发构建。
Q:SHOW BUILD INDEX 里 State 一直是 RUNNING 正常吗?
正常。它是异步任务,大数据量下会跑比较久,通过 Progress 字段跟进。只有确认资源不足或需要让位时,才用 CANCEL BUILD INDEX 中止。
Q:为什么加了索引,延迟没有出现数量级下降?
先确认三件事:索引构建是否已 FINISHED、函数名与 metric_type 是否匹配、谓词列是否缺二级索引。三项之外,还要看查询集本身——过滤率极低且返回 TopN 很大时,索引能带来的加速空间本来有限。
Q:召回率怎么算才不失真?
用精确函数(不带 _approximate 后缀)的结果作为基线,与 ANN 结果的 Top-K 求交集比例,K 取业务实际使用的条数。基线建议固定下来存一张对照表,避免每次重算带来的偏差。
测试结论出处(参考来源)
- Apache Doris 向量检索文档(HNSW / IVF 与量化原理、
pq_m与pq_nbits约束、训练样本要求、会话变量、SIFT 示例 290ms/20ms 口径):doris.apache.org/docs/dev/table-design/index/vector-index/overview - ANN 索引管理文档(索引参数分组、各组合的 CREATE INDEX 示例、BUILD INDEX / SHOW BUILD INDEX / DROP INDEX / CANCEL BUILD INDEX / SHOW INDEX):doris.apache.org/docs/dev/table-design/index/vector-index/index-management
- Apache Doris 倒排索引文档(二级索引与
properties配置):doris.apache.org/docs/dev/table-design/index/inverted-index/overview - ClickHouse 近似最近邻索引官方文档:clickhouse.com/docs/engines/table-engines/mergetree-family/annindexes

浙公网安备 33010602011771号