hnswlib / Annoy / ScaNN 向量数据库手册
hnswlib / Annoy / ScaNN 向量数据库手册
适用版本:hnswlib v0.9.0(2026-03-28)/ Annoy 1.17.3(2023-06-14)/ ScaNN 1.4.2(2025-08-29,PyPI),核实于 2026-11 许可与形态:开源索引库(hnswlib / Annoy 官方 LICENSE 正文未核验、ScaNN 许可官方资料未提供,见第 11 章);嵌入式 / 进程内索引库,不是数据库 一句话定位:三个都只负责「建索引 + 算 top-k」,持久化、并发、鉴权、CRUD、分布式、元数据过滤全部由调用方自己实现 约定:
$普通用户、#root 或 sudo、<xxx>为占位符;标 ⚠️ 的是高危操作。 更新时间:2026-11(版本与 API 变化快,执行前请以官方文档复核)
目录
1. 它是什么、什么时候选它
1.1 三库一句话定位
| 子产品 | 定位 | 维护状态 |
|---|---|---|
| hnswlib | header-only C++/Python,只做 HNSW + brute-force,支持增量插入 / 删除标记 / 过滤 | 活跃 |
| Annoy | Spotify 随机投影树,mmap 只读静态索引,读多写少 | 成熟但停滞,继任者 Voyager |
| ScaNN | Google Research,各向异性量化 + 分区树 + 精确重排 | 研究型发布,google-research monorepo 子目录 |
1.2 核心命题:索引库不是数据库
三者都是进程内库:没有守护进程、没有网络端口、没有存储引擎、没有用户体系。你灌进去向量,它给你 top-k 的距离/相似度与下标。检索链路里它们只占中间一格:
| 层 | 谁负责 | 三库是否提供 |
|---|---|---|
| 文档 → Embedding 向量化 | 调用方 / 模型服务 | 不提供 |
| 向量 + 业务字段的事实源 | 调用方 | 不提供 |
| 建索引 + 算 top-k | hnswlib / Annoy / ScaNN | 唯一提供的一层 |
| 元数据 / 标量过滤 | 调用方自己写(hnswlib 有过滤能力,但仍是进程内、无服务端) | 部分 / 需自己封装 |
| 持久化、快照、崩溃恢复、WAL | 调用方自己写;Annoy 只有 mmap 静态文件 | 不提供托管 |
| 并发控制、鉴权、多租户 | 调用方自己写 | 不提供 |
| 分布式、分片、副本、扩缩容 | 调用方自己写 | 不提供 |
| 服务化(REST/gRPC)、SLA、监控 | 调用方自己写 | 不提供 |
避坑:官方资料明确的共同坑是「无网络服务、无持久化管理、无权限与并发控制」。把这三个库当数据库用,是选型阶段最该避免的错误 —— 它们甚至连「单条改完立即生效」都不保证(Annoy build 后只读,改一条要重建)。
1.3 横向对照表(一):定位 / 维护状态 / 许可 / 平台支持 / 增量写
| 维度 | hnswlib | Annoy | ScaNN |
|---|---|---|---|
| 定位 | HNSW + brute-force,header-only | 随机投影树,mmap 只读静态索引 | 各向异性量化 + 分区树 + 精确重排 |
| 最新发布 | v0.9.0(2026-03-28);0.10.0-rc.2 预发布 |
1.17.3(2023-06-14) |
1.4.2(2025-08-29,PyPI) |
| 维护状态 | 活跃 | 成熟但停滞,继任者 Voyager | 研究型发布,monorepo 子目录 |
| 许可 | 官方 LICENSE 正文未核实,以官方为准 | 官方 LICENSE 正文未核实,以官方为准 | 官方资料未提供,以官方为准 |
| 平台支持 | header-only C++/Python,通常可跨平台编译,wheel 以 PyPI 为准 | C++/Python + mmap,通常跨平台,wheel 以 PyPI 为准 | 仅 Linux manylinux_2_27 x86-64 / aarch64 wheel(x86 需 AVX+FMA),无 macOS / Windows;Python ≥3.9 |
| 是否可增量写 | 可:支持增量插入 / 删除标记 / 过滤 | 不可:build 后只读,改一条要重建 | 官方资料未明确,以官方为准;通常建好后以批量检索为主 |
表中「通常可跨平台」「通常建好后以批量检索为主」为经验性表述,非官方结论;许可与 ScaNN 增量写能力见第 11 章未核实清单。
1.4 适合 / 不适合
适合:离线批量建索引与召回评测;数据量能装进单机内存且少变;作为自研检索系统的 ANN 内核;读多写少(Annoy 尤其);需要 HNSW 增量插入与删除标记(hnswlib);Linux x86 平台上做高精度量化检索(ScaNN)。
不适合:多进程 / 多机并发写与事务一致性;按标量字段做服务端 pre-filter;单条即时删除 / 更新且马上生效;账号、权限、审计、TLS、多租户;崩溃自动恢复、弹性扩缩容、SLA 与告警;数据量超单机内存或需要分片副本;macOS / Windows 上想用 ScaNN;依赖活跃维护与快速安全响应(Annoy 已停滞)。
1.5 规模与链路位置(经验值,非官方承诺)
| 量级 | 说明 |
|---|---|
| 百万级以内 × 768 维 | 精确基线常可接受,建议先做 brute-force 对照 |
| 千万级 × 768 维 | 通常要 HNSW / 随机投影树 / 量化,全扫吃紧 |
| 亿级 | 单机内存一般放不下,需自己分片或换分布式库 |
| QPS / 维度 | 取决于索引参数与机器,以实测为准;示例用 3~4 维,实际常见 768 / 1024 / 1536 |
三者只承担「候选召回」:离线灌 embedding,在线取 top-k 下标,再回到自己的事实源取回文本与元数据做后过滤、重排、拼 prompt。RAG 的文档管理、增量更新、权限过滤全在库之外。
⚠️ 危险:把返回的下标当业务 ID 用,一旦做过删除、重排或重建,下标会整体错位,召回会静默串到错误文档。必须维护稳定的「下标 ↔ 业务 ID」映射,并在每次重建时同步重建。
1.6 什么时候该换真数据库(选型视角判断线)
出现任意一条且无法接受自研成本,就应改用真正的向量数据库或托管服务:
| 触发条件 | 为什么索引库兜不住 | 建议去处 |
|---|---|---|
| 多进程 / 多机并发写 | 进程内对象,无跨进程锁与一致性 | Milvus / Qdrant / Weaviate / 云托管 |
| 需要服务端标量 pre-filter | 无元数据引擎(hnswlib 的过滤仍是进程内) | pgvector / Qdrant / Milvus |
| 需要单条即时删除 / 更新 | Annoy build 后只读;hnswlib 只有删除标记 | 真向量数据库 |
| 需要鉴权 / 多租户 / RBAC | 无网络层、无用户概念 | 专用向量库或云托管 |
| 需要崩溃恢复 / WAL / 托管持久化 | 只有 mmap 静态文件 / 手动导出 | 真向量数据库 |
| 数据量超单机内存 / 需要分片副本 | 单进程内存索引 | 分布式向量库 |
| 需要 REST/gRPC、SLA、监控告警 | 完全没有服务化能力 | 真向量数据库 / 云托管 |
| 需要 macOS / Windows 上用 ScaNN | ScaNN 仅 Linux wheel | 换跨平台库 |
2. 版本、许可与官方入口
2.1 版本状态表
| 子产品 | 版本号 | 时间 | 状态 | 备注 |
|---|---|---|---|---|
| hnswlib | v0.9.0 | 2026-03-28 | 当前稳定 | 0.10.0-rc.2 为预发布,勿用于生产 |
| Annoy | 1.17.3 | 2023-06-14 | 最新发布 | 成熟但停滞,继任者 Voyager |
| ScaNN | 1.4.2 | 2025-08-29(PyPI) | 研究型发布 | google-research monorepo 子目录 |
版本差异:三条版本线互不相干,不要横向比大小、也不要混用「SDK / 服务端 / 格式」版本号;官方资料未提供更早的发布史。
2.2 许可与商业边界
- hnswlib / Annoy:官方 LICENSE 正文未核实(见第 11 章),许可名称与条款以官方仓库为准;ScaNN 许可官方资料未提供,同样以官方为准。
- 三者都是开源研究型 / 工具型库,无「企业版 / 商业特性」分层。真正的成本在你要自己补齐的整套数据库能力,而非授权。
- 许可变更史官方资料未提供,以官方仓库为准。
2.3 官方入口表
| 子产品 | 官方入口 |
|---|---|
| hnswlib | https://github.com/nmslib/hnswlib/releases |
| Annoy | https://github.com/spotify/annoy/releases |
| ScaNN | https://pypi.org/project/scann/ |
官方资料只给出以上三个入口;官网 / 文档站 / 镜像仓库地址未提供,以官方仓库导航为准,本篇不臆造 URL。
2.4 版本识别与近两年变更
无服务端 --version。用 Python 侧自检能否 import(包名以 PyPI 为准):
$ python -c "import hnswlib; print('hnswlib ok')"
$ python -c "import annoy; print('annoy ok')"
$ python -c "import scann; print('scann ok')"
版本差异:ScaNN 自 1.4.0 起 TensorFlow 集成需
pip install scann[tf];ScaNN 仅 Linux manylinux_2_27 x86-64 / aarch64 wheel;Annoy 已停滞并有继任者 Voyager;hnswlib 0.10.0-rc.2 是预发布。以上均为官方资料明确事实,别照抄旧教程的「默认就带 TF 集成 / 全平台可装」。
3. 安装与启动
3.1 前置条件与形态
Python 环境;向量一般需转 float32(经验性,以各库官方文档为准)。无服务端、无端口、无守护进程,「启动」= 在进程里 import 并构造索引对象。
3.2 方式一:pip(三库分小节)
hnswlib
$ pip install hnswlib
Annoy
$ pip install annoy
ScaNN
$ pip install scann
# 1.4.0 起 TensorFlow 集成需额外装
$ pip install scann[tf]
避坑:
pip install hnswlib/pip install annoy为按包名给出的通用安装命令,包名与 wheel 可用性以 PyPI 为准。ScaNN 的scann[tf]是官方资料明确的可选依赖,不要与基础包混为一谈。
3.3 方式二:平台前置检查(ScaNN 必做)
ScaNN 有硬平台限制,装之前先确认,避免白装:
import sys
import platform
assert sys.version_info >= (3, 9), "ScaNN 需 Python >= 3.9"
assert platform.system() == "Linux", "ScaNN 仅提供 Linux wheel"
print("平台前置检查通过:", platform.system(), platform.machine(), sys.version.split()[0])
避坑:x86 还需 AVX+FMA,可用
grep -m1 -o -E 'avx|fma' /proc/cpuinfo粗查(经验性,以实测为准)。hnswlib 为 header-only C++(官方资料明确),可直接包含头文件;Annoy / ScaNN 的源码构建命令官方资料未给出,以各自官方仓库为准,本篇不臆造编译参数。
3.4 目录与端口表 / 健康检查 / 生产最小配置
| 项 | 值 / 做法 |
|---|---|
| 监听端口 | 无(三者均为进程内库) |
| 数据目录 | 调用方自定;Annoy 为 mmap 静态索引文件 |
| 健康检查 | 第 2.4 节 import 自检;无 /health 端点 |
| 认证 | 无原生能力,服务层套鉴权(见第 7 章) |
| 持久化路径 | Annoy 静态文件只读挂载;hnswlib / ScaNN 由调用方决定导出路径 |
| 资源限制 | 用容器 / cgroup 限内存;占用 ≈ 向量矩阵 + 索引结构,见第 6.5 节 |
| 只读加载 | 索引文件只读挂载,避免多进程同时写坏文件 |
4. 核心概念与数据模型
4.1 术语对照表
| 术语 | 通用术语 | 关系型类比 | 说明 |
|---|---|---|---|
| Index / 索引对象 | ANN 索引 | 一张「索引」 | 进程内对象,非表 |
| 返回的下标 | 近邻下标 | 行号 / rowid | 不是业务 ID |
| 距离 / 相似度 | 打分 | 分数 | 方向见 4.3 |
| mmap 静态索引(Annoy) | 只读内存映射文件 | 只读快照 | build 后不可改 |
| 删除标记(hnswlib) | 软删除 | 逻辑删除位 | 非物理删除,需自己管 |
4.2 数据模型层级
三者都没有集合 / 数据库 / 字段 / schema 层级,只有向量矩阵 + 可选下标;「表」由调用方的 records 列表或外部数据库承担。hnswlib 支持过滤,但仍是进程内能力,不等于服务端标量查询。
4.3 距离度量与方向
官方资料未给出三库距离度量的完整枚举与参数名,以下为通用原理(经验性表述),具体取值与「值越大越相似还是越小越相似」以各库官方文档为准:L2 / 欧氏类距离越小越相似;内积 / 余弦类相似度越大越相似。查询时度量必须与建索引时一致,否则结果无意义。
4.4 横向对照表(二):许可 / 平台 / 数据模型 / 增量写 / 维护状态
| 维度 | hnswlib | Annoy | ScaNN |
|---|---|---|---|
| 许可 | LICENSE 正文未核实 | LICENSE 正文未核实 | 官方资料未提供 |
| 平台支持 | 通常跨平台(header-only C++) | 通常跨平台(C++/mmap) | 仅 Linux x86-64 / aarch64,x86 需 AVX+FMA |
| 数据模型 | HNSW 图 + brute-force | 随机投影森林 + mmap 文件 | 分区树 + 各向异性量化 + 精确重排 |
| 持久化形态 | 由调用方导出 / 管理 | mmap 只读静态文件 | 由调用方导出 / 管理 |
| 是否可增量写 | 可(增量插入 / 删除标记 / 过滤) | 不可(build 后只读,改一条重建) | 官方资料未明确,以官方为准 |
| 维护状态 | 活跃 | 成熟但停滞,继任者 Voyager | 研究型发布 |
4.5 写入 → 建索引 → 可检索的生命周期
| 阶段 | hnswlib | Annoy | ScaNN |
|---|---|---|---|
| 构造 | 建索引对象 | 建索引对象 | 官方资料未给逐字 API,以官方为准 |
| 灌数据 | 支持增量插入 | 逐条加入后 build,此后只读 | 官方资料未给逐字 API,以官方为准 |
| 可检索 | 插入后即可查;删除走标记 | build 之后才能查 | 以官方为准 |
| 落盘 | 调用方自己导出 / 加载 | mmap 静态文件 | 调用方自己导出 / 加载 |
| 是否异步 | 官方资料未提,通常同步 | 通常同步 | 以官方为准 |
避坑:Annoy 的「build 后只读」是硬约束。官方资料明确「改一条要重建」,不要指望增量更新。
5. 快速上手:完整 CRUD 示例
说明:三者都没有「连接 / 表 / CRUD」概念,且官方资料未提供三库逐字的 API 方法名与参数名。为避免臆造,本篇以一段等价的、完整可运行的 numpy brute-force 基线示范「调用方必须自己维护的胶水层」—— 索引库只替换其中「相似度检索」一步;各库具体调用以官方文档为准(见第 11 章)。
5.1 完整可运行示例:调用方胶水层基线(Python)
import numpy as np
d = 4 # 示例用 4 维,实际常见 768 / 1024 / 1536
records = [
{"id": "doc-0", "text": "三国演义", "category": "history", "vec": [0.1, 0.2, 0.3, 0.4]},
{"id": "doc-1", "text": "红楼梦", "category": "history", "vec": [0.1, 0.2, 0.3, 0.5]},
{"id": "doc-2", "text": "三体", "category": "scifi", "vec": [0.9, 0.1, 0.2, 0.1]},
{"id": "doc-3", "text": "流浪地球", "category": "scifi", "vec": [0.8, 0.1, 0.2, 0.2]},
]
def build_matrix(rows):
"""C = 建集合 / U = 插入:索引库只吃数值矩阵,业务字段自己留着"""
return np.asarray([r["vec"] for r in rows], dtype="float32")
def brute_topk(xb, query_vec, k):
"""R:精确 top-k 基线(hnswlib 自带 brute-force;另两库把这里换成自己的索引)"""
xq = np.asarray(query_vec, dtype="float32")
dist = np.sum((xb - xq) ** 2, axis=1) # 平方 L2,越小越相似
return [(int(i), float(dist[i])) for i in np.argsort(dist)[:k]]
def search_with_filter(xb, rows, query_vec, category, k):
"""带标量过滤检索:取回候选后自己按元数据后过滤"""
return [(rows[i]["id"], rows[i]["text"], dist)
for i, dist in brute_topk(xb, query_vec, k)
if rows[i]["category"] == category]
xb = build_matrix(records) # 连接(无)+ 创建集合 + 插入
print("已灌入行数 =", len(records))
print("R 精确 top-2 =", brute_topk(xb, [0.1, 0.2, 0.3, 0.45], 2))
print("过滤 history =", search_with_filter(xb, records, [0.1, 0.2, 0.3, 0.45], "history", 4))
# U:更新(无行级 update,改事实源后重建矩阵;Annoy 还要重建整个索引)
records[1].update(text="红楼梦(修订)", vec=[0.1, 0.2, 0.3, 0.55])
xb = build_matrix(records)
print("更新后检索 =", search_with_filter(xb, records, [0.1, 0.2, 0.3, 0.55], "history", 2))
# D:删除(从事实源移除,再整表重建;hnswlib 的删除标记不是物理删除)
records = [r for r in records if r["id"] != "doc-3"]
xb = build_matrix(records)
print("删除后剩余行数 =", len(records))
# 清理 / 持久化:三库都不托管,Annoy 只有 mmap 静态文件,其余由调用方导出
np.save("vectors.npy", xb)
print("已写出 vectors.npy;索引文件与事实源必须一起备份")
5.2 输出怎么读
R 精确 top-2 的 i 是 add 矩阵行下标(不是业务 ID),dist 是平方 L2、越小越像;过滤 history 是后过滤结果 (业务ID, 文本, 平方L2);删除后剩余行数 是事实源行数,重建后旧下标全部失效。
避坑:把下标当业务主键是三库通用的错。Annoy 只有 mmap 静态索引文件、hnswlib / ScaNN 无持久化管理,业务字段永远在你自己的事实源里。
5.3 按子产品分小节:接入生命周期
5.3.1 hnswlib:header-only C++/Python,只做 HNSW + brute-force;支持增量插入 / 删除标记 / 过滤,是三者中唯一明确可增量写的。适合需要在进程内持续追加数据的场景。删除是标记而非物理删除,需自己管标记集合与重建时机。
5.3.2 Annoy:Spotify 随机投影树,mmap 只读静态索引,读多写少。逐条加入后 build,build 之后只读;官方资料明确「改一条要重建」。适合索引基本不变、靠 mmap 低内存加载的离线召回。
5.3.3 ScaNN:Google Research,各向异性量化 + 分区树 + 精确重排;仅 Linux manylinux_2_27 x86-64 / aarch64 wheel(x86 需 AVX+FMA),Python ≥3.9,1.4.0 起 TF 集成需 pip install scann[tf]。平台前置检查见第 3.3 节。官方资料未提供官方 Python 最小接入代码,逐字 API 以官方文档为准(见第 11 章)。
避坑:官方资料未提供三库逐字的 API 方法名与参数名,本篇不臆造。上面第 5.1 节的 numpy 基线是可运行的等价实现;上线前请把「相似度检索」一步替换为对应库的官方调用,并以官方 README / 文档核对参数。
6. 索引与检索进阶
6.1 横向对照表(三):调优 / 平台 / 增量写 / 维护状态
| 维度 | hnswlib | Annoy | ScaNN |
|---|---|---|---|
| 调优抓手 | 建图连接数、检索候选规模(概念性,参数名以官方为准) | 树数量、检索候选规模(概念性) | 分区数、量化阈值、重排候选(概念性) |
| 平台支持 | 通常跨平台 | 通常跨平台 | 仅 Linux x86-64 / aarch64,x86 需 AVX+FMA |
| 是否可增量写 | 可(增量插入 / 删除标记 / 过滤) | 不可(build 后只读) | 官方资料未明确 |
| 维护状态 | 活跃 | 成熟但停滞,继任者 Voyager | 研究型发布 |
| 许可 | LICENSE 未核实 | LICENSE 未核实 | 官方资料未提供 |
6.2 索引参数调优表(概念层)
| 抓手 | 影响 | 建议范围 |
|---|---|---|
| 建图 / 建树规模 | 越大召回越高、内存与构建越慢 | 以实测为准 |
| 检索候选规模 | 越大召回越高、延迟越高 | 从小到大压测 |
| 量化 / 重排(ScaNN) | 压缩内存、用精确重排补回精度 | 以实测为准 |
官方资料未给出三库逐字的参数名、默认值与推荐范围,本表为概念性经验表述,不是官方结论;具体参数以各库官方文档为准(见第 11 章)。
6.3 召回 / 延迟 / 过滤 / 混合检索
- 候选规模调大 → recall 上升、延迟上升;建图 / 建树规模调大 → 召回上升、内存与构建时间上升。没有统一最优值,必须用自己的数据压测 recall@k 与 p99。
- 三者都没有服务端标量过滤:hnswlib 的过滤是进程内能力,Annoy / ScaNN 更需调用方自己后过滤(第 5.1 节演示)。
⚠️ 危险:后过滤会让返回条数变少。
k取小、过滤条件又很 Selective 时可能一条不剩。生产上要么放大k再截断,要么换支持 pre-filter 的向量数据库。
- BM25 + 稠密混合、RRF 融合、稀疏向量、重排都要在外部自己实现 —— 同样是「索引库不是数据库」的直接后果。
6.4 量化与压缩
ScaNN 的核心就是各向异性量化 + 分区树 + 精确重排(官方资料明确):用量化压缩候选、再用精确计算重排补回精度。Annoy 的 mmap 静态索引走的是随机投影树 + 只读内存映射。hnswlib 以 HNSW 图为主、另有 brute-force 基线。量化必然有召回损失,上线前必须用精确基线(第 5.1 节)算 recall@k。
6.5 内存与磁盘容量估算
- 原始向量矩阵:
n × d × 4字节(float32每元素 4 字节),再加索引结构开销。例:1,000,000 × 768 × 4 ≈ 2.86 GiB,这只是原始向量,HNSW 图 / 投影森林 / 量化码字另算。 - Annoy 的 mmap 静态文件常可只读映射、按需换页,进程常驻内存可低于文件尺寸(经验性,以实测为准)。
- 三者都无增量落盘、无 WAL,每次导出都是整份快照。以上为经验估算,以产物大小与
free -m实测为准。
7. 生产部署与运维
7.1 把边界再讲透:三库不负责什么
| 缺失能力 | 自己要补的东西 |
|---|---|
| 持久化 | hnswlib / ScaNN 自己导出与版本化;Annoy 只有 mmap 静态文件,无 WAL |
| 并发 | 单写多读锁、进程间共享,或只读镜像 + 定期重建 |
| 鉴权 | 服务层做认证 / 授权 / 审计 / TLS,索引库完全不感知 |
| CRUD | 自己维护事实源;hnswlib 用删除标记,Annoy 改一条要重建 |
| 分布式 | 自己分片、聚合、路由、副本 |
| 元数据过滤 | 自己后过滤(第 6.3 节),或换支持 pre-filter 的库 |
| 服务化 | 自己包 REST/gRPC、连接池、超时、限流 |
| 监控 | 自己埋点:召回率、p99、索引行数、文件大小、内存 |
⚠️ 危险:多进程同时导出 / 覆盖同一索引文件会互相写坏。落盘要「写临时文件 → 原子改名」,并保留上一版可回滚。
高可用 / 集群 / 分片 / 副本三者原生都不提供,可行做法都是调用方自建:多副本各持只读索引、前置负载均衡、定期全量重建后滚动替换。这与「托管服务买的是运维责任转移」形成鲜明对比 —— 这里责任全在你(对比见 04 分册)。
7.2 什么时候该换真数据库(运维视角判断线)
第 1.6 节是选型视角,这里是上线前自检:任一问答「是」就别硬撑索引库。
| 上线自检 | 若为「是」 | 处置 |
|---|---|---|
| 需要 7×24 在线、有 SLA 与告警 | 无服务端、无监控端点 | 换真向量数据库 / 云托管 |
| 写入与检索来自多个进程 / 机器 | 无跨进程一致性 | 换分布式向量库 |
| 数据改一条要分钟级生效 | Annoy 重建、hnswlib 仅标记 | 换支持 delete/upsert 的库 |
| 需要按租户 / 用户做数据隔离 | 无鉴权与多租户 | 换带 RBAC 的库 |
| 需要崩溃后自动恢复 | 无 WAL | 换真数据库 |
| 索引超单机内存 / 要弹性扩容 | 无分片与扩缩容 | 换分布式 / 云托管 |
| 平台含 macOS / Windows 且选了 ScaNN | ScaNN 仅 Linux wheel | 换跨平台库 |
| 依赖厂商安全响应 | Annoy 已停滞、ScaNN 研究型发布 | 换活跃维护的产品 |
7.3 备份与恢复(索引是否需要重建)
| 项 | 做法 |
|---|---|
| 备份对象 | 索引导出文件 / Annoy mmap 静态文件 + 业务事实源(缺一不可) |
| 恢复 | 重新加载索引文件;加载 API 名以各库官方文档为准(见第 11 章) |
| 是否需要重建 | Annoy 改一条就要重建;hnswlib 删除标记累积后要重建;索引参数变更必须重建 |
| 恢复演练 | 定期用快照 + 事实源重建一遍并跑 recall 校验 |
避坑:不要只备份索引文件。没有事实源,你无法把返回下标还原成业务记录,等于数据丢了。
7.4 监控指标与告警阈值建议 / 容量与升级
三者无 /metrics 端点,需在服务层埋点:
| 指标 | 告警阈值建议(经验值) |
|---|---|
| 进程内存 | 超过容器 limit 的 80% 告警(OOM 是头号故障) |
| 单次检索 p99 | 超过业务 SLO 告警 |
| recall@k(对比精确基线) | 低于约定下限(如 0.95)告警 |
| 索引行数 vs 事实源行数 | 不一致即告警(下标错位前兆) |
| 导出产物大小 / 失败次数 | 突增或连续失败告警 |
| 后过滤命中率 | 过低说明 k 偏小或过滤过强 |
按第 6.5 节公式估算内存并预留索引结构与峰值余量;扩缩容 = 重建索引 + 换机器 / 换副本,无在线弹性。升级:固定 pip 版本,升级前用同一份数据跑 recall 对比;索引文件跨版本兼容性官方资料未提供说明,稳妥做法是升级后用事实源重建索引;ScaNN 升级还要核对 Linux wheel 与 AVX/FMA 依赖。
版本差异:ScaNN 1.4.0 起 TF 集成需
scann[tf];hnswlib 0.10.0-rc.2 是预发布;Annoy 已停滞并有继任者 Voyager。别照抄旧教程的默认行为。
7.5 安全基线与日志
| 面 | 建议 |
|---|---|
| 认证 / RBAC | 全部在服务层;三库无用户概念 |
| TLS / 网络暴露面 | 进程内调用无需;理想暴露面为 0,别为调试把脚本直接暴露端口 |
| 索引文件权限 | 只读、最小权限;文件里可能含可反推的向量 |
| 输入校验 | 校验维度、dtype、k 上界,防畸形输入打爆内存 |
无自带日志,统一打到业务服务日志。系统级服务管理(systemd / 防火墙等)属通用运维,见根目录手册,本篇不重复。
8. 高频故障与排障
| 症状 | 可能原因 | 定位命令 / 方法 | 处置 |
|---|---|---|---|
| 连不上 / 服务无响应 | 把库当服务了,其实没有监听端口 | 查端口 + 第 2.4 节自检 | 改进程内调用;需要服务化就换真数据库 |
import scann 失败 / macOS、Windows 装不上 |
不是 Linux / Python <3.9 / x86 缺 AVX+FMA | 第 3.3 节平台检查 | 换 Linux x86-64 / aarch64 且满足指令集;或换跨平台库 |
| TF 集成报错 | 1.4.0 起需 pip install scann[tf] |
pip show scann |
补装可选依赖,别以为基础包自带 |
| Annoy 修改后不生效 | build 后只读,改一条要重建 | 检查是否漏了重建 | 全量重建索引并原子替换 |
| 写入失败:dtype / 维度错 | 输入不是数值矩阵或形状不对 | 打印 dtype / shape |
转 float32 并对齐维度(以各库文档为准) |
| 查不到 / 召回低 / 查询慢 | 候选规模太小、量化损失、后过滤滤光、全扫 | 第 5.1 节精确基线算 recall;放大 k |
调检索候选、降压缩、放大 k 或换 pre-filter 库 |
| OOM | 向量矩阵 + 索引结构超单机内存 | free -m;按第 6.5 节估算 |
量化、分片、减副本、换分布式库 |
| 磁盘满 / 索引不生效 | 整份快照多版本累积;加载了旧文件 | df -h;对比精确基线 |
只保留最近 N 版;重建索引、确认加载新文件 |
| 版本升级后报错 | 索引文件跨版本不兼容 / wheel 变了 | 固定版本复现;查 Releases | 用事实源重建索引,核对平台依赖 |
| 过滤结果变少 / 删除后串位 | 后过滤在 k 之后;重建后旧下标未失效 |
打印过滤前后条数;比对事实源行数 | 放大 k 再截断;重建索引同时重建「下标→ID」映射 |
9. 常见坑与避坑清单
- 现象:把三者当数据库,直接拿返回下标当业务主键。原因:返回的是
add矩阵行下标。怎么做:自己维护「下标 ↔ 业务 ID」映射,删改后同步重建。 - 现象:Annoy 改一条数据后结果不变。原因:build 后只读,改一条要重建。怎么做:全量重建 + 原子替换,别做增量幻想。
- 现象:macOS / Windows 上
pip install scann失败。原因:仅 Linux manylinux_2_27 x86-64 / aarch64 wheel。怎么做:第 3.3 节先查平台,或换跨平台库。 - 现象:ScaNN 在老 x86 CPU 上崩或报指令错。原因:x86 需 AVX+FMA。怎么做:查
/proc/cpuinfo,换满足指令集的机器。 - 现象:ScaNN 的 TensorFlow 集成用不了。原因:1.4.0 起需
pip install scann[tf]。怎么做:补装可选依赖,别照抄旧教程「装了就有」。 - 现象:hnswlib 删除后旧向量仍被召回。原因:是删除标记,非物理删除。怎么做:自己维护标记并在结果层过滤,累积后重建。
- 现象:照旧教程以为有服务端 /
/health。原因:三者无网络服务、无权限与并发控制。怎么做:服务层自建健康检查与鉴权。 - 现象:以为索引会自动持久化、崩溃能恢复。原因:Annoy 只有 mmap 静态文件,另两库无持久化管理。怎么做:自己导出 + 版本化 + 备份,且事实源一起备。
- 现象:只备份索引文件,恢复后拿不回文档。原因:业务字段本就不在库里。怎么做:索引文件与事实源一起备份,否则等于数据丢失。
- 现象:上了量化后召回明显掉。原因:量化是有损的。怎么做:用第 5.1 节精确基线算 recall@k,调候选规模 / 重排后再上线。
- 现象:带
category过滤后一条不返回。原因:无服务端标量过滤,只能取回 top-k 后自己后过滤。怎么做:放大k再截断;高选择性过滤换 pre-filter 库。 - 现象:多进程同时导出把索引文件写坏。原因:无 WAL、无文件锁、整份覆盖写。怎么做:⚠️ 写临时文件 → 原子改名,保留上一版可回滚。
⚠️ 危险:第 9、12 条可能导致线上数据丢失;第 1、6 条会产生长期静默的错误召回结果,上线前必须做恢复演练与 recall 回归。
10. 速查表
10.1 横向对照表(四):速查版
| 维度 | hnswlib | Annoy | ScaNN |
|---|---|---|---|
| 定位 | HNSW + brute-force | 随机投影树,mmap 只读 | 各向异性量化 + 分区树 + 精确重排 |
| 最新发布 | v0.9.0 | 1.17.3 | 1.4.2 |
| 维护状态 | 活跃 | 成熟但停滞,继任者 Voyager | 研究型发布 |
| 许可 | LICENSE 未核实 | LICENSE 未核实 | 官方资料未提供 |
| 平台支持 | 通常跨平台 | 通常跨平台 | 仅 Linux x86-64 / aarch64,x86 需 AVX+FMA |
| 是否可增量写 | 可 | 不可 | 官方资料未明确 |
| 持久化 | 调用方导出 | mmap 静态文件 | 调用方导出 |
10.2 操作速查表
| 操作 | 命令 / 代码 | 说明 |
|---|---|---|
| 装 hnswlib | pip install hnswlib |
包名以 PyPI 为准 |
| 装 Annoy | pip install annoy |
包名以 PyPI 为准 |
| 装 ScaNN | pip install scann |
仅 Linux wheel |
| 装 ScaNN TF 集成 | pip install scann[tf] |
1.4.0 起需要 |
| 查平台 | python -c "import platform; print(platform.system(), platform.machine())" |
ScaNN 仅 Linux |
| 查 AVX/FMA | `grep -m1 -o -E 'avx | fma' /proc/cpuinfo` |
| import 自检 | python -c "import hnswlib; print('ok')" |
无服务端版本命令 |
| 精确基线 | 第 5.1 节 brute_topk |
算 recall@k 的 ground truth |
| 强制 float32 | np.asarray(x, dtype="float32") |
经验性,以各库文档为准 |
| 增量插入 | hnswlib 支持;Annoy 不支持 | 见横向对照表 |
| 删除 | hnswlib 删除标记;Annoy 重建 | 非数据库式 delete |
| 过滤 | 自己后过滤;hnswlib 有进程内过滤 | 无服务端 pre-filter |
| 落盘 | Annoy mmap 文件;其余调用方导出 | 无 WAL |
| 备份 | 索引文件 + 事实源一起备 | 缺一不可 |
| 平台不符 | 换跨平台库或换机器 | ScaNN 硬限制 |
| 换真数据库 | 见第 1.6 / 7.2 节判断线 | 别硬撑 |
常用端口与路径小表
| 项 | 值 |
|---|---|
| 网络端口 | 无(三者均为进程内库) |
| Python 版本 | ScaNN ≥3.9;其余以官方文档为准 |
| 索引文件示例 | Annoy mmap 静态文件;vectors.npy(调用方自定) |
| 官方入口 | https://github.com/nmslib/hnswlib/releases | https://github.com/spotify/annoy/releases | https://pypi.org/project/scann/ |
11. 参考与延伸
11.1 官方文档链接清单
| 子产品 | 地址 |
|---|---|
| hnswlib | https://github.com/nmslib/hnswlib/releases |
| Annoy | https://github.com/spotify/annoy/releases |
| ScaNN | https://pypi.org/project/scann/ |
官网 / 文档站 / 镜像仓库地址官方资料未提供,以官方仓库导航为准,本篇不臆造 URL。
11.2 与本册其他产品的关系
- 同属「向量索引库(不是数据库)」:Faiss.md,四库选型对照见两篇的横向对照表。
- 出现第 1.6 / 7.2 节任一判断线时,改用真向量数据库:Milvus / Qdrant / Weaviate / pgvector(见对应分册)。
- 想买「运维责任转移」而非自建,见 04 分册:
Pinecone.md、Zilliz-Cloud.md、国内云向量数据库.md。
11.3 进阶阅读方向
HNSW / 随机投影树 / 各向异性量化的原理与召回-延迟取舍;以精确基线为 ground truth 的 recall@k 评测方法学;mmap 静态索引的只读发布与原子替换;从索引库迁移到支持 pre-filter / 删除 / 分布式向量库的改造路径;Annoy 继任者 Voyager 的评估。
11.4 本篇「未核实 / 以官方为准」事项
| 事项 | 状态 |
|---|---|
| hnswlib 官方 LICENSE 正文 | 未核实(官方资料「六、未核实清单」第 5 条),正文条款以官方仓库 LICENSE 为准 |
| Annoy 官方 LICENSE 正文 | 未核实(同上),正文条款以官方仓库 LICENSE 为准 |
| Faiss 官方 LICENSE 正文 | 未核实(同上,见 Faiss.md 第 11 章) |
| ScaNN 许可 | 官方资料未提供,以官方为准;正文未写成任何确定协议 |
| 三库逐字的 Python API 方法名 / 参数名 / 参数默认值 | 官方资料未提供,本篇不臆造;第 5.1 节以 numpy 等价基线代替,上线前以官方 README / 文档核对 |
| ScaNN 是否可增量写 | 官方资料未明确,正文标注「以官方为准」 |
| ScaNN 官方 Python 最小接入代码 | 官方资料未提供,以官方文档为准 |
| 三库距离度量完整枚举、健康检查端点、监控端点 | 官方资料未提供,第 4.3 / 7.4 节为经验性表述 |
| 各库官网 / 文档站 / 镜像仓库 URL | 官方资料未提供,不臆造 |
| hnswlib / Annoy 跨平台 wheel 的具体可用平台清单 | 官方资料未逐字给出,正文为「通常跨平台」的经验性表述 |