VexDB HybridAnn 向量标量混合索引原理与实践
VexDB HybridAnn 向量标量混合索引原理与实践
之前分享了VexDB的ivfpq 聚集点向量索引 和 图向量索引原理
《容器体验VexDB! 》 这家国产向量数据库说他们的图索引超越HNSW,是不是真的? 《VexDB DiskANN 图索引原理与实践》
但是现实中很多搜索是既有向量也有标量条件的, 这篇就分享一下VexDB的 向标混合索引.
下面的内容主要来自VexDB官方文档和个人的理解.
https://vexdb.com/docs/user-guide/hybridann
文档中仅仅给出了使用方式, 并没有提到HybridAnn 向量标量混合索引的实现原理, 我个人根据其内容反推了一下具体的实现, 仅供参考.
首先, HybridAnn 肯定不是简单的在向量索引叶子节点里添加标量信息(或反之), 因为这个用PG index include(覆盖索引)就可以实现.
最后补一下,20251025刚刚请教了原厂老师,已经解惑。标量b树,节点下面根据数据量选择是否建向量索引,建图还是倒排向量索引。不能再说太多了,看未来产品文档更新吧。
向量标量混合索引实现猜测
文档中提到使用向标联合索引时, 标量字段支持等值、范围检索.
文档还提到, vec_index_magnitudes_offset 这个参数是以冒号分隔的正整数列表。共5层。参数类型为字符串,格式为N1[:N2[:N3[...]], 默认值:20000:100000:500000:2500000:12500000 . 触发构建内部向量索引的数据量级,提供的量级越多,向量索引构建的越精细,查询时匹配度越高,但索引数量也越多,构建时间越长。
文档还提到, graph_magnitude_threshold 图索引构建阈值,大于等于该数值的向量索引使用图索引。
文档还提到, 暂不支持在空表上创建 HybridANN 索引。建议将所有需要查询的数据提前插入表中,最后再建立索引.
文档还提到, fillfactor 参数为构建索引时B+树叶子节点的百分比负载度,参数逻辑同B+树索引同名参数。不推荐更改。 这点让我很困惑, 因为我猜想的是没有B+树索引. 除非这列标量的唯一值特别多, 搜索时等值匹配较多, 但如果是这样的话, 根本就不需要向标混合索引, 直接标量索引就好了性能还更好.
综合以上信息, 推测 HybridAnn 索引实现原理如下:
1、根据vec_index_magnitudes_offset来划分row group, 每个标量字段进行排序后, 在每个row group中都有其min_max值.
例如默认值:20000:100000:500000:2500000:12500000, 假设表有2500万条记录.
N1层每2万行一个row group, 这一层总共就有1250个row group. 且每个row group都存储标量字段排序后的min_max值(可能排除了NULl值), 作为元数据.
N2层每10万行一个row group, 这一层总共就有250个row group. 且每个row group都存储标量字段排序后的min_max值(可能排除了NULl值), 作为元数据.
N3层每50万行一个row group, 这一层总共就有50个row group. 且每个row group都存储标量字段排序后的min_max值(可能排除了NULl值), 作为元数据.
N4层每250万行一个row group, 这一层总共就有10个row group. 且每个row group都存储标量字段排序后的min_max值(可能排除了NULl值), 作为元数据.
N5层每1250万行一个row group, 这一层总共就有2个row group. 且每个row group都存储标量字段排序后的min_max值(可能排除了NULl值), 作为元数据.
然后给每个row group创建向量索引, 当这个row_group中的实际记录条数大于graph_magnitude_threshold时, 构建的就是图索引, 否则构建的是ivf索引?
这样的话, 可满足支持标量字段的等值和范围查询, 同时支持向量搜索.
问题1: 如果标量字段很多, 可能创建非常多的重复向量索引.
问题2: 索引中的标量字段发生增删改时, 可能要在索引中挪动相关联的向量值, 所以标量字段更新越少越好.
2、搜索时, 先比对标量条件和元数据选出覆盖了哪些row group(尽量选择记录数少, 并且组数少的.)
3、在对应的向量索引中进行向量检索, 同时在返回结果前, 使用recheck 再次检查标量条件, 过滤出精准数据.
推测另外一种可能的 HybridAnn 索引实现原理如下:
与以上相反, 采用ivf的方法将向量分成若干个桶, 每个桶只存储中心点和最外围的若干个点的向量作为元数据. 然后每个桶的标量建立B+树索引.
搜索时, 先根据向量条件选出最近的若干最相近的个桶, 然后使用标量条件去这几个桶对应的B+树去进行索引扫描, 最后合并结果, 并回表找到实际的向量值进行精算和reranking.
以上仅为个人猜测, 希望有知道内幕的老司机斧正.
接下来上官方文档.
原理介绍
HybridAnn 索引允许在向量索引基础上额外增加对标量条件的过滤功能。在构建索引时将需要支持的标量放入索引构建目标中。
HybridAnn 采用了改良后的graph_index作为向量检索索引,令 HybridANN 也继承了 graph_index 的优势 —— 更高的召回率,更低的内存占用和更短的索引构建耗时。
注意事项
1、构建 HybridANN 索引时,该索引第一列必须为向量类型floatvector,作为向量检索中的向量查询目标;其余列为非向量的其他标量数据,比如数值,时间,枚举类型等。具体支持的标量类型可以通过如下语句查看:
SELECT DISTINCT typname
FROM pg_am, pg_amop, pg_type
WHERE
pg_type.oid = amoplefttype AND
pg_am.oid = amopmethod AND
amname = 'hybridann';
2、单个 HybridANN 索引最多能包含31个标量字段。
3、当增删业务较少时,可以适当放宽向标联合索引的数量限制。具体数量由同时被使用到的索引数量,增删业务量,机器规格等综合考虑。
4、向标联合索引的创建和查询资源占用以及对增删业务的影响高于其他索引类型。
5、与B+树索引要求相同,字符匹配、数组操作、表达式等B+树不支持的条件不能使用向标联合索引。
6、暂不支持在空表上创建 HybridANN 索引。
7、向标联合索引查询不支持针对标量字段执行 IS NULL 或 IS NOT NULL 查询, 也不支持order by标量字段走向标联合索引查询。
索引参数
索引构建参数
m | 2~100默认值: 16 | |
ef_construction | 4~1000默认值: 64 | |
parallel_workers | 0~64默认值: 0 | |
fillfactor | 10~100默认值: 90 | B+树叶子节点的百分比负载度,参数逻辑同B+树索引同名参数。不推荐更改。 |
vec_index_magnitudes_offset | N1[:N2[:N3[...]]。 默认值: 20000:100000:500000:2500000:12500000 | |
graph_magnitude_threshold | 1-10'000'000'000。 默认值 20000 |
索引构建GUC参数
max_vector_indexer_worker_threads | 2~64默认值: 16 | max_vector_indexer_worker_threads 用于控制后台用于构建向量索引的线程数量上限。构建任务由系统自动调度分发,合理配置该参数可以有效加速大规模向量数据的索引构建过程。 |
查询参数
hnsw_ef_search | 1~32767默认值: 100 | 实际生效值是 top-k(查询的limit n)和hnsw_ef_search中更大的值。 ( 难道不是更小值吗? ) |
hybrid_query_ivf_probes_factor | 1~100默认值: 3 | 该参数控制选择率小的查询搜索范围,调参逻辑等同于 ivf_probes ,根据召回率要求修改。 |
max_vector_indexer_query_threads | [0,256]默认值: 0,表示关闭向量并行检索。 |
使用建议
1、建议将所有需要查询的数据提前插入表中,最后再建立索引;如果决定先建立索引再导入数据,在数据导入完成后需要重建索引。
2、当需要提升索引构建速度, 可以适当增大 parallel_workers,建议设置为机器 CPU 核数的 75%,最大值 64。
3、当需要提升召回率时, 可适当增大 ef_search 和 hybrid_query_ivf_probes_factor,参数越大则召回率越高,查询变慢。
4、当前标量支持条件与系统默认索引 B+ 树一致。
5、使用 explain 语句确定语句是否使用向标混合索引以及预计的具体执行。
使用示例
1、数据准备:创建用于生成随机向量的函数。
CREATE OR REPLACE FUNCTION random_array(dim integer,min_value int, max_value int)
RETURNS text
AS $$
SELECT REGEXP_REPLACE(REGEXP_REPLACE(array_agg(round(random()* (max_value - min_value + 1) + min_value,3))::text,'{','['),'}',']')
FROM generate_series(1, dim);
$$
LANGUAGE SQL
VOLATILE
COST 1;
2、创建测试表并插入数据。
DROP TABLE t_1194690;
CREATE TABLE t_1194690(id BIGINT, v floatVECTOR(15));
INSERT INTO t_1194690 SELECT i, random_array(15,1,3)::floatvector(15) FROM generate_series(1, 10000) AS i;
3、创建索引。注意把需要用到的标量放入索引创建内容中, 将向量字段排在第一位 。
根据查询需求选择匹配的向量操作符创建对应索引.
按余弦距离构建索引,使用B+树挂载向量索引实现:
CREATE INDEX idx_1194690a ON t_1194690 USING hybridann(v floatvector_cosine_ops, id) WITH (parallel_workers=5);
按欧几里得距离构建索引,使用混合图索引实现(WITH设置参数也可省略,此时为默认值):
CREATE INDEX idx_1194690b ON t_1194690 USING hybridann(v floatvector_l2_ops, id) WITH (parallel_workers=5);
4、设置查询参数(可选)。
set ef_search=10;
set hybrid_query_ivf_probes_factor = 3;
向量标量混合检索:带有标量条件的余弦距离排序。
SELECT * FROM t_1194690 WHERE id BETWEEN 5000 AND 6000
ORDER BY v <=> '[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]'::floatvector
LIMIT 10;
向量标量混合检索:带有标量条件的欧几里得距离排序。
SELECT * FROM t_1194690 WHERE id BETWEEN 2000 AND 4000
ORDER BY v <-> '[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]'::floatvector
LIMIT 10;