PostgreSQL码农集散地

这样用pgvector会踩大坑

pgvector hnsw高频更新场景的坑

知识类、音视频内容等的向量更新操作应该是比较少见的, 因为这些内容基本不变.

但是有些场景向量更新可能会比较频繁, 例如反映用户近期行为、喜好的向量.

如果向量频繁更新, 使用pgvector hnsw索引时, 可能与到巨大的坑.图链断裂召回下降、膨胀性能变差等。

注意这里说的是hnsw.

问题1: 高频更新可能导致hnsw索引的图结构出现问题, 查询的recall变差

pgvector hnsw索引的结构为多层图, 类似跳表结构.  顶层稀疏, 每个点都有进入下一层的连接. 最下层最全(包含全部点).  vector点会出现在第一次出现的层以及后面的所有层.

Level 0: 10 → 25 → 40  
Level 1: 10 → 25 → 35 → 40  
Level 2: 10 → 18 → 25 → 35 → 40  
Level 3: 10 → 18 → 25 → 30 → 35 → 40  

不是每个点在每一层都有入口(只有出现在顶层的点才会在每一层都有入口). 如果一个向量的在它所处的最高层(不一定是整张图的最高层)入口点消失, 仅与它相邻的点(并且这些点没有更上层入口)就会找不到, 导致断链.

例如有4层的图(1为最高,4为底层), v1所处最高层是第2层, v1连接了v2,v3. 然后v1被修改或删除了, 如果v2,v3没有其他的点关联, 并且v2v3它们在上层也没有入口点, 那么v2,v3也就变成了孤点, 永远也不能被查到.

这个问题需要vacuum修复.

详见: https://deepwiki.com/pgvector/pgvector/6.2-updating-vectors-and-vacuum

graph 未修复之前, 查询可以照样查, 但是recall变差. 这个是符合索引预期的, 因为hnsw本身就是近似向量相似搜索, 并不是返回精准的结果.

问题2: 垃圾回收很慢很慢

垃圾回收会自动回收索引的垃圾, 如果表上有hnsw索引, vacuum可能非常慢.

因为垃圾回收可能涉及重新构图, 所以会很慢很慢.

官方的建议是重建索引, 然后vacuum.

-- Reindex with minimal impact on operations  
REINDEX INDEX CONCURRENTLY index_name;  
VACUUM table_name;  

hnsw 索引build索引分为2个阶段, 1个阶段在内存中完成, 另一个阶段相当于图已经持久化, 没有进入索引的向量相当于insert的方式插入索引内, 因此第二个阶段非常慢.  vacuum操作就处于第二阶段.

HNSW 指数构建分为两个阶段: https://deepwiki.com/pgvector/pgvector/4.1-hnsw-index

内存阶段:

  • 创建Elements并将其插入到内存图中
  • 使用搜索算法发现邻居
  • 随着向量的添加,graph逐渐建立
  • 继续,直到所有向量都处理完毕或内存(maintenance_work_mem)耗尽

磁盘阶段:

  • 如果graph超出内存,则触发磁盘阶段
  • 内存中的graph被刷新到磁盘
  • 使用磁盘算法逐个插入剩余向量
  • 在索引创建期间插入不会被 WAL 记录(只有最终状态会被 WAL 记录)

为了加速hnsw索引创建, 建议:

  • 增加内存, maintenance_work_mem.
  • 开启并行: max_parallel_maintenance_workers, max_parallel_workers
  • 当内存有限时, 可以考虑压缩向量大小(维度、或者精度), 如halfvec, binary vec.
  • 在使用binary vec的情况下提高recall? 可以使用表达式索引, 使用原始vec进行rerank. 例如 CREATE INDEX ON items USING hnsw ((binary_quantize(embedding)::bit(3)) bit_hamming_ops);   , select x from (select x from tbl order by binary <-> ...) t order by vec <-> ...;
    • 参考: https://deepwiki.com/tensorchord/VectorChord/1-overview
    • https://deepwiki.com/tensorchord/VectorChord/1.2-features-and-capabilities
    • residual_quantization : https://deepwiki.com/tensorchord/VectorChord/2-architecture  通过该residual_quantization选项启用后,该技术仅存储向量与其质心之间的差异(残差),从而大大减少了高维向量的存储要求。
    • vectorchord 的实现好像有点像基于量化后的索引, vectorchord支持1字节量化(比pgvector binary 1bit量化更高精度, 比4字节和2字节halfvec又更低精度.). 但是内置自动使用量化vec <x> 量化query 以及自动进行 非量化vec <x> 非量化query rerank.

推荐解法

所以hnsw接口的话使用要看准场景. 如果你想解决以上问题, 还可以选择其他索引接口, 例如vectorchord 这个是多层cluster结构(底层是ivfflat):

非得使用pgvector hnsw时,向量更新频繁的表超过100万向量可以考虑分区表,同时提升vacuum内存和回收频率。注意要采用reindex concurrently+vacuum的模式(提升maintenance mem+parallel). 
其他字段更新频繁考虑加大fillfactor使用HOT。

参考

https://deepwiki.com/pgvector/pgvector/4.1-hnsw-index

https://deepwiki.com/pgvector/pgvector/6.2-updating-vectors-and-vacuum

最后,推广一下if club 5.24上海沙龙,欢迎报名