Chapter 06

自测辨析

前五章建好了"一条向量在 pgvector 里的一生"。这一章不再讲新东西——它用三层梯度的题把那条主线从你脑子里逼出来:记得住(概念)、想得通(原理)、选得对(跨章判别)。

用法:每道题先合上前面的章节,把答案写在纸上或编辑器里,再翻到文末 答案 对照。直接看答案等于把这一章当成第六遍泛读——那不会留下任何东西。判别层尤其要写完整理由,不只是选一个选项。

选得对 — 判别层 第 4·5 章:选计划 · 选型 想得通 — 原理层 第 2·3 章:构建 · 存储 · MVCC · vacuum 记得住 — 概念层 第 1 章:类型 · 运算符 · opclass · 排序契约 记忆 迁移
图 6.1三层题按难度叠成金字塔,每层挂在对应的章节上。注意:朱红的顶层(判别层)测的不是"记不记得",而是"换个场景选不选得对"——它故意把第 1 到第 5 章的概念混在一道题里,这才是能不能用的真正检验。

▸概念层(对应 01 章)

  1. 说出 vector、halfvec、bit 三种类型的可建索引维度上限,并解释 vector 那个上限为什么是 2,000。
  2. <=>、<->、<#> 各算什么距离?哪个返回的是负值,为什么?
  3. opclass 在建索引时起什么作用?查询运算符和它对不上号会怎样——报错还是变慢?
  4. 一句话说清 amcanorderbyop 排序契约:向量索引只服务什么形状的查询?
  5. 为 3072 维 embedding 建 HNSW 索引,写出能成功的那行 DDL。
  6. 向量已归一化时,<=> 和 <#> 的排序结果一样吗?哪个更省算力?

▸原理层(对应 02 · 03 章)

  1. HNSW 和 IVFFlat,哪个能在空表上建、哪个必须先灌数据?为什么 IVFFlat 不行?
  2. "构建悬崖"是什么?跨过 maintenance_work_mem 那条线后,建索引为什么会慢 10–50 倍?
  3. HNSW 图的搜索为什么是随机 I/O 密集?这和"图放不放得进 RAM"有什么关系?
  4. 默认 ef_search = 40 给的召回偏低且不报警。怎样取一个精确的基准来量召回?
  5. UPDATE / DELETE 一行后,HNSW 图里那个旧节点立刻消失了吗?查询时它怎么被处理?
  6. vacuum 修 HNSW 图为什么"写得重"?它修复连通性,但不做的那件事是什么?
  7. 高频写入的 embedding 表召回逐周下降。什么时候该 REINDEX 而不是等 vacuum?

▸应用判别层(跨 01–05 章,重点)

每道题给一个真实场景。先判断根因在哪一章,再在多个方案里选一个并说清为什么其它不合适。

  1. 欠返 vs 腐烂:多租户 RAG,某小语种租户搜出来只有 3 条。另有一张高频 re-embedding 的表,召回逐周下降。这两个症状看起来都是"结果变少",但根因分属不同章、解法完全不同。分别指出根因与解法。
  2. 选哪条过滤计划:WHERE tenant_id = 99 在全表只命中 0.3%。iterative scan、partial index、B-tree 精确排序——选哪个?为什么另外两个在这个选择性下不划算?
  3. EXPLAIN 显示 Seq Scan:列出至少三个会让 planner 放弃 HNSW 索引的原因,并各给一句诊断/修复。其中哪个根本不是 bug?
  4. 内存放不下图:5 千万条 1536 维向量,HNSW 图远超 RAM,p99 延迟爆炸。在"加大机器内存 / 换 halfvec / 换 pgvectorscale 的 StreamingDiskANN"里,分别解决了问题的哪一面?哪个最治本?
  5. 该不该离开 pgvector:团队数据已在 Postgres,2 千万向量、查询几乎都带租户过滤、写入中等。另一个团队 8 亿向量、纯向量热路径、几乎不过滤。两个团队该继续用 pgvector 还是换专用库 / pgvectorscale?把判断依据落到具体维度上。
亲手画一张图

合上整本教程,在纸上画出"一条向量在 pgvector 里的一生"——只画 6 个节点:向量列 → 排序契约 → 索引实现 → MVCC → planner 过滤 → 选型。在每条边上写一句它发生了什么。
画完回到 起点页的概念地图 对照:你画的图里,"排序契约"那个节点有没有标出"不是过滤索引"?这条边是后面三章所有事故的总根源——漏了它,说明主线还没真正立住。

答案(三层都做完再展开)

概念层

  1. vector ≤ 2,000、halfvec ≤ 4,000、bit ≤ 64,000。2,000 来自 8KB page:一个索引项要把向量原值连同图指针塞进单页,4 × 2000 + 开销 已逼近 8KB。
  2. <=> cosine 距离、<-> L2、<#> 负内积。<#> 返回负值:内积越大越相似要降序,但索引只能升序吐,取负让"最相似"变成"最小"。
  3. opclass 决定索引按哪种距离建图,并让 planner 把查询运算符和索引对上号。对不上不报错,只是索引用不上、退化全表扫描(30s vs 5ms)。
  4. 只服务 ORDER BY <距离运算符> ... LIMIT k:按距离排序、取前 k。没有 ORDER BY、没有 LIMIT、或带 DESC,都用不上。
  5. CREATE INDEX ON doc_chunks USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);
  6. 排序结果完全一样(都是 dot 的单调变换)。<#> 更省:cosine 还要多算模长和除法,单位向量下是浪费。

原理层

  1. HNSW 能在空表上建(增量插入建图);IVFFlat 必须先有数据,因为 lists 个聚类中心要靠 k-means 在已有行上算出来。
  2. 构建在内存里进行,直到 maintenance_work_mem 填满,之后退化成逐元组的磁盘路径(日志打印 "hnsw graph no longer fits...")。同样数据、只跨了一条线,建索引时间就 10–50 倍地涨。
  3. 图节点的邻居指针散落在任意 8KB 页上,每跳一步就要解引用到不同页。图放得进 RAM 时这些是内存访问;放不下时每跳都可能触发 page fault,延迟塌方。
  4. BEGIN; SET LOCAL enable_indexscan = off; 跑同一条 ORDER BY ... LIMIT k 得到精确暴力结果作为 ground truth,再和走索引的结果算 overlap@k。
  5. 不会立刻消失。MVCC 下它仍指向已死的堆行;查询时 pgvector 走完图、拿到候选后,逐个重检堆可见性,把已死的丢掉。图本身要等 vacuum 才清。
  6. 因为 vacuum 的第二趟要为每个"邻居被删了"的节点重新计算替补边(HnswFindElementNeighbors)并改写邻居元组——这是大量随机写。它修复连通性,但不重平衡图,所以质量仍会随删除累积而漂移。
  7. 当死元组比例到 ~10–15%:此时 vacuum 只能修不能重建,召回已腐烂,REINDEX INDEX CONCURRENTLY 重建一张干净的图更划算。

应用判别层

  1. 症状一(小语种只剩 3 条)= post-filter 欠返,根因第 1 章排序契约 + 第 4 章:索引先取固定 ef_search 候选、WHERE 后筛,命中率低就筛没了。解法:开 hnsw.iterative_scan = relaxed_order(或对该租户建 partial index)。症状二(逐周下降)= 召回腐烂,根因第 3 章:churn 在图里堆死节点。解法:REINDEX CONCURRENTLY + 调激进 autovacuum。两者同形(结果变少)但一个在查询期、一个在索引寿命。
  2. B-tree 精确排序。命中仅 0.3%(极少行),精确算距离又快又准、无近似误差,planner 走 B-tree 是对的。iterative scan 会一路扫到 max_scan_tuples 仍可能补不够;partial index 对 0.3% 散落的行维护不起、且若 tenant 值很多则不可扩展。
  3. ① 查询带 DESC(索引只升序);② 没有 LIMIT(要求全量有序,扫表更便宜);③ opclass 与查询运算符不匹配;④ 0.8.0 前的成本误估在复杂查询里退回 Seq Scan;⑤ 表太小。其中⑤(小表)根本不是 bug——几百行暴力算比走图快,planner 选 Seq Scan 正确。诊断统一用 EXPLAIN。
  4. 加内存:治标,把图勉强塞回 RAM,但 5 千万还会继续涨、成本高。换 halfvec:把图体积砍半(2 字节/分量),多数情况召回损失 <1%,能把"放得下 RAM"的天花板推高一倍。StreamingDiskANN:最治本——它把图放 SSD、只在内存留压缩副本做搜索,本就不受"图必须进 RAM"约束。规模继续涨时只有它不撞墙。
  5. 团队一继续 pgvector:数据已在 PG、2 千万在舒适区、查询都带关系过滤(pgvector + 关系过滤正是它的主场)、写入中等。团队二该换(pgvectorscale 或专用库):8 亿向量远超 pgvector 单机 HNSW 的舒适区、纯向量热路径让向量延迟成为瓶颈、又不靠关系过滤——这正是 StreamingDiskANN / 专用库的主场。判断维度:数据量级、是否带关系过滤、向量延迟是否是热路径、churn 率。