向量索引GsDiskANN
GsDiskANN是针对大数据量(千万/亿级别)的向量检索,支持在已创建含向量数据的列上创建单列索引,并支持删除索引、支持数据变更后的索引自动更新。
前置条件
- 数据库正常运行,用户正常登录。
- 已创建带有向量类型的表,且表中已有少量规模行数的数据(1w行以内)。
- 数据库的GUC参数enable_vectordb设置为on。
使用约束
- GsDiskANN索引仅支持浮点类型(FLOAT)。
- GsDiskANN索引支持的向量长度上限为4096维。
- 使用原始向量时,上限为1024维。
- 启用pq/lvq量化压缩时,上限为4096维,此时需要设置enable_vector_copy=false以及subgraph_count > 0。
- 启用pq量化压缩时,需设置pq_nseg和pq_nclus,以保证量化后的向量大小不超过页面大小8000 bytes。具体计算方法:将pq_nclus向上取到16/256,分别占用0.5/1 byte,再乘以pq_nseg,得到量化向量大小。
使用建议
- 在需要加速查询的向量属性列上创建GsDiskANN类型索引。
- 参数pq_nseg控制的是PQ创建段数,此段数必须要满足的条件是向量维度要能够被段数整除。段数设置的越大,占用空间越大,但是距离计算越准确;因此经验推荐设置规则如下:
- 当向量维度小于512维度的时候,建议设置段数和维度相同。
- 当向量维度高于512维度小于等于1024维度的时候,建议设置为维度的一半(能整除的情况下)。
- 参数pq_nclus控制的是PQ每段的分类数,此参数控制的是每个片段的映射聚类个数,此参数必须要满足少于数据行数,片段聚类设置越大,距离计算精度越高;向量维度1024维度以内,推荐值是16。
- 参数num_parallels控制的是构建索引时候的并发度,在内存充足(能够放下全部索引),Intel Xeon 5代以上处理器环境上,这个参数在30并发以内可以多核跑满,建议设置值是50;num_parallels参数受到数据库内bgworker最大值的限制,这个值是64,数据库进程同时申请占用的bgworker数量超过这个值会报错;特别需要注意的是,多会话同时创建索引的场景,应该避免创建过多的线程总数。
- 从已有数据表构建索引时,数据量充足的情况下,参数enable_pq推荐值是true,以便使用PQ计算邻居节点距离,避免大量磁盘(或者共享内存)访问,以加速构建。
- 如果在表数据不足的情况下,强行创建enable_pq=true的索引,索引创建会上报告警,并且自动切换为enable_pq=false的方式进行创建;当使用未经过PQ压缩的gsdiskann索引时,会导致性能存在劣化,如果希望重新创建enable_pq=true的索引,请保证数据量充足的情况下进行REINDEX操作。在数据库进行表TRUNCATE操作、表级别备份恢复等过程中会发生数据不足导致无法完成创建索引的问题,完成之后建议重新构建索引,保证使用PQ压缩优化的索引效率。
- 自动扩展分区场景中如果使用enable_pq=true参数构建索引,可能会产生较多数量的告警,属于正常提醒;如果不希望提示告警,可以将索引enable_pq参数设置为false。
- 从已有数据表构建索引时,数据量不足的情况下,参数enable_pq的值必须设置为false,此场景不使用PQ计算距离。
- 从已有数据表构建索引时,参数using_clustering_for_parallel在开启之后可以缓解并发冲突,但是存在聚类不均衡导致长尾的风险,默认是关闭的。
- 新增数据格式必须与创建的数据表各项类型一致,向量数据维度也需要一致。
- 删除数据需要指定待删除数据的搜索条件,支持单点和批量删除。
- 由于存储长度限制,GsDiskANN图中的每个节点出度有限制,因此无法保证全图连通性,用户查询离群点的时候可能会发生查询返回结果少于指定TopK的情景,如果需要保证输出数量,请改用GsIVFFLAT索引。
- GsDiskANN索引大小预计为原数据占用磁盘的10-50倍,建索引前请提前确定磁盘容量。
- 由于GsDiskANN每次更新一条向量之后需要更新多个索引页数据,因此记录的日志量较大,可能会频繁出现LOG: checkpoints are occurring too frequently的日志打印,属于正常现象;如果不希望打印,在资源充足的情况下可以增加参数“checkpoint_segments”(参见《参考》中“数据库运行参数说明 > GUC参数说明 > 预写式日志 > 检查点”章节)的大小。
- 从大量已有数据表构建索引时,预计索引大小若大于shared_buffers时,会导致构建时间大幅度延长,该场景下可以通过设置subgraph_count参数将索引分片构建,加速构建时间,但会降低构建出索引的检索召回率。参数subgraph_count将索引分成数值设置的片数,每片大小需小于maintenance_work_mem,若用户规格充足,建议在当前连接中将maintenance_work_mem设置为不低于最大可用内存的33%后构建索引。如果maintenance_work_mem不足会在构建初报错并给出估计的最低建议规格。在规格不足时可以尝试增加subgraph_count分片数量,或者设置为0由磁盘存储格式构建。
- subgraph_count分片数量越高,建成索引的检索召回率越低,设置数值不建议超过10。
- 在使用astore时,默认情况下params_adaptive为true。若subgraph_count为0,数据库会自动将其调整为 1。若params_adaptive设置为false,则subgraph_count默认为0时会触发报错。此时,需显式设置subgraph_count为大于0的值,或将params_adaptive设置为true以避免错误。
- 创建索引时的内存需求超过maintenance_work_mem参数指定的内存大小时,会产生警告,并将subgraph_count参数值视为0进行处理。请注意,此情况可能导致索引构建时长超出预期,如果用户无法接受此行为,请终止构建流程,并将maintenance_work_mem参数值设置为大于抛出预警中描述的内存需求值的范围并进行重新构建。内存需求的计算是统计当前数据量的大小,并根据统计数据量估算需要的内存大小,此提示值为统计值,大小可能会波动,建议根据实际情况在提示值基础上适当增大,将GUC参数maintenance_work_mem设置为合适大小。
- maintenance_work_mem属于user_set的GUC参数。如果用户直接设置maintenance_work_mem的大小对自动扩展分区表创建索引,产生的自治事务的maintenance_work_mem值不会发生改变,当创建索引时的内存需求超过之前设置的maintenance_work_mem参数指定的内存大小时,会产生警告,并将subgraph_count参数值视为0进行处理。建议用户使用reload修改参数。
- 参数enable_neighbor_embedded用于指定是否允许邻居向量密集存储,即每个点是否存储所有邻居的向量副本,该参数仅在enable_pq=true且subgraph_count>0时生效。启用此参数会导致索引大小发生显著膨胀,且查询所需的缓存也会相应增加。然而,当向量长度较短时,查询速度可能会显著提升。
- 参数enable_vector_copy用于指定是否存储原始向量的副本,即索引中是否存储原始向量的副本,该参数仅在enable_pq=true且subgraph_count>0时生效。 查询时,路由过程使用原始向量,可以少量提升查询精度;查询时,重排过程使用原始向量,可以少量提升查询速度。
- 参数build_with_quantized_vector用于指定构建时是否使用编码向量,即是否使用量化向量构建,该参数仅在enable_pq=true且subgraph_count>0时生效。启用此参数,会使用量化向量替代原始向量进行构建,减少构建所需的内存,可能会导致构建精度下降,且构建速度可能上升或下降。
- 参数quantization_type用于指定量化算法类型,选择量化方法,取值范围是{pq, lvq},默认值是pq,该参数仅在enable_pq=true时生效。
- 参数lvq_nclus用于指定LVQ的量化向量的长度和质量,该参数仅在enable_pq=true且quantization_type=lvq时生效。
- 参数graph_degree用于指定图的连通度,控制图索引中每个节点的出度,该参数仅在subgraph_count>0时生效,推荐值为96。
- 参数build_instruction_set用于指定构建所用的指令集,默认值为AUTO。当subgraph_count=1时,支持{AUTO、NONE、SSE4、AVX2、AVX512F、AVX512BWDQ、NEON}。当subgraph_count=0时,仅支持{AUTO、SSE4、NEON}。取值范围与效果如下:
- SSE4:可用于几乎所有的X86平台,但性能较差。
- AVX2:可用于大部分X86平台,性能较好,支持相对快速的基于量化向量构建。
- AVX512F:可用于部分X86平台,部分指令计算较快。
- AVX512BWDQ:可用于高性能X86平台,性能较优,支持最高性能的基于量化向量构建。
- NEON:可用于几乎所有ARM平台,但性能较差。
- AUTO:基于用户机器的信息,自动寻找所支持的性能较优的指令集并显式使用。
- NONE:不显式使用指令集,由编译器决定使用何种指令集进行优化。
在升级观察期时创建向量索引GsDiskANN,新版本参数取值仅支持默认值,既往版本参数取值无额外限制。
参数说明
GsDiskANN仅支持floatvector。
floatvector支持欧式距离(L2)、余弦距离(Cosine)的计算。对于维度为dim的向量,其中L2的索引标量范围是-9.223E+18/sqrt(dim)~9.223E+18/sqrt(dim),Cosine的索引标量范围同样是-9.223E+18/sqrt(dim)~9.223E+18/sqrt(dim)。超出此范围时,如果运算过程中有数据溢出情况,会提示报错并返回。
在505.2.1版本之前,建立索引时标量超出此范围时,不会有显式报错,但仍然不推荐超出此范围,运算过程中超过3.402E+38的数据会被截断至3.402E+38;在505.2.1版本及之后,建立索引时标量超出此范围时,如果运算过程中有数据溢出情况,会提示报错并返回。
| 距离类型 | 标量约束 |
|---|---|
| L2 | (-9.223E+18/sqrt(dim), 9.223E+18/sqrt(dim)) |
| Cosine | (-9.223E+18/sqrt(dim), 9.223E+18/sqrt(dim)) |
对于维度相同的向量a、b,在余弦距离(Cosine)的计算过程中,如果a、b有任意一个向量为零向量,那么余弦距离的计算结果为NaN,一般情况需要避免零向量参与余弦距离的计算。
| 参数名称 | 取值说明 | 参数描述 |
|---|---|---|
| pq_nseg | 取值范围:1~65535 默认值:1 | 原始矢量量化分片个数。该参数在enable_pq=true,且quantization_type='pq'时有效。 |
| pq_nclus | 取值范围:2~256 默认值:16 | 每个量化分片的中心点个数。该参数在enable_pq=true,且quantization_type='pq'时有效。 |
| queue_size | 取值范围:64~1000 默认值:100 | 构建磁盘时候选列表的大小。 |
| num_parallels | 取值范围:1~64 默认值:10 | 构建索引并行计算数量。 |
| using_clustering_for_parallel | 取值范围:T/F 默认值:F | 是否使用集群进行并行构建(T为是,F为否)。该参数在subgraph_count=0时有效。 |
| lambda_for_balance | 取值范围:0~DBL_MAX 默认值:0.00001 | 控制并行计算之间的平衡。 |
| enable_pq | 取值范围:T/F 默认值:T | 在构建和搜索时是否使用PQ进行加速(T为是,F为否)。 |
| subgraph_count | 取值范围:0~32 默认值:0 | 控制索引分片构建数量。 |
| enable_neighbor_embedded | 取值范围:T/F 默认值:F | 每个点是否存储所有邻居的向量副本。
|
| enable_vector_copy | 取值范围:T/F 默认值:T | 是否存储原始向量的副本。
|
| build_with_quantized_vector | 取值范围:T/F 默认值:F | 构建时是否使用编码向量。
|
| quantization_type | 取值范围:pq/lvq 默认值:pq | 量化算法类型。该参数在enable_pq=true时有效。 |
| lvq_nclus | 取值范围:2~128 默认值:128 | LVQ的量化向量的长度和质量。该参数在enable_pq=true,且quantization_type='lvq'时有效。 |
| graph_degree | 取值范围:48~256 默认值:96 | 图索引中每个节点的出度。 |
| build_instruction_set | 取值范围:SSE4/ AVX2/ AVX512F/ AVX512BWDQ/ NEON/ AUTO/ NONE 默认值:AUTO | 指定构建所用的指令集。 |
| params_adaptive | 取值范围:T/F 默认值:T | 当maintenance_work_mem内存不足时,是否支持索引参数自适应降级(例如:子图构建降级为磁盘构建)。
|
在向量索引中一次性进行大量的删除或者更新操作,或者频繁的删除或者更新操作,可能会导致索引质量下降,索引膨胀,搜索时间变长的问题。
查询索引健康状态
使用函数gs_diskann_inspect('diskann_index_name')可查询索引健康状态,包括索引磁盘使用、PQ压缩准确度、索引图连接健康度、Vacuum状态等信息。每条信息的返回类型为record,需要用select * from语句获取,函数参数为索引名,由建索引时指定或者按照默认规则生成,该名可以使用\d table_name或者从系统表pg_index进行查询。对于包含向量索引的分区表,该函数会返回所有分区下索引的状态,查询某一具体分区的索引请使用gs_diskann_inspect('diskann_index_name', 'index_partition_name')只显示该分区索引的健康状态,索引分区名可以通过查询系统表pg_partition获取。
在使用查询索引健康状态函数分析索引数据过程时若有大量DML操作可能会造成分析结果有误差甚至分析函数执行失败并报错,执行过程中数据变化量若小于原数据总量的5%则输出结果可信度较高。在设置subgraph_count>1,build_with_quantized_vector=true,graph_degree<96等索引构建质量参数时,可能会导致索引构建质量下降,应谨慎调整。
示例
--创建表 gaussdb=# CREATE TABLE "test" ("id" BIGINT PRIMARY KEY, "repr" floatvector (128)); --随机插入数据 gaussdb=# CREATE OR REPLACE FUNCTION float_random_array(dims int,range int) RETURNS float[] AS $$BEGIN RETURN ARRAY(SELECT (random() * range)::int FROM generate_series(1, dims));END;$$ LANGUAGE plpgsql; gaussdb=# INSERT INTO test SELECT i,floatvector(float_random_array(128,100)) FROM generate_series(1,1000) as i; --创建索引 gaussdb=# CREATE INDEX diskann_sift1m_l2 on test USING GsDiskANN (repr L2) with (pq_nseg=128, pq_nclus=16, num_parallels=50, enable_pq=true, using_clustering_for_parallel=false); --向量ANN检索 gaussdb=# SELECT "id" FROM "test" ORDER BY "repr" <-> '[ 23.0,0.0,0.0,0.0,1.0,5.0,43.0,114.0,3.0,0.0,0.0,0.0,14.0,121.0,120.0,78.0,81.0,4.0,0.0,0.0,31.0,126.0,23.0,18.0,126.0,12.0,0.0,0.0,0.0,1.0,0.0,10.0,0.0,0.0,0.0,0.0,8.0,29.0,96.0,43.0,0.0,0.0,0.0,0.0,1.0,81.0,126.0,44.0,126.0,1.0,0.0,0.0,1.0,45.0,66.0,96.0,126.0,0.0,0.0,0.0,1.0,16.0,12.0,63.0,1.0,2.0,0.0,0.0,11.0,40.0,26.0,0.0,5.0,20.0,28.0,1.0,0.0,17.0,36.0,5.0,126.0,45.0,10.0,1.0,0.0,2.0,12.0,29.0,126.0,6.0,0.0,0.0,2.0,110.0,96.0,46.0,18.0,13.0,0.0,0.0,3.0,5.0,1.0,2.0,29.0,50.0,30.0,7.0,8.0,3.0,0.0,1.0,55.0,24.0,14.0,5.0,9.0,15.0,8.0,10.0,10.0,1.0,0.0,0.0,19.0,79.0,16.0,4.0 ]' LIMIT 10; --插入数据 gaussdb=# INSERT INTO test(id,repr) VALUES(99999999,'[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]'); gaussdb=# SELECT COUNT(*) FROM test; --更新数据 gaussdb=# UPDATE test SET repr = '[ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]' WHERE id = 99999999; gaussdb=# SELECT * FROM test WHERE id = 99999999; --删除数据 gaussdb=# DELETE FROM test WHERE id = 99999999; gaussdb=# SELECT COUNT(*) FROM test; --清理数据 gaussdb=# VACUUM test; --查询索引健康度 gaussdb=# SELECT * FROM gs_diskann_inspect('diskann_sift1m_l2'); --删除函数 gaussdb=# DROP FUNCTION float_random_array; --删除索引 gaussdb=# DROP INDEX diskann_sift1m_l2 ; --删除表 gaussdb=# DROP TABLE test;