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;

状态核对清单:

  1. State 必须到 FINISHED 才开始评估性能,否则量到的其实是未上索引的暴力扫描耗时。
  2. SHOW INDEX 里 Index_type 显示 ANN,Properties 列输出完整参数,用于确认是否与预期一致。
  3. 需要中止时用 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
posted @ 2026-09-24 16:29  SeleectDB  阅读(26)  评论(0)    收藏  举报