HNSW 与近似最近邻
向量检索的默认索引。分层图 + 贪心下沉把 O(N) 降到近似 O(log N),代价是召回不再是 100%,而且图结构常驻内存。M、ef_construction 定索引质量,ef_search 是唯一能在线拧的旋钮 —— 拿延迟换召回。
也叫:HNSW · ANN · 近似最近邻 · ef_search · ef_construction · 向量索引
原理拆解:HNSW出自 T2-4
| 参数 | 作用 | 调大的后果 | 改它要不要重建 |
|---|---|---|---|
M | 每个节点的邻居数(图的稠密度) | 召回↑、内存↑、构建慢 | 要重建 |
ef_construction | 建索引时的候选队列长度 | 索引质量↑、建索引更慢 | 要重建 |
ef_search | 查询时的候选队列长度 | 召回↑、查询变慢 | 不用,运行时直接调 |
一句很实在的工程回答:「召回不够,先调 ef_search 看看」—— 它是唯一能在线调的旋钮,不重建索引就能试。而 M 和 ef_construction 一旦建好就固定了。
其他索引:IVF 先聚类、只搜几个簇,内存友好但召回略低;DiskANN 把索引放 SSD,适合内存放不下的超大规模;量化(标量/乘积/二值)用精度换内存,能压到原来的几分之一。
ef_search 调太小会漏;删除只能软删,打标记跳过,删多了得重建。HNSW(Hierarchical Navigable Small World,分层可导航小世界)是目前最主流的 ANN 索引,pgvector、Qdrant、Milvus 默认都用它。
直觉:像查地图一样找最近的餐馆
想象你要从北京找上海某条街的一家店:
- 顶层:只有城市级节点(北京、上海、广州)。你一步跳到上海。
- 中层:上海的各个区。你跳到黄浦区。
- 底层:所有街道和店铺,你在附近小范围精找。
HNSW 就是这个结构——多层图,上层稀疏用来"长距离跳跃",下层稠密用来"精细定位"。每层的节点通过边连成一张"小世界"网络(任意两点间跳几步就能到),检索时从顶层入口出发,每层贪心地走向离目标更近的邻居,走不动了就下沉一层,直到底层输出 Top-K。
复杂度从暴力检索的 O(N) 降到大约 O(log N)——这就是它能撑起毫秒级检索的原因。
三个必须知道的参数
| 参数 | 作用 | 调大的后果 |
|---|---|---|
M |
每个节点的邻居数(图的稠密度) | 召回↑、内存↑、构建慢 |
ef_construction |
建索引时的候选队列长度 | 索引质量↑、建索引更慢 |
ef_search |
查询时的候选队列长度 | 召回↑、查询变慢(唯一能在线调的旋钮) |
面试要点:ef_search 是运行时参数,可以不重建索引直接调——「召回不够先调 ef_search 看看」是很实在的工程回答。而 M 和 ef_construction 一旦建好就固定了,要改得重建。
HNSW 的代价(追问重点)
- 内存开销大:图结构常驻内存,向量涨到千万级时内存可能成为瓶颈——上面「轻装冒进」那个故障就是这么来的。
- 召回不是 100%:本质是近似,
ef_search调太小会漏。 - 删除是软删除:图结构不好真删,一般打标记跳过,删多了需要重建(下面细讲)。
其他索引:IVF(先聚类再只搜几个簇,内存友好但召回略低)、DiskANN(索引放 SSD,适合超大规模内存放不下的场景)、量化(标量/乘积/二值量化,用精度换内存,能压到原来的几分之一)。知道它们的存在和适用场景即可。
以上节选自T2-4 向量数据库与 HNSW:原理与选型,读全文能看到前后语境。