把向量塞进Postgres,在200万条数据时几乎是无敌的:查询8毫秒返回,备份统一,运维开销为零。等到企业文档入库管线扩到5000万条1536维向量,并且持续流式更新、带行级安全过滤,情况就变了——复制延迟飙到40分钟,autovacuum把NVMe的IOPS钉在100%,带租户过滤的向量查询连10条结果都返回不了。
![]()
这是一道物理题:多层邻近图不能当成B-tree索引来用。HNSW(分层可导航小世界图)必须常驻内存,才能避免贪心遍历时的随机磁盘寻道。5000万条1536维float32向量,索引体积超过580GB,而实例内存只有256GB。
在一台64 vCPU、256GB内存、io2 NVMe存储的云数据库实例上,从500万条扩到5000万条1536维向量的实测数据里,5000万行未分区HNSW那一行最刺眼:584GB索引溢出256GB系统内存后,每一步多跳图遍历都要触发几十次8KB随机页读取。同时,严格多租户查询(WHERE tenant_id = 42)的召回率掉到61.5%。
一家区域性金融机构的夜间文档重建索引任务更新了8%的行。PostgreSQL的MVCC机制让每次UPDATE都写入新元组,并改动几十个双向HNSW邻居指针,在WAL里产生14倍写放大,直接冻住了早上的copilot查询。
三个生产级瓶颈
第一是MVCC写放大与VACUUM图修复卡顿。删除或更新一行,并不会立刻把它从HNSW索引里摘掉。VACUUM必须走一遍HNSW图,解链死元组、修复双向邻居列表(每个节点M=16到64条连接)。持续流式写入下,autovacuum追不上图指针修复的速度,表膨胀和WAL复制延迟随之而来。
第二是选择性WHERE子句上的过滤召回悬崖。HNSW遍历的是所有向量构成的单张连通图。当查询带上高选择性过滤条件(比如WHERE tenant_id = 'bank_b',只命中表的0.2%),后过滤的ef_search候选常常丢弃99%的已访问节点,返回结果不足k条。即便用上pgvector 0.8.0+的迭代索引扫描(hnsw.iterative_scan = relaxed_order),扫过99.8%的不匹配图节点,也会把10毫秒的查询变成数秒的随机I/O风暴。
第三是OLTP与向量内存争抢。未量化的float32 HNSW页灌满shared_buffers后,会挤掉核心应用的热B-tree索引和事务表。RAG搜索并发一冲高,同一实例上的结账、计费、认证SQL查询跟着遭殃。
三条扩容规则
按租户或域哈希分区向量表。不要在5000万多租户行上建单张巨型HNSW索引,用PostgreSQL声明式分区(按tenant_id做LIST或HASH),让每个分区维护一张紧凑独立的HNSW图,干净地装进缓存,在不扫描无关租户的前提下拿到99%以上的过滤召回。
把vector(float32)换成halfvec(float16)或二值量化。1536维嵌入按32位浮点存储,浪费一半内存带宽。换成halfvec(1536)索引体积减半,召回损失测不出来;Matryoshka截断到768维再加二值量化,内存占用最多可降96%。
冷语料上NVMe感知的StreamingDiskANN(pgvectorscale)。单个分区超过2000万条向量时,用pgvectorscale的diskann索引访问方法配合统计二值量化(SBQ)替换内存型HNSW。DiskANN把压缩后的图节点顺序存放在本地NVMe SSD上,P99延迟低于25毫秒,内存成本只有内存型HNSW的20%。
"Just use Postgres"是个靠谱的工程默认值——前提是按图结构的物理规律设计存储布局,而不是假设默认HNSW设置能无限扩展。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.