Chapter 05

自测与辨析

前四章铺完了:01 的词汇、02 的架构、03 的索引、04 的检索与一致性。这一章不讲新东西,只做一件事——把它们从“读过”逼成“能用”。三层梯度题 + 跨章选型辨析 + 一次手画。

这一章怎么用

  • 三层梯度:概念层(记得住)→ 原理层(讲得清机制)→ 应用判别层(真实场景里能取舍)。
  • 判别题是重点——它逼你在多章方案之间选择,这才是检验掌握的地方。
  • 所有答案集中在文末一个折叠块里。合上前四章作答,写完再对照;直接点开等于重读一遍。
难度递增 → 概念层 · 回忆 对应 01 · 记得住名词与边界 原理层 · 机制 对应 02 + 03 · 讲得清 how/why 应用判别层 跨章 · 01–04
图 5.1题库三层,自下而上越来越难。注意:真正检验掌握的是顶层的判别题——它逼你在多章方案之间做选择,而不是复述定义。能顺利做完概念层不代表会用 Milvus。

A概念层(对应 01)

  1. 一个 Collection 刚建好、还没写数据。此刻它在 etcd 里、在对象存储里各有什么?提示:01 §1.1
  2. 决定一条数据进哪个 shard 的是什么?决定它进哪个 partition 的是什么?这两者哪个在建表后还能改?提示:01 §1.3
  3. growing 段和 sealed 段,在“能否走向量索引加速”上有什么区别?提示:01 §1.4
  4. 一个 BinaryVector 字段能配 COSINE 度量吗?该用什么度量?提示:表 1.1
  5. 开了 AutoID 后,生成的主键是 1、2、3 这样连续递增的吗?这对写代码意味着什么?

B原理层(对应 02 + 03)

  1. 一条 insert 在哪一刻就算“持久”了——是落到对象存储那一刻吗?提示:02 §2.3
  2. 2.6 的 MixCoord 至少承担哪两项职责?它是怎么从旧版本演化来的?提示:02 §2.2
  3. 为什么说 Milvus 的计算节点“无状态”?某个 query node 崩了,为什么不丢数据?提示:02 §2.1
  4. nlist 和 nprobe 分别在什么时候生效?把 nprobe 调大需要重建索引吗?提示:03 §3.8
  5. HNSW 为什么内存比 IVF_PQ 重得多?它靠什么结构把检索从“扫全部”变成“扫一小部分”?提示:03 §3.4
  6. DiskANN 号称“磁盘索引”,为什么它仍然要占用 RAM?提示:03 §3.5
  7. 把一个稠密向量字段建成 GPU_CAGRA 索引、并指定 COSINE 度量,会发生什么?正确做法是什么?提示:03 §3.6

C应用判别层(跨 01–04,重点)

每题都要在“多章给的不同方案”之间做选择并说明依据——这是这份教程真正想训练的能力。

  1. 选型:一个团队约 50 万条向量、数据已经在 PostgreSQL 里、没有专职运维。该上 Milvus Distributed 吗?还是别的方案?给出依据。
  2. 选索引:10 亿条向量、单机内存放不下、预算紧、可接受 SSD 级延迟——选哪个索引?如果需求换成“要极致 QPS、且有一块 A100 GPU”,又改选哪个?
  3. 选一致性:场景 A——每晚离线把全量文档灌进 Milvus,白天只读检索;场景 B——用户提问时实时写入对话向量,紧接着的下一轮要能检索到自己刚写的。两个场景各选哪个一致性级别?为什么默认的那个不一定够?
  4. 综合 RAG 设计:持续增量写入文档块,每块带 tenant_id 和 date;在线检索按 tenant 过滤;要求刚入库的块尽快可被检索到。请同时定下三件事并说明它们如何相互影响:① 用不用 partition key、用哪个字段;② 选哪个索引(考虑过滤比);③ 选哪个一致性级别。
亲手画一张图

合上教程,在纸上或 Excalidraw 里画出一次 search 的读路径——从 Proxy 到 streaming node / query node、到 segment、到两级归并,只画 6 个节点。

画完翻回 04 §4.1 对照:你画的图里,growing 段和 sealed 段分别挂在哪个节点下?reduce 一共发生了几次、在哪里发生?这两点最容易漏。

D答案

三层都做完再展开。瞄一眼答案,这一章就退化成了再读一遍。

展开全部答案

概念层

  1. etcd 里有一条 schema + 配置元数据;对象存储里什么都没有——没有任何 segment,直到首次 insert。collection 的“存在”与数据的“存在”是两件事。
  2. hash(主键) 决定 shard;业务字段 / partition key 决定 partition。partition 可动态增删;shard 数建表定死、不可改(它绑定底层日志流分区)。
  3. growing 段还没建索引,只能暴力扫描;要等 sealed → flushed → indexed 之后才走向量索引。所以刚写入的数据查起来更慢。
  4. 不能。二值向量只配 HAMMING / JACCARD。COSINE 是稠密浮点向量的度量。向量类型决定可用度量(表 1.1)。
  5. 不是。AutoID 生成的是 snowflake 风格的大整数(按时间戳派生),非连续。代码里别假设主键是 1、2、3,也别用它做顺序或区间逻辑。

原理层

  1. 在落 WAL 那一刻就持久了,不是落对象存储时。WAL 是持久性边界;落对象存储(flush 成 binlog)是后续的固化与建索引步骤。
  2. MixCoord 至少管:发 TSO 时间戳(时钟)、DDL/DCL、查询拓扑与负载均衡、调度 compaction 与建索引。它由 2.6 之前的 root / query / data / index 多个独立 coordinator 合并而来。
  3. 因为持久状态只在 etcd + 对象存储 + WAL,计算节点本身不持有唯一数据,是日志与 binlog 的派生态。某个 query node 崩了,换一个节点从对象存储重新加载 segment、从 WAL 检查点重放即可,数据不丢。
  4. nlist 是建索引时定的(聚成多少簇);nprobe 是查询时定的(扫几个最近簇)。调 nprobe 不需要重建索引——同一份索引靠它在“低延迟 ↔ 高召回”之间滑动。
  5. HNSW 存完整向量 + 多层图的邻接关系,两者都吃内存;IVF_PQ 把向量乘积量化压成很小的码。HNSW 靠多层 NSW 图(类似跳表)从稀疏顶层贪心下降,逐层精化,只走图上一小部分点。
  6. DiskANN 把 PQ 压缩后的向量留在 RAM 做快速近似距离,完整向量和图邻接放 SSD;检索时用 RAM 里的近似距离做 beam search、再按需读 SSD。所以它不是零内存,是“大头在盘、索引摘要在内存”。
  7. 建不起来 / 检索结果不对——GPU 索引不支持 COSINE。正确做法:把向量归一化后改用 IP(归一化向量的内积等价于余弦相似度)。

应用判别层

  1. 不该上 Milvus Distributed。50 万向量远没到需要分布式向量库的规模,数据又已经在 Postgres 里——直接上 pgvector:不引入第二套服务、不用双写、能和业务表做 ACID join。Milvus 的存算分离架构在这个量级是纯运维负担(etcd + 对象存储 + WAL + 多组件)。结论的依据来自三章:01 的规模感、02 的架构复杂度、以及选型常识——为“将来可能的规模”提前上分布式,是真实存在的过度设计。
  2. 第一种需求选 DiskANN:数据超过单机内存、可接受 SSD 延迟,它把大头放盘、PQ 摘要留内存,单位成本最低(注意它默认关闭、仅 float 向量)。第二种需求选 GPU_CAGRA:有 GPU、要极致 QPS,GPU 原生图在大批量/高并发下吞吐可达 CPU 数十倍(代价:约 1.8x 内存、且不支持 COSINE,要归一化用 IP)。同一份数据、两种约束,索引选择完全不同——这就是 03 取舍三角的实战。
  3. 场景 A 用默认的 Bounded 就够:离线灌库、白天只读,能容忍秒级滞后,换取吞吐。场景 B 必须用 Session:要 read-your-writes,让同一 client 立刻看到自己刚写的对话向量。默认 Bounded 在 B 里不够,因为它是有界滞后,刚写的数据可能短暂不可见。机制上,四个级别就是给 guarantee timestamp 选不同的值(04 §4.4)。
  4. ① 用 partition key、选 tenant_id:在线检索总按 tenant 过滤,用它做 partition key 能把其他租户的 segment 直接裁掉。(若还想按 date 裁,可用标量过滤叠加。)② 索引:按 tenant 过滤后过滤比通常较高,但单租户数据规模常到不了十亿——内存够就用 HNSW(高召回高 QPS);若内存吃紧用 IVF_SQ8。注意高过滤比会打断图遍历,必要时对比 IVF。③ 一致性用 Session 或 Bounded,取决于“刚入库要多快可见”——要紧就 Session。三者相互影响:partition key 减小了每次检索的段范围(缓解扇出),从而放宽了对索引和一致性的压力;选 Session 又要求新段尽快 sealed+indexed,于是 flush / compaction 策略也得跟上。这正是 04 §4.6 的综合判别。
进阶挑战 · 刚好够不着

一句话本质,自己复述

不翻教程,用三到四句话向一个只用过 FAISS 的同事解释:为什么 Milvus 把一次 insert 拆成“先写日志 → 再固化 segment → 再异步建索引”,以及这套拆分如何决定了它的一致性默认值、扩展方式、和适用边界。

对照点(说完再展开)

一份合格的复述应当点到:① 日志(WAL)是事实源,写入落日志即持久、计算节点因此可无状态、可独立伸缩;② 数据固化成不可变 segment、每段各自建索引,所以检索是“多段并行 + 归并”、没有整表大索引;③ 异步建索引 + 有界滞后(默认 Bounded)是这套流式架构的自然结果——刚写的数据短暂只在 growing 段被暴力扫;④ 因此它擅长十亿级、读写分离伸缩,但小规模 / 已在 Postgres 的场景是过度设计。能把这四点串成因果链,就过了。