Chapter 02
索引原理:在三角里选一个点
上一章定义了向量、相似度度量与 recall@k。这一章回答一个尖锐的问题:当数据到了亿级,怎么在不暴力扫全量的前提下找到最近邻——以及为此付出什么代价。
本章骨架
- 为什么精确 KNN 在亿级不可行——维度灾难如何摧毁所有树形索引。
- IVF / HNSW / PQ / DiskANN 各自的运行机制、可调旋钮、以及"带来的代价"。
- recall × 延迟 × 内存 三角作为统一框架:选索引 = 选你愿意牺牲哪个角。
整章只有一个核心主张:所有 ANN 索引都是 recall × 延迟 × 内存 这个三角里的一个点。每一种索引——IVF、HNSW、PQ、DiskANN——都是为了往某个角靠拢,而代价是远离另一个角。读完后,面对一个陌生索引或一组陌生参数,能力不是背它的定义,而是定位它在三角里的位置、说出它牺牲了什么。先把这张图刻进脑子。
00为什么精确 KNN 不 scale
暴力最近邻在小数据上是对的答案,在亿级上是不可能的答案——而高维度让所有"聪明"的树形索引退化回暴力。
精确 K 近邻(brute-force KNN)的机制只有一句话:对每个 query,逐一计算它到全部 N 个库向量的距离,排序取前 k。每次距离计算要遍历 d 个维度,于是单 query 的复杂度是 O(N·d)。在 N=100 万、d=768 的规模上,每次查询要做约 7.7 亿次乘加,单机可以扛;到 N=10 亿,每次查询变成约 7700 亿次浮点运算,且光是把向量装进内存就要 1e9 × 768 × 4 字节 ≈ 3 TB(float32)。这不是"慢一点",是从根上做不到在线服务。
维度灾难:树形索引为什么失败
低维空间(2D、3D)有成熟的精确加速结构——KD-tree、ball-tree、R-tree——靠递归切分空间,把搜索从 O(N) 降到 O(log N)。它们的前提是"能用一个边界把远的点整片剪掉"。高维空间摧毁这个前提:当维度升到几百,所有点对之间的距离趋于相等——最近邻距离与最远邻距离的比值趋近 1。直观地说,高维球壳的体积几乎全部集中在表面附近,任意两个随机点都"几乎一样远"。
后果是 KD-tree 的剪枝条件几乎永远不成立:每次想剪掉一个分支,超球与超平面总是相交,于是回溯访问几乎所有节点,复杂度退化回 O(N)——而且常数比纯暴力还大(多了树遍历的指针跳转开销)。这就是整个领域的分水岭:精确树法在高维失败,于是只剩两条路——要么放弃精确(近似最近邻 ANN,用图法/量化法换概率正确),要么放弃稠密遍历(也是 ANN)。本章后面的全部索引都是 ANN,没有一个保证返回真正的最近邻。
距离趋同破坏的是"用边界剪枝"的能力,但没有破坏"沿着邻接关系走向更近的点"这件事。图法(HNSW、DiskANN)放弃剪枝、改用导航:从一个入口点顺着边贪心地往目标靠。量化法(PQ)则放弃存全精度、改用近似距离。两者都不依赖维度灾难所摧毁的那个假设,所以它们 scale,而 KD-tree 不行。
既然暴力 KNN 在大数据上不可行,那它是不是永远该被索引取代?什么场景下建索引反而是错的决定?
展开
暴力(Flat)在两个条件同时成立时是最优解:N 很小(万级以下)且要求 recall = 1.0。原因有三:① 建任何近似索引都有构建开销与内存开销,N 小的时候这笔投入收不回;② 近似索引牺牲 recall,而很多场景(小型知识库、精排候选集打分)不能容忍漏检;③ 现代 SIMD(AVX-512)让暴力距离计算快到离谱,万级向量的暴力扫描往往就是亚毫秒。所以"先上索引"是一个常见的过度工程——先问 N 有多大、recall 要求多高,再决定要不要索引。
01IVF:先聚类,再只看几个桶
把向量空间预先切成若干区域,查询时只进最相关的几个区域扫描——用"少看一些"换速度。
IVF(Inverted File,倒排文件索引)的机制分建索引与查询两步。建索引期:对全集做一次 k-means 聚类,得到 nlist 个质心;这些质心把空间划分成 nlist 个 Voronoi cell(每个点归属于离它最近的质心);然后为每个 cell 维护一张倒排表,记录落在该 cell 里的所有向量。查询期:先做一次"粗筛"——计算 query 到全部 nlist 个质心的距离,挑出最近的 nprobe 个 cell;再做"精排"——只在这 nprobe 个 cell 的倒排表里暴力扫描。如果 nprobe 远小于 nlist,扫描量就从 N 降到约 N · nprobe / nlist。
nprobe:召回与延迟的直接拨杆
nprobe 是 IVF 最重要的查询期旋钮,它直接坐落在三角的 recall–延迟边上。nprobe=1 最快,但有一个结构性失败模式——边缘漏检:query 落在某个 cell 的边界附近,它真正的最近邻却在隔壁 cell 里(见图 2.2);只探测 query 所在的那一个 cell,隔壁那个真近邻永远不会被看到。调大 nprobe 会把相邻的 cell 也纳入扫描,召回随之上升,但扫描量与延迟同步上升。nprobe 一路调到等于 nlist 时,IVF 就退化成全量暴力——recall=1.0,但也失去了全部加速。
nlist(建期):质心数量,经验值取 √N 量级(百万级数据取 1k–4k)。nlist 越大,每个 cell 越小、精排越快,但粗筛阶段要比对的质心更多、且更容易边缘漏检(需要更大 nprobe 补偿)。nprobe(查询期):探测多少个最近 cell,不改索引、随查询调,是线上换 recall↔延迟 的拨杆。两者分工清晰:nlist 定"格子多细",nprobe 定"看几个格子"。
IVF + PQ:把 cell 内的向量压成码
纯 IVF 的倒排表里存的还是全精度向量,内存占用与暴力相当(Sift1M 上约 520MB)。生产里 IVF 几乎总和 PQ(下一节)组合成 IVFPQ:cell 内不存原始向量,而存 PQ 压缩码,内存可降一到两个数量级,于是单机能扛十亿级。代价是 recall 再损一档——PQ 的近似距离叠加 IVF 的边缘漏检,两层近似累积。
纯 IVF:recall 0.7–0.95、单 query 1–9ms、内存约 520MB(随 nprobe 在 recall 与延迟间滑动)。IVFPQ:内存可压到几十 MB 量级、可扩到十亿级,recall 比纯 IVF 再低一档。数量级参考,非本地复现。
带来的代价:IVF 的 recall 上限被聚类质量锁死——k-means 的边界是静态的,边缘漏检无法靠查询期完全消除,只能用更大的 nprobe 缓解,而那直接吃延迟。IVF 还需要一次全集训练(k-means)才能建索引,数据分布漂移后质心会失准、召回静默下降,需要周期性重训。相比之下图法(HNSW)无需训练、增量插入更友好——这是 HNSW 成为默认而 IVF 退居"内存敏感/超大规模"场景的原因之一。
02HNSW:跳表思想叠在邻近图上
把"高速公路 + 乡间小路"的分层导航搬到向量空间——顶层大跨步逼近,底层小步精定位,近似对数复杂度。
HNSW(Hierarchical Navigable Small World)是当前事实上的默认索引,几乎所有向量库的首选。它的结构是把跳表(skip list)的思想叠加在一张邻近图上,形成多层结构:底层(layer 0)是一张包含全部点的邻近图,每个点连着若干个近邻;往上每一层都是下层的稀疏子集,层级越高点越少、边越长(长程边)。每个新插入的点,按指数分布随机抽一个"最高层数"——绝大多数点只在底层,少数点穿透到高层充当长程枢纽。
搜索过程:greedy best-first 逐层下降
查询从顶层的固定入口点开始:在当前层做贪心 best-first 搜索——不断移动到比当前更接近 query 的邻居,直到走到一个局部最近点(没有邻居更近了);然后以这个点为入口降到下一层,重复。越往下层越密,搜索步长越来越细。到了 layer 0,用一个大小为 ef_search 的动态候选堆做最后收敛——维护一个候选集,持续扩展直到堆里前 k 个稳定下来。整个过程跳过了大量无关区域,复杂度近似对数级 O(log N)。
M(建期):每个节点的最大边数(度),典型 16–64。M 越大图越密,同 recall 下导航更稳、recall 上限更高,但内存(要存更多邻接)和建索引时间都升。ef_construction(建期):建图时每步保留的候选宽度,越大建出的图质量越高、建得越慢。ef_search(查询期):layer 0 收敛时的候选堆大小,直接在 recall↔延迟 间换——这是线上最常调的拨杆,调大召回升、延迟升。
为什么 HNSW 是默认
原因只有一句:在相同 recall 下,HNSW 的延迟与吞吐通常优于所有其它索引。它不需要训练(不像 IVF 的 k-means),支持增量插入,且查询期只用一个 ef_search 就能平滑地在 recall–延迟边上滑动。在 ann-benchmarks 这类公开基准上,HNSW 在 recall–QPS 曲线的高召回区段长期领先。
带来的代价(一)吃 RAM:HNSW 把全精度向量 + 每个点 M 条邻接边全部驻留内存。邻接信息本身就是一笔可观开销,叠加原始向量后,总内存通常是裸向量的 1.5–2 倍。这就是图 2.1 里 HNSW 远离"内存"角的原因——它用 RAM 买速度和召回。亿级数据上这意味着上百 GB 内存,成本陡增(这正是下文 DiskANN 存在的理由)。
带来的代价(二)删除难:删一个点会切断图的连通性,原本经它中转的路径随之断掉,召回受损。工程上的常规做法是 tombstone 软删除——标记为已删但保留在图里、查询时过滤掉,再周期性地重建索引把墓碑真正清除。频繁增删的场景下,HNSW 的图会逐渐劣化,重建是必须的运维动作。
论文《Down with the Hierarchy: the "H" in HNSW stands for Hubs》(2024) 提出一个反直觉结论:在高维数据上,HNSW 的分层结构(那个 "H")对性能贡献甚微——真正起导航作用的是图里自然涌现的少数高连通度 hub 节点,去掉分层、保留单层图加 hub,性能几乎不变。这说明 HNSW 的"魔法"更多来自小世界图本身,而非分层。DiskANN 的单层 Vamana 图(下文)某种程度上是这一观察的印证。
HNSW 的 ef_search 和 IVF 的 nprobe 都是"查询期换 recall↔延迟"的旋钮。它们换的是同一种东西吗?背后的机制差别在哪?
展开
表面效果一样(调大都是召回升、延迟升),但机制不同。nprobe 控制的是扫描范围的宽度——看几个 cell,本质是"多看一些候选区域来对抗边缘漏检",是一种空间覆盖。ef_search 控制的是图搜索时的候选堆深度——保留多少个待扩展候选来对抗"贪心走进局部最优、错过真近邻",是一种搜索回溯的容量。一个对抗聚类边界的硬切分,一个对抗贪心导航的早停。理解这层差别,才能在调参时知道自己在补哪种漏检。
03PQ 与量化家族:把向量压成字节
不改变"找最近邻"这件事,只把向量本身压缩——用近似的距离换断崖式的内存下降。
PQ(Product Quantization,乘积量化)是把内存往下压的主力机制。它的做法:把 D 维向量切成 m 段连续的子向量;对每一段子空间独立跑一次 k-means,训出 2^nbits 个质心(nbits=8 即每段 256 个码本条目);然后每段只存"离它最近的那个质心的 ID",占 1 个字节。举例:128 维向量切成 m=8 段、每段 nbits=8,原始 128 × 4 = 512 字节被压成 8 × 1 = 8 字节,约 64× 压缩。"乘积"二字来自这里——整个空间被表示成 m 个子空间码本的笛卡尔积,256^8 种组合用 8 字节就能编码。
ADC:query 不量化,查表累加
压缩后怎么算距离?用 ADC(Asymmetric Distance Computation,非对称距离计算):query 向量不量化,保持全精度。查询时先为 query 预计算一张距离表——它的每个子段到对应子空间 256 个质心的距离,得到一张 m × 256 的表。之后计算 query 到任意库向量的距离,就退化成按库向量的 m 个码 ID 查表、累加,m 次查表加法即可。"非对称"指的就是 query 全精度、库向量量化这种不对称——它比"两边都量化"的对称方式更准,因为 query 侧没有量化误差。
量化家族的其它成员
PQ 是表达力最强但也最复杂的量化。还有两个更简单的表亲,生产里用得同样多:
- Scalar quantization(标量量化,SQ):最朴素——按每一维的
min/max把 float32 线性映射到 int8,4× 压缩,recall 损失极小(int8 的 256 级对大多数 embedding 足够)。几乎是免费的内存优化,很多库默认开。 - Binary quantization(二值量化,BQ):最激进——每一维只取符号(正→1,负→0),float32 变 1 bit,32× 压缩;距离用 Hamming 距离算,检索快可达 40×。单看精度它损失巨大,但靠 oversampling + rescore 救回来:先用二值码超量召回(取比 k 多几倍的候选),再用保留的全精度向量对这批候选重新精确打分排序,recall 能拉回约 0.98。
量化方法宣称的"实际 recall"几乎都不是压缩码本身的功劳,而是挂在 rescore 这一步:用压缩码快速圈出一批候选,再用原始(或更高精度)向量对候选重排。没有 rescore 的纯二值/纯 PQ 检索,recall 会惨不忍睹;有了 rescore,同一套压缩码就能逼近全精度。所以看到"binary quantization 召回 0.98"这种数字,要立刻反问:oversampling 倍数是多少、rescore 用的是什么精度的向量——召回是在那里产生的,不在量化里。
带来的代价:纯 PQ 的 recall 可以掉到约 50%——量化误差让"哪个更近"的判断频繁出错。所以 PQ 几乎从不单独使用,总是配 IVF(成 IVFPQ,先用 IVF 缩小范围再 PQ 算距离)或配图法(DiskANN 用 PQ 码在内存里粗筛、SSD 上的全精度向量做 rescore)。此外 PQ 需要训练码本,和 IVF 一样面临分布漂移失准的问题。量化的统一代价可以归成一句:所有量化都在用"距离的精度"换"存储的体积",而把精度补回来的唯一办法是保留一份高精度向量做 rescore——那份向量又得占地方。
04DiskANN / Vamana:把图放到 SSD 上
让图和全精度向量躺在 SSD、内存只放压缩码——用"磁盘 IO 换内存成本",把十亿级拉进单机预算。
DiskANN 存在的理由,直接来自 HNSW 的代价:HNSW 把全精度向量加图全压在 RAM 里,十亿级要上百 GB 内存,贵得离谱。DiskANN 的机制是分层存储——把图结构和全精度向量放到 SSD 上,内存里只保留每个向量的 PQ 压缩码。查询时用内存里的 PQ 码做快速粗筛(沿图导航时用近似距离决定走向),需要精确判断时才去 SSD 读对应的全精度向量做 rescore。结果是单机 64GB RAM + 一块 NVMe SSD 就能跑 10 亿个点,recall@1 > 95%、延迟 < 5ms。
Vamana 图与 α:故意留长程边
DiskANN 用的图叫 Vamana,它是单层扁平图(不像 HNSW 那样分层)。它的核心创新是建图时 RobustPrune 剪枝里的 α 参数。剪枝决定每个节点保留哪些边:HNSW 的剪枝隐含 α=1(偏向保留短边、剪掉被"遮挡"的长边);Vamana 用 α>1(典型 1.2)放松剪枝条件,故意保留更多长程边。多保留长程边的效果是图的直径变小——任意两点之间的跳数更少,于是 beam search 用更少的跳数就能收敛。
在内存里跑图(HNSW),瓶颈是算力(距离计算);在 SSD 上跑图(DiskANN),瓶颈换成了 磁盘随机读的次数——每多走一跳就多一次 SSD 随机 IO,而 SSD 随机读比内存慢几个数量级。Vamana 用 α>1 把图直径压小、减少跳数,本质是在减少 SSD 随机读次数。HNSW 不在乎这点(内存里多跳几下无所谓),DiskANN 把它当成头等设计目标。同一个"图导航",因为底层介质不同,最优结构就不同。
R(度):每节点最大边数,类比 HNSW 的 M。L(beam/搜索宽度):beam search 的候选束宽,类比 ef_search,查询期换 recall↔延迟。α(建期直径):剪枝松紧,>1 留更多长程边、压小直径、减少 SSD 跳数;这是 Vamana 区别于 HNSW 的关键旋钮。
带来的代价:DiskANN 把延迟从"内存级"抬到"SSD 级"——虽然靠小直径把单机十亿级压进 5ms,但这仍比纯内存 HNSW 的亚毫秒慢一截,且强依赖 SSD 性能(机械盘上不可用,云上要选高 IOPS 卷)。它换来的是内存成本的断崖式下降。在图 2.1 里 DiskANN 和 IVFPQ 一起压向"内存"角,区别在 DiskANN 用图导航+SSD rescore 守住了更高的 recall。选 DiskANN 的判据很清晰:数据量大到 HNSW 的内存账单不可接受,且能接受毫秒级(而非亚毫秒)延迟。
05LSH:为什么基本被淘汰
用一族哈希让近向量高概率撞同一个桶——思路优雅,但在高维高召回需求下打不过图法。
LSH(Locality-Sensitive Hashing,局部敏感哈希)的机制:设计一族特殊的哈希函数,使得距离近的向量有较高概率被映射到同一个桶(与普通哈希刻意打散相反)。建索引时把所有向量哈希进桶,几乎是瞬时的;查询时只算 query 所在桶(及少数邻桶)里的向量距离。理论上很美——亚线性查询、建索引极快。
为什么被取代:要达到高 recall,单个哈希撞桶的概率不够,必须用很长的签名 + 很多张哈希表来提升命中概率,这导致内存急剧膨胀;而且它同样受维度灾难拖累(高维下"近的撞桶"这件事本身就变难)。更致命的是它的概率性——同一个 query 的性能不可预测,召回波动,不像 HNSW 那样行为确定、可在 recall–延迟边上精确调节。在同等 recall 目标下,HNSW 延迟更低、内存更省、行为可复现,于是 LSH 在通用向量检索里基本退场。
残留价值:LSH 没有完全消失——它的建索引极快、写入便宜,在某些写多读少、对召回要求不高、或需要快速去重/近似匹配的场景仍有用武之地。但作为"在线高召回最近邻检索"的主力,它已被图法取代。
06统一框架:在三角里推理一次
前面五种机制的全部旋钮,最终都服务于同一个动作——在 recall × 延迟 × 内存 三角里把你的点放到正确位置。
把图 2.1 落到一个具体场景上推一遍。需求:1000 万条 768 维向量,延迟预算 20ms(P99),内存预算 8GB。该选哪个索引?不背结论,用前面的旋钮一步步推。
第一步,算裸向量内存:1e7 × 768 × 4 字节 ≈ 30.7 GB(float32)。光裸向量就 4× 超了 8GB 预算——这一步就否决了任何"全精度驻内存"的方案,包括纯 HNSW 与纯 IVFFlat。三角的"内存"角已经成为硬约束。
第二步,看内存约束逼向哪里:要塞进 8GB,必须量化。两条路:① HNSW + scalar quantization(int8,4×)把向量压到约 7.7GB,再加图的邻接开销会逼近上限,配合 halfvec(fp16,2×)或更激进的量化可落进预算,且守住低延迟与高 recall;② IVFPQ,PQ 压到几 GB 内绰绰有余,但 recall 要靠 rescore 兜。
第三步,看延迟约束:20ms 对 1000 万规模相当宽松,HNSW 在这个量级的查询通常是个位数毫秒,延迟角完全不紧张——所以不必为省延迟牺牲别的。结论:优先 HNSW + 量化(scalar/fp16),用 ef_search 把 recall 顶到需要的水平;若量化后内存仍超或未来要扩到亿级,再退向 IVFPQ 或 DiskANN 换更狠的压缩。三个约束里,内存是这道题的紧约束,它决定了"必须量化",而延迟宽松给了 HNSW 发挥空间。
这就是整章的方法论:先看哪个角是硬约束,让它筛掉一批索引;再看剩下的旋钮能不能把另外两个角调到达标。没有"最好的索引",只有"在你这三个预算下、紧约束允许的索引"。下面这张总表把六种索引在三角里的位置一次性列清。
| 索引 | recall | 延迟 | 内存 | 关键旋钮 | 何时用 |
|---|---|---|---|---|---|
| Flat(暴力) | 1.0(精确) | 高(O(N·d)) | 高(全精度驻留) | 无 | N < 1 万且要 recall=1.0;精排候选打分 |
| IVF(IVFFlat) | 0.7–0.95 可调 | 中(nprobe 控) | 高(≈暴力) | nlist(建)· nprobe(查) | 需快于暴力、内存够、可接受重训 |
| IVFPQ | 中偏低(再损一档) | 中 | 很低(压缩码) | nlist · nprobe · m · nbits | 十亿级、内存吃紧、可配 rescore |
| HNSW | 高(同延迟最优) | 低(≈对数级) | 高(1.5–2× 原始) | M · ef_construction · ef_search | 通用默认;内存够、要低延迟高召回 |
| DiskANN(Vamana) | 高(>95%@1) | 中(SSD,~5ms) | 很低(仅 PQ 码驻内存) | R · L · α | 十亿级、内存预算紧、可接受毫秒级 |
| LSH | 低且波动 | 中(不可预测) | 高(多表膨胀) | 签名长度 · 表数量 | 基本淘汰;写多读少/快速去重的残留场景 |
Flat 守 recall 角;HNSW 占 recall–延迟边、用 RAM 买单;IVF 用 nprobe 在边上滑;IVFPQ / DiskANN 压向内存角、用 rescore 把 recall 捞回来;LSH 出局。任何新索引来了,先问它把自己摆在三角的哪个角、牺牲了哪一个。
自测
先合上屏幕,在脑子里或纸上把每题答完,再展开对照。能复述出"机制"而非"结论"才算过。
-
为什么 KD-tree 在 768 维上会退化成近似线性扫描?这和"图法 / 量化法胜出"是同一个原因吗?
展开
维度灾难:高维下所有点对距离趋同(最近邻/最远邻距离比 → 1),KD-tree 赖以剪枝的"用边界整片剪掉远点"的前提失效,超球与超平面几乎总相交,回溯访问几乎所有节点 → 退化回 O(N) 且常数更大。这正是图法(沿邻接导航,不依赖剪枝)与量化法(近似距离)胜出的根因——它们都不依赖被维度灾难摧毁的那个剪枝假设。
-
IVF 的 nprobe=1 在什么情况下会漏掉真正的最近邻?调大 nprobe 为什么能缓解,代价是什么?
展开
边缘漏检:query 落在它所属 cell 的边界附近,真近邻却在相邻 cell 里;nprobe=1 只扫 query 所在 cell,隔壁的真近邻永远看不到。调大 nprobe 把更多相邻 cell 纳入扫描,召回上升;代价是扫描量与延迟同步上升,nprobe→nlist 时退化为全量暴力。
-
HNSW 为什么吃 RAM、又为什么删除困难?这两个代价分别来自结构的哪一部分?
展开
吃 RAM:全精度向量 + 每个点 M 条邻接边全部驻内存,总量约裸向量的 1.5–2×——代价来自"图的邻接信息也要常驻"。删除难:删点会切断图连通性、断掉经它中转的路径、损召回——代价来自"导航依赖图的连通"。常规解法是 tombstone 软删 + 周期重建。
-
跨机制综合题:为什么纯 PQ 很少单独使用,几乎总要配 IVF 或图法?
展开
两个独立理由叠加。① 精度:纯 PQ 的量化误差让距离判断频繁出错,recall 可掉到约 50%,单用不可接受;要把召回补回来必须 rescore,而 rescore 需要一份高精度向量和一批候选——候选从哪来?② 候选来源:PQ 本身只压缩向量、不缩小搜索范围,仍要遍历全部码做 ADC;配 IVF(先聚类粗筛缩范围)或配图(DiskANN 用图导航选候选)才能既缩范围又压内存,再用全精度向量 rescore 把 recall 拉回。所以 PQ 是"压缩器",必须挂在一个"候选生成器 + rescore"的管线里才有用。
内存超预算 3×,且 99% 查询带高基数过滤,先动哪个?
线上 HNSW 索引内存超预算 3×,同时 99% 的查询都带一个高基数 metadata 过滤(例如 tenant_id = ?,每个值只命中极小一部分数据)。在「调 HNSW 参数」「换 IVFPQ」「换 DiskANN」三者之间,先动哪个?为什么过滤这件事会改变答案?
提示
先把两个问题拆开:内存超 3× 是容量问题,高基数过滤是访问模式问题,最优解可能不是"调索引"而是"换数据组织方式"。高基数过滤的关键陷阱:HNSW 上做过滤会遇到"过滤后图连通性被破坏 → 召回崩"或"post-filter 后候选不够"的失败模式(这是 03 生产章的主题)。而高基数、低选择率的过滤字段,往往更适合按该字段分区/分片(每个 tenant 一个小索引),这同时缓解内存(每个分片单独可放、可量化)和过滤(过滤变成选分片,不再污染图搜索)。所以"先动哪个"的答案很可能是:先按 tenant_id 物理分区,再在每个分片内决定 HNSW+量化还是 DiskANN——纯调参或盲目换索引都没触及"过滤模式"这个真正的紧约束。