目录

向量数据库中的门门道道

加入 Tensorchord 已经一年有余,一直也没有时间静下心来写一些文章。主要是有了彤彤女儿后,事情多了很多。中间也经历过业务从 Serverless 模型推理 Modelz pivot 到向量搜索领域 VectorChord 的过程。Pivot 的经历或许可以在之后的文章中和大家分享,感兴趣的也可以直接联系我。最近半年一直在开发 VectorChord Cloud, 所以在这里边学边总结向量数据库中的门门道道。

1. 什么是向量

向量在物理,数学,以及计算机科学等领域的含义都有所不同。这里的向量主要指的是计算机科学中的向量,也就是一组有序的数值。在计算机科学中,向量通常用来表示数据,比如在机器学习中,我们通常会将一张图片转换成一个向量,或者将一段文字 tokenizer 之后转换成一个向量,然后再进行训练。在向量数据库中,我们通常会将一张图片,一段文本,或者一段音频通过 embedding 模型转换成一个向量,然后再进行存储和检索。下面是一个简单的例子,我们通过 all-MiniLM-L6-v2 模型将一段文本转换成一个向量。all-MiniLM-L6-v2 将句子和段落映射到 384 维 dense vector,并可用于聚类或语义搜索等任务。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from sentence_transformers import SentenceTransformer

# 初始化模型
model = SentenceTransformer('all-MiniLM-L6-v2')

# 要嵌入的文本示例
sentences = [
    "Hugging Face is creating a tool that democratizes AI.",
    "I love natural language processing.",
    "Transformers are state-of-the-art models for NLP tasks."
]

# 生成嵌入
embeddings = model.encode(sentences)

# 打印嵌入
for sentence, embedding in zip(sentences, embeddings):
    print(f"Sentence: {sentence}")
    print(f"Embedding: {embedding}\n")

总结一下,向量其实是真实世界的实体和计算机世界的桥梁, 计算机通过向量来理解和处理真实世界的数据。

2. 什么是向量数据库

世界本没有向量数据库,只是向量多了,就成了向量数据库,开个玩笑hh。这里我给个简单的定义:能够索引并存储 vector,以实现快速检索和相似性搜索功能的数据库。网络上很多人将向量数据库定义为专注于处理向量数据的数据库,这个定义是不准确的。准确的说向量与向量搜索是一种新的数据类型和查询处理方法,和传统数据库的类似和索引方法并无本质区别。

3. 什么是向量搜索

向量搜索也叫向量检索,是一种 Information Retrieval 的技术,用于在高维向量空间中查找与给定查询向量最相似的向量。为了衡量两个向量之间的相似性,我们通常会使用余弦相似度,欧氏距离,曼哈顿距离等。为了加速向量搜索,我们通常会使用索引结构,比如 KD-Tree,IVF(Inverted File Index),HNSW(Hierarchical Navigable Small World)等。向量搜索在很多领域都有应用,比如在推荐系统中,我们可以使用向量搜索来查找与用户历史行为最相似的商品,然后推荐给用户;在图像检索中,我们可以使用向量搜索来查找与给定图片最相似的图片;在 RAG(Retrieval Augmented Generation)中,我们可以使用向量搜索来查找与给定问题最相似的文本,增强大模型的 Context 从而提高生成答案的质量。

3.1 向量搜索应用场景

3.1.1 推荐系统

如 Qdrant 关于 Video Content-based Recommendation 的 On-premise 案例,通过 multilingual universal sentence encoder 来对上传视频时候的脚本进行嵌入。这里不是简单的对视频进行抽帧,更多的信息来自于上传时候的视频标题,描述,自动检测标签以及通过 whisper 语音识别的内容。所以目前遇到的问题是如果视频是无音频,被迫使用标题以及描述进行推荐,这样对于审核团队来说是一个很大的挑战。这里提到了推荐领域的 call start issues, 也就是用户在刚开始使用的时候,推荐系统的推荐质量不高,这个时候用户体验会很差。在非即时更新的协作推荐器以及元数据推荐器的基础上,增加基于内容的推荐器,可以大大优化 call start issues。

3.1.2 图像检索

immich 是一个高性能的开源 self-hosted 图像以及视频管理解决方案。试想当你把你所有的视频和图片都上传到 immich 之后,你很难在很短的时间内找到你想要的图片或者视频。这个时候就需要一个高效的图像检索系统 smart search,通过向量搜索技术,你可以通过文本描述以及额外的过滤器(标签,日期等)来快速精准的找到你想要的图片或者视频。

image tag filter search
image search

图片来自于 immich

3.1.3 RAG

RAG(Retrieval Augmented Generation)主要解决在 LLM 应用中的几个问题:

  1. LLM 训练模型的数据不是即时的,换句话说是静态的数据,获取最新数据重新进行训练的成本太大。
  2. LLM 缺乏特定领域的知识,因为 LLM 的训练语料大都是网络上通用的数据集。而在比如金融,医疗,法律等领域,私域中的数据或许是最重要的,缺乏领域内数据会让 LLM 出现幻觉问题。
  3. LLM 的黑匣子问题,我们无法知道 LLM 是如何生成答案的,其答案的来源来自何处。

这里借用 Paul lusztin 和 Aurimas Griciunas 的两张图来解释 RAG 的工作原理:

./rag-1.png

  1. 获取金融新闻的流式即时数据,以及历史数据
  2. 将数据进行 chunking 转换成 embedding 模型的输入,然后将 embedding 存储到向量数据库中
  3. 用户提问
  4. 通过向量搜索找到最相似的新闻 chunks,然后将用户历史的 chat 信息和新闻 chunks 进行 Prompt composition
  5. 输入到 LLM 中生成答案。
  6. 将答案返回给用户
  7. 将新的 chat 信息存储到用户历史数据中

./rag-2.png

  1. 私域中的数据,例如 Notion, Jira,本地 pdf 文件等等进场 chunking 转换成 embedding 模型的输入
  2. 将 chunk 输入到 embedding 模型中,然后将 embedding 存储到向量数据库中
  3. Vector Database 构建 Index
  4. 用户提问, 输入到 embedding 模型
  5. embedding 输出 query 的 embedding vector
  6. 将 5 中的 vector 作为 Query vector 输入到向量数据库中
  7. 向量数据库通过 ANNs(Approximate Nearest Neighbors Search)找到最相似的 chunks
  8. 将搜索到的 chunks 和 query 构建 Prompt
  9. 输入到 LLM 中生成答案

3.2 相似度指标

3.2.1 余弦相似度

余弦相似度是一种用于衡量两个向量之间的相似性的方法,它是通过计算两个向量之间的夹角来衡量的。余弦相似度的取值范围是[-1, 1],其中1表示两个向量之间的夹角为0度,表示两个向量完全相同;-1表示两个向量之间的夹角为180度,表示两个向量完全相反;0表示两个向量之间的夹角为90度,表示两个向量之间没有相似性。计算公式如下:

cos

这个公式计算了向量 𝐴 和 𝐵 之间的夹角余弦值。

3.2.2 欧氏距离

欧氏距离是一种用于衡量两个向量之间的相似性的方法,它是通过计算两个向量之间的距离来衡量的。欧氏距离的取值范围是[0, ∞],其中0表示两个向量完全相同, 数值越大则表示两个向量之间的差异越大。计算公式如下:

l2

这个公式计算了向量 𝐴 和 𝐵 之间的欧氏距离, 有些直接不开根号其只是数值不同,并无本质区别。

3.2.3 负内积

负内积(Negative inner product),它是通过计算两个向量之间的内积来衡量的。数值越大则表示两个向量之间的相似性越高。计算公式如下:

negative_inner_product

3.2.4 曼哈顿距离

曼哈顿距离(taxicab distance),它是通过计算两个向量之间的距离来衡量的。曼哈顿距离的取值范围是[0, ∞],其中0表示两个向量完全相同, 数值越大则表示两个向量之间的差异越大。计算公式如下:

taxicab_distance

3.3 向量搜索算法

直觉上,我们可以通过遍历所有的向量来找到与给定查询向量最相似的向量,但是这种方法的时间复杂度是 O(n)。当向量的数量很大时,这种方法是不可行的,对于你的应用延迟不可接受。为了加速向量搜索,我们通常会使用索引结构,比如 IVF(Inverted File Index),HNSW(Hierarchical Navigable Small World)等。通过 ANNs (Approximate Nearest Neighbors Search) 算法,我们可以在更低的时间复杂度,比如 O(log(n)),找到与给定查询向量最相似的向量。

3.3.1 LSH (Locality Sensitive Hashing)

局部敏感哈希 (LSH) 的工作原理是通过哈希函数处理每个向量,将向量分组到存储桶中,从而最大化哈希冲突,而不是像通常的哈希函数那样最小化冲突。

这里引用 Pinecone 的一张图:

lsh

LSH 的具体细节如下图所示:

lsh_details

  1. Shingling:使用 k-shingling 以及 one-hot encoding 将文本转换成稀疏向量
    • k-shingling 的意思是以窗口大小为 k 的滑动窗口,在文本中提取 k 个连续的字符
    • one-shot encoding 的意思是,将 k-shingling 的结果和词汇表进行比较,如果存在则在词汇表表示为1,不存在则为0

https://www.pinecone.io/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fvr8gru94%2Fproduction%2Fc259242606006f5c5505a6e677f3d05be75a26da-1280x720.png&w=1920&q=75

  1. 后使用 MinHash 创建“签名”
    • 创建 [1…len(voc)+1] 的随机排列
    • 随机排列中从上到下的值作为 index ,如果原始稀疏 vector 的 index-1 位置为1则取随机排列的 index-1 位置数为签名值
    • 重复 n 次得到 n 维度稠密向量 https://www.pinecone.io/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fvr8gru94%2Fproduction%2F866cea917043cfd7eb8221fc1a3b715a61e9d14f-1280x720.png&w=1920&q=75
  2. Band and Hash
    • 将 n 维度的签名向量分成 b 组,每组 r 个
    • 对每组进行 hash,得到 b 个 hash 值
    • 如果两个向量的 hash 值相同,则将这两个向量放到同一个桶中
    • 如果在同一个桶中,则认为其为候选对
lsh5

这里随着 b 的增大返回更多的候选对,这自然会导致更多的误报

lsh6

这意味着随着纬度的增加,误报的可能性越大,而且维数增大后需要维护更多的 hash 桶,存储的开销也会增大。所以 LSH 更适合低维度的向量搜索,不是目前的主流向量搜索算法。

3.3.2 IVF(Inverted File Index)

倒排索引算法是一个简单、易懂而且非常容易实现的算法,而且有着不错的搜索速度,但是搜索的精度较 HNSW 较差些,但是内存消耗相对 HNSW 更少。

构建 IVF 索引的核心分为两个步骤:

  1. 通过聚类算法将向量分成 nlist 个簇
  2. 将向量分配到对应的簇中

搜索时候,设定需要搜索的聚类个数 nprobe

ivf

这里参数的影响是:

  • 增大 nlist 会降低构建索引的速度,因为在聚类过程中向量需要跟更多的中心点进行计算;同时会降低搜索时间,因为对应中心点的向量更少了,做 knn 的时候更快。
  • 增大 nprobe 会提高召回率但是会降低搜索速度,因为需要搜索更多的单元格。
ivf2

3.3.3 HNSW (Hierarchical Navigable Small World)

HNSW 结合了 NSW 以及 Skip List 的优点,是一种高效的向量搜索算法。HNSW 的核心思想是通过构建一个多层的图,每一层都是一个小世界,通过在每一层中搜索最近的节点,然后在下一层中搜索最近的节点,最终找到与给定查询向量最相似的向量。

NSW 是建立在一个理论的基础上,NSW 上的点到任意点的距离都是有限的,而且是通过很少的几次跳跃就能找到的。

NSW 的构造过程:

  1. 随机选择一个点作为插入点
  2. 查找与插入点最近的 m 个点
  3. 将插入点与 m 个点相连

这里的随机性会让前期的图中长连接线增多,加速搜索,可以理解成“高速公路”,下图中的红色线就是长连接线: ./nsw_graph.png

NSW 的搜索过程如下,这里借用知乎网友“工牌厂程序猿”的一张图:

nsw
  1. 初始化三个集合,分别是 visited,candidate,result(定长);随机选择初始点进入,并加入 visited 以及 candidate 集合, candidate 保存和 query 点的距离
  2. 寻找初始点的 n 最近邻点,加入到 visited 集合,注意如果友点在 visited 集合中则废弃,,对 n 个近邻点并行计算和 query 的距离,进行升序排序(由近到远)加入 candidate 集合
  3. 寻找 candidate 集合中的 n 个近邻点,加入 visited 集合,如果已经存在 visited 集合中废弃;这里查询的是 C 点,只有 D 点没访问过,因为 D 点距离 query 点距离小于 C 到 query 点距离,所以 result 中将 C 换成 D 点,candidate 中将 C 换成 D 点
  4. 重复 3 步骤,寻找 D 的 n 哥最近邻,加入 visited 集合,如果已经存在 visited 集合中废弃;这里查询的是 E 点和 G 点,因为 E 点距离 query 点距离小于 result 集合中的最大距离,所以 result 中将 H 换成 E 点,candidate 中将 E 点剔除
  5. 重复 3 candidate 集合中距离 query 最小距离的点 H 的距离比 result 集合中距离 query 最大的点 E 的距离还大,则停止查询

Skip List 是一种高效的数据结构,可以在 O(log(n)) 的时间复杂度内找到与给定查询向量最相似的向量。Skip List 的核心思想是通过构建一个多层的链表,每一层都是一个有序的链表,通过在每一层中搜索最近的节点,然后在下一层中搜索最近的节点,最终找到与给定查询向量最相似的向量。

skiplist

这里需要注意 HNSW 的几个点:

  1. 需要控制 HNSW 每一层的点最大连接数 Max, 在随机(越底层概率越大)插入节点时,如果有邻居节点 N 的连接数大于 Max,则对 N 进行 KNN 搜索重新与新的邻居建立连接。
  2. 启发式选边策略:在每一层搜索与插入点最邻近的 M 个节点的时候,它是先召回了 efConstruction 个,然后再选择出 M 个(efConstruction >= M),选择 M 的过程可以直接选择 Top-M 但是可能会降低整体的连通性,“工牌厂程序猿” 的文章具体列举了这个 case:
hnsw

这里的 efConstruction 是 4,M 是 2,如果直接选择 Top-M 的话,一定会选择 A 和 B, 这样 ABQ 和 CD 的连通性就降低了,这里在选择 A 后寻找第二个最近邻的时候检测 QA 和 AB 距离,如果 QA > AB 则再寻找下一个最近邻,知道大于 QA 为止,这里找到 C 点时 AC > AQ。

  1. High degree vertex 越靠近最上层,这样可以减少搜索路径,提高搜索效率

构建参数:

  • efConstruction: 图构建过程中的一个参数,用来控制在为每个节点建立连接时,考虑的最近邻候选节点的数量。该参数具体影响的是图在构建过程中节点之间连接的质量。较高的 efConstruction 值意味着在为一个节点选择邻居时会考虑更多的候选节点,从而生成更优质的图结构。但是,较高的 efConstruction 值会增加构建图的时间和空间复杂度,而且在搜索时也会增加搜索时间。
  • m: 每个顶点添加的最大邻居数,分为 m_0=2m 以及 m_max=m, 参看代码.

搜索参数:

  • efSearch: 在搜索时,用来控制搜索的质量。较高的 efSearch 值意味着搜索时会考虑更多的候选节点,从而提高搜索的质量。但是,较高的 efSearch 值会增加搜索时间。

HNSW 由于检索过程中涉及平均单条查询会产生数百个读磁盘操作,需要不停的寻找下一个随机点,放到 SSD 会导致极高的时延,所以是全内存的。

NSG (Navigating Spreading-out Graph)

虽然 HNSW 的查询速度快,精度高,但是并非完美,它主要有以下几个痛点:

  • 索引复杂且占用内存大:HNSW 的核心是“多层高速公路”网络。这个多层结构虽然效果好,但实现起来相对复杂,而且为了存储每一层的图结构,需要占用不小的内存(RAM)。当数据量达到十亿、百亿级别时,内存成本会非常高。
  • 构建过程复杂:在构建 HNSW 索引时,需要为每个新加入的节点随机选择一个层级,然后在该层级及以下的所有层级中寻找邻居并连接,这个过程也比较耗时。

NSG 论文要解决的核心问题是:能不能设计一种**结构更简单、索引更小(更省内存)**的图,但搜索性能却能和复杂、庞大的 HNSW 相媲美,甚至超越它? NSG 的创新可以总结为“一个理论,一个枢纽,一个技巧”。

  1. 核心理论:从“任意两点”到“一个中心”的思想转变 (MRNG)
  • 理想的图 (MSNET):学术界早就提出过一种理想的图,叫做“单调搜索网络”(Monotonic Search Network, MSNET)。在这种图上搜索,你从任何一个点出发,每走一步都必然离你的目标更近。就像在一个山谷里找最低点,你只要一直往下走,保证能找到。这种图上搜索不会“走回头路”,效率极高。但问题是,构建一个完美的 MSNET 非常非常耗时,不切实际。
  • NSG的理论基础 (MRNG):论文作者基于 MSNET 的思想,提出了一种新的、更稀疏的图叫 MRNG (Monotonic Relative Neighborhood Graph)。它也是一种能保证“单调性”的图,但构建规则更巧妙。然而,构建一个完整的 MRNG 仍然太慢了。
  • 思想的飞跃:作者们意识到,我们真的需要保证任意两个点之间都有单调路径吗?在实际搜索中,我们总是从一个(或少数几个)入口点开始搜索。
    • HNSW 的思路:从顶层图的入口点开始,层层下沉。
    • NSG 的革命性思路:我们干脆只设置一个全局的“入口点”,我们称之为“导航节点”(Navigating Node)。我们不再追求构建一个“任意两点都能高效互通”的复杂网络,而是构建一个“从这个导航节点出发,到任何其他节点都有一条高效路径”的中心化网络。 这就好比城市交通规划:
  • HNSW:建立了一个“地铁 + 公交 + 快速路”的立体交通网,你从任何地方到任何地方都很方便,但系统复杂,建设成本高。
  • NSG:在城市正中心建立一个巨大的“宇宙中心交通枢纽”。所有远距离出行,都先到这个枢纽,再从枢纽出发去目的地。整个网络结构变成了以枢纽为中心的放射状,大大简化了。
  1. 核心设计:导航节点 (Navigating Node)

这个“宇宙中心交通枢纽”就是 NSG 的灵魂——导航节点。

  • 如何选择? 通常选择整个数据集的“质心”(几何中心)附近的一个点。这个点具有最好的全局视野。
  • 它有什么用?
    • 搜索的唯一入口:所有的查询,无论目标在哪里,都统一从这个导航节点开始。
    • 建图的绝对核心:在为每个节点 p 选择邻居时,NSG 不再是盲目地在全局找,而是利用一个非常聪明的技巧。
  1. 核心技巧:高效的邻居选择策略

这是 NSG 能够兼顾“图的稀疏性”(省内存)和“搜索的高效性”的关键。当给节点 p 构建边时,如何为它选择邻居?

  • 传统方法:在 p 周围一定范围内找最近的几个点。范围选多大?邻居选多少个?很难权衡。
  • NSG 的天才技巧:
    • 我们先在图上(一个预先构建的粗糙 k-NN 图)模拟一次从“导航节点”到 p 的搜索。
    • 这次搜索会经过一条路径,比如 导航节点 → a → b → … → p。
    • NSG 认为,这条路径上经过的所有节点 (a, b, …),都是 p 的“绝佳邻居候选人”。为什么?因为这些点是连接全局中心和局部点 p 的关键桥梁。
    • 最后,NSG 只需要在这些少量的“候选人”中,再使用 MRNG 的规则精选出最终的邻居。 这个技巧的妙处在于,它把一个全局的、开放的邻居选择问题,变成了一个局部的、数量有限的优化问题,极大地降低了建图的计算复杂度。

结合上面的创新点,我们来梳理一下 NSG 的完整流程:

  1. 构建 (Build)
  • 步骤一:找枢纽。计算数据质心,找到离质心最近的点作为“导航节点”。
  • 步骤二:打基础。构建一个基础的、粗糙的 k-近邻图(k-NN Graph)。这一步是为了后续的路径搜索。
  • 步骤三:连主路 (核心)。遍历图中除导航节点外的每一个节点 p:
    1. 以 p 为目标,从“导航节点”在上一步的 k-NN 图上进行一次搜索。
    2. 把搜索路径上访问过的所有节点都收集起来,形成 p 的“候选邻居池”。
    3. 从这个池子里,为 p 精心挑选出固定数量(比如 m 个)的邻居并连接。同时,为了保证图的稀疏性,严格控制每个节点出度的上限。
  • 步骤四:查漏补缺。由于严格限制了出度,可能会导致图的某些部分断开连接。最后,从导航节点开始做一次深度优先搜索(DFS),检查是否所有点都可达,如果发现有“孤岛”,就强行给孤岛上的点连一条边到主网络,确保图的连通性。
  1. 搜索 (Search) NSG 的搜索过程异常简洁:
  • 从“导航节点”出发。
  • 不断地、贪心地跳到当前节点的所有邻居中,离查询目标最近的那个邻居。
  • 重复上一步,直到找不到比当前节点更近的邻居为止,此时就到达了局部最优,也就是搜索结果。

NSG 选边跟 HNSW 选择最小边策略不同。以点 r 为例,当 r 与 p 建立连接时,以 r 和 p 为圆心,r 和 p 的距离为半径,分别做圆,如果两个圆的交集内没有其他与 p 相连接的点,则 r 与 p 相连。在连接点 s 时,由于以 s 和 p 距离为半径的交集圆内,已有点 r 与 p 相连,所以 s 和 p 不相连。t点因为s点已经排除,所以保留,下图中最终与点 p 相连的点只有r, t 和 q,这样减少了冗余边,减少了平均出度。

nsg

NSG 相对 HNSW 的优势:

  • 索引大小 (Index Size):NSG 完胜。NSG 的索引(图占用的内存)通常只有 HNSW 的 1/2 到 1/3。这是 NSG 最大的优势,尤其是在处理超大规模数据集时,能节省巨大的硬件成本。
  • 搜索性能 (Performance):两者旗鼓相当,NSG 略有优势。在很多数据集上,NSG 的搜索速度(QPS, Queries Per Second)都超过了 HNSW,尤其是在要求高精度(High Recall)的场景下。
  • 简洁性 (Simplicity):NSG 完胜。NSG 是单层图结构,没有 HNSW 复杂的层级概念,无论是理解还是实现都更加简单。

但是业界普遍还是采用 HNSW, 主要原因有以下几点:

  1. 增量索引 (Incremental Indexing) 的能力至关重要
  • 工业场景:在实际应用中,数据是动态变化的。比如,电商网站每秒都有新商品上架,社交网络每秒都有新图片发布。我们不可能每次都为了新增的几个数据点,就花几小时甚至几天去重建整个索引。我们需要一个能高效“添加”和“删除”单个数据点的索引。
  • HNSW 的优势:HNSW 的多层结构天然支持增量索引。当一个新节点加入时,算法会给它随机分配一个层级,然后从顶层开始,像普通查询一样找到它应该插入的位置,并连接附近的邻居。这个过程是局部化的,非常高效,不会影响整个图的结构。删除节点也相对容易处理。
  • NSG 的劣势:NSG 的设计哲学——依赖一个固定的全局“导航节点”——让增量索引变得非常困难。
    • 添加节点:新节点 p 的邻居是通过从“导航节点”到 p 的路径来决定的。但新节点 p 还没有加入图,怎么找路径?即便加入,它的存在也可能会改变其他节点的最优路径,理论上可能需要更新大量节点的边,这违背了增量索引的初衷。
    • 删除节点:如果删除的节点恰好是某个关键路径上的一环,可能会导致图的连通性被破坏,或者搜索效率下降。 这是 HNSW 在工业界占据主导地位的最核心、最关键的原因。 对于静态数据集(比如一次性构建好,不再改变的图像库),NSG 是个好选择。但对于绝大多数需要持续更新的在线服务,HNSW 的灵活性是不可替代的。
  1. 生态系统和成熟度 (Ecosystem and Maturity)
  • 先发优势:HNSW 提出得更早,并且其作者很快将其集成到了当时非常流行的 nmslib 库中。随后,像 Faiss (Facebook AI)、Lucene (Apache) 等业界顶级的开源库都迅速跟进,提供了稳定、高效的 HNSW 实现。
  • 社区和文档:有了这些顶级库的支持,HNSW 迅速积累了庞大的用户社区、丰富的文档、教程和生产环境下的实践经验。当一个工程师遇到问题时,他能很容易地找到解决方案。
  • 信任和稳定性:HNSW 已经在无数公司的生产环境中经受了多年的考验,其鲁棒性(robustness)和稳定性得到了充分验证。对于追求稳定压倒一切的工业界来说,选择一个“久经沙场的老将”远比选择一个“初出茅庐的天才”要稳妥。
  1. 鲁棒性和参数敏感度 (Robustness & Parameter Sensitivity)
  • HNSW:其多层结构提供了很好的容错性。即使在高层走错了一步,下沉到更稠密的低层后,仍然有很大的机会修正路线,最终找到正确的目标。它的性能对参数(如 M, efConstruction)虽然敏感,但调整起来相对直观。
  • NSG:其性能高度依赖于“导航节点”的选择和图的连通质量。如果导航节点选得不好(比如选在了数据分布的边缘),或者在建图过程中为了稀疏性而剪枝过多,导致关键路径丢失,那么搜索性能可能会急剧下降。它的“天花板”可能很高,但“地板”也可能更低。

3.3.4 DiskANN

DiskANN 是建立在这样一个背景下:

  1. ANNS 算法(如 HNSW、NSG)为了保证速度,都依赖于将索引存储在内存中。这导致存储成本非常高,限制了可以处理的数据集大小。
  2. SSD 的潜力: 固态硬盘(Solid-State Drives, SSDs)相比传统的硬盘更快,但仍然比内存慢很多。大家普遍认为,将索引放在 SSD 上会导致搜索速度大幅下降,无法满足实际应用的需求。

DiskANN 要解决的问题:如何设计一种新的 ANNS 索引,突破内存限制,使得我们能够在单台机器上,利用廉价的 SSD 存储十亿级别的数据,并保持高精度和低延迟?

DiskANN 系列有三篇文章,DiskANN,FreshDiskANN,FilterDiskANN。

DiskANN 的核心创新在于:算法 (Vamana 图) + 系统设计(SSD 优化)。

  1. 核心算法:Vamana 图

Vamana 图是 DiskANN 论文提出的新型图索引,它主要解决了以下两个问题:

  • 优化 SSD 访问模式: 普通的图索引(如 NSG、HNSW)在 SSD 上进行搜索时,会产生大量的随机读取,导致延迟很高。Vamana 图旨在减少 SSD 的随机读取次数,使其更倾向于顺序读取。
  • 更好的搜索性能: 相比于 NSG 和 HNSW,Vamana 图在特定场景下可以提供更好的搜索精度和速度。 为了实现这些目标,Vamana 图的设计有以下几个关键点:
  • Robust Prune: 在构建图的过程中,Vamana 图使用了一种名为 Robust Prune 的策略来选择每个节点的邻居。这个策略的目标是确保搜索路径上的节点能够以一定的比例因子 alpha 更快地接近查询目标,从而减少搜索的跳数和 SSD 的读取次数。
  • Iterative Construction: Vamana 图的构建是一个迭代的过程。首先,图被初始化为随机连接的图,然后通过迭代地优化每个节点的邻居来改进图的结构,使其更适合于近似最近邻搜索。
  • Graph Merging: 为了处理大规模数据集,Vamana 图支持将多个小的图索引合并成一个大的图索引。这个特性使得我们可以并行地构建索引,并将其合并成一个完整的索引,从而提高构建效率。
  1. 系统设计:SSD 优化策略

DiskANN 的另一大创新在于针对 SSD 的特性进行了一系列优化,从而最大限度地发挥 SSD 的性能。这些优化策略包括:

  • Beam Search: 为了减少 SSD 的随机读取次数,DiskANN 使用了一种名为 Beam Search 的策略。在搜索过程中,算法维护一个包含多个候选节点的集合(beam),并并行地从 SSD 中读取这些节点的邻居信息。这样可以减少搜索过程中的 round-trip 次数,从而降低延迟。
  • Caching: DiskANN 利用内存来缓存频繁访问的节点信息。通过将热点数据缓存在内存中,可以减少对 SSD 的访问次数,从而提高搜索速度。
  • Implicit Re-Ranking: DiskANN 使用 Product Quantization (PQ) 等向量压缩技术来减少内存占用。但是,压缩后的向量会损失精度。为了弥补这个损失,DiskANN 在从 SSD 中读取邻居信息时,同时读取原始的、未压缩的向量,并使用这些向量来重新排序候选结果,从而提高搜索精度。
  • Full-Precision with Neighborhoods: 将全精度向量和邻居信息一起存储在 SSD 上,读取邻居信息时也可以一起读取全精度信息,无需额外开销,便于使用全精度信息rerank。

构图过程:

  1. 首先构建随机图,这里和 NSG 的K最近邻图不一样,对每一个节点随机选择 R 个节点相连接
  2. 计算起点,找全局质心最近的点,目的是尽量减少平均搜索半径
  3. 搜索起点对每个点做 ANN,将搜索路径上所有的点作为候选邻居集,执行 alpha = 1 的裁边策略 (参考 NSG)
  4. 整 alpha > 1(论文推荐 1.2)重复步骤 3。因为 3 是基于随机近邻图做的,第一次迭代后图的质量不高,所以需要再迭代一次来提升图的质量,这个对召回率很重要。这里拿上图举例,如果 alpha为1.2,当 ps 的距离大于 1.2 * pr 的距离,才会裁撤 ps 边。

下图可以直观感受下,alpha=1 以及 alpha=1.2 最后图的区别,第一行是 alpha=1, 第二行是 alpha=1.2,alpha=1.2 的图更加稠密,明显多了长边,减少了搜索半径。

diskann

这时候你可能有疑问,如果按照这样建图,根本不可能在 64GB 的机器上存放超过 1B 的数据,这里就要有一些优化手段:

  1. 先做全局 kmeans,将数据分成 k 个簇,然后将每个点分到距离最近的 I 个簇中,一般 I 取 2 就够了。对每个簇建基于内存的 Vamana 索引,最后将 k 个 Vamana 索引合并成一个索引。
  2. 使用量化的方法,建索引时用原始向量,查询的时候用压缩向量。因为建索引使用原始向量保证图的质量,搜索的时候使用内存可以 hold 住的压缩向量进行粗粒度搜索,这时的压缩向量虽然有精度损失,但是只要图的质量足够高,大方向上是对的就可以了,最后的距离结果还是用原始向量做计算的。
  3. 每个点的邻居集和原始向量数据存在一起。这样做的好处是可以利用数据的局部性。

如果索引文件放在 SSD 上,为了保证搜索时延,尽可能减少磁盘访问次数和减少磁盘读写请求。因此 DiskANN 提出两种优化策略:

  1. 缓存热点:将起点开始 C 跳内的点常驻内存,C 取 3~4 就比较好。
  2. beam search: 简单的说就是预加载,搜索 p 点时,如果 p 的邻居点不在缓存中,需要从磁盘加载 p 点的邻居点。由于一次少量的 SSD 随机访问操作和一次 SSD 单扇区访问操作耗时差不多,所以我们可以一次加载 W 个未访问点的邻居信息,W 不能过大也不能过小,过大会浪费计算和 SSD 带宽,太小了也不行,会增加搜索时延。

此外 DiskANN 相对于 HNSW 或者 NSW 还有一个优势就是,其删除一个节点是真删除,而且删除后不会影响图的连通性。HNSW 删除一个节点后,可能会导致图的连通性被破坏,需要重新构建索引。而 DiskANN 删除一个节点后,策略是这样的:假设我们要删除的节点是**“郑州”**这个交通枢纽。郑州在中国的交通网络中是一个至关重要的“十字路口”。

  • 有高速公路从“北京”、“西安”开往郑州(这些是郑州的in-neighbors)。
  • 也有高速公路从郑州通往“武汉”、“南京”(这些是郑州的out-neighbors)。 当我们要删除“郑州”时:
    1. 确定“受影响的节点”:谁最受伤?是那些原本有路通往郑州的城市,比如“北京”和“西安”。它们的出行选择变少了。
    2. 提供“修复候选方案”:规划师对北京说:“你原来可以去郑州,现在郑州没了。但是郑州原来可以通往‘武汉’和‘南京’。现在我把‘武汉’和‘南京’作为新的高速公路修建提案交给你。”
    3. 运行 RobustPrune 进行智能决策:现在,北京的工程师(RobustPrune 算法)开始工作了。他会评估:
    • 输入:我(北京)现有的所有高速公路 + 新的提案(通往武汉、南京)。
    • 决策过程:在所有这些选项中,根据 RobustPrune 的核心原则(即选择那些能最大化导航效率、提供“捷径”且不冗余的连接),重新选出不超过 R 条最优的高速公路。
    • 可能的结果:
      • 北京的工程师发现,修一条到“武汉”的高速确实是个好捷径,于是决定修建。
      • 他发现到“南京”的提案并不比现有路线好,于是放弃。
      • 他甚至可能为了给到武汉的新路腾出名额(保持总数不超过R),拆掉了一条原来通往某个小城市的、不太重要的旧路。
    1. 对所有受影响节点重复此过程:“西安”的工程师也会做同样的事情。

最终结果: 通过在每个受影响的节点上运行 RobustPrune,我们不是盲目地添加所有可能的连接,而是让每个节点根据自己的局部情况和全局导航目标,自主地、智能地选择最优的连接来“修复”网络破损的地方。 这样,我们既成功删除了目标节点“郑州”,又没有破坏网络的稀疏性(度限制 R),同时还最大程度地保持了整个高速公路网的导航质量。这就是 Vamana 删除操作的精髓所在。

DiskANN 的优势在于可以在很小的内存占用下配合 SSD 达到不错的搜索性能,但是规格小的机器在构建索引的时候会比较慢,这里需要做好权衡。

FreshDiskANN 在 DiskANN 基础上做了进一步的优化,主要是针对数据更新的场景。FreshDiskANN 通过引入一个新的索引结构,称为 FreshVamana,并引入删除列表,来支持并加速对数据的的插入和删除。于此同时采用两阶段 StreamingMerge 算法,将索引分为长期索引,临时索引,通过分层支持大规模数据。

FilterDiskANN 从满足查询标签条件(如日期、价格范围、语言)的索引点中找到查询嵌入的最近邻,支持两种过滤算法:

  1. FilteredVamana:构建单一索引,其中每个顶点的邻居基于几何结构和共同标签来决定。该算法在图构建过程中同时考虑向量距离和标签信息,形成既考虑几何邻近性又考虑标签相关性的连接
  2. StitchedVamana:为每个标签构建单独的子图,然后将这些子图叠加并剪枝形成最终图。这种方法在实际数据集上consistently提供比FilteredVamana高2倍的QPS,但构建时间更长

3.3.5 Summary

这里总结下,内存占用上 HNSW 明显大于 IVF,LSH 以及 Flat(KNN),召回率以及搜索速度上 HNSW 优于 IVF,LSH。DiskANN 在内存占用上优于 HNSW,但是在构建索引的时候会比较慢,搜索速度上优于 HNSW,但是召回率上不如 HNSW。所以在选择向量搜索算法的时候需要根据自己的需求来选择。

3.4 向量搜索算法优化

通过减少 Vector 大小,或者通过降维让搜索更快,这里列举了一些常见的向量搜索算法优化方法。

3.4.1 PQ(Product Quantization)

这里借用知乎网友的一张图,没办法,网友的图画的太好了: /vector-search/v2-8f9324cc9ee6d50b4f72eea54ff4abdc_b.jpg

构建阶段:

  1. 首先将N个原始向量,各自切分为多个子向量。比如256维向量,切分为8个32维子向量
  2. 然后在每个子向量空间内进行聚类,可以采用KMeans等聚类算法。假设每个子空间有1024个聚类,对每个聚类中心编码,得到1024个ID
  3. 将原始向量编码成最近的聚类中心ID,最后做拼接

检索阶段:

  1. 检索向量进行切分
  2. 切分的子空间和计算每个聚类中心的距离,做成距离表
  3. 利用距离表计算query和候选样本在每个子空间的距离,累加后取 top-k

其中涉及到切分都可以使用并行求解,这里一般不直接使用 PQ 因为依旧需要很多的距离计算,这里一般先进行 IVF 找到最有希望的top-k cluster 然后再进行 PQ。

3.4.2 SQ(Scalar Quantization)

SQ 比较简单 编码: scalar = (max-min)/255, floor(value-min/scaler) 如果小于0 则取 0,大于 255 则取 255,这样就将向量压缩到 0-255 之间,这样可以减少向量的大小,但是会损失一些信息。 解码:value = min + (code + 0.5)*(max-min)/255

3.4.3 RabitQ

RabitQ 来源于论文 RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search, 具体理论细节可以参考 RabitQ 的作者 Jianyang Gao 的 blog

RabitQ 指出现阶段 PQ 算法的两个问题:

  1. 用 kmeans 的质心作为 codebook, 构建时启发式的近似估计,没有理论保证
  2. 距离估计,用量化后的向量和 query向量的距离估计原始向量和 query 向量的距离,缺乏近似误差范围 上述的两个问题就导致 PQ 可能在一些数据集或者真实场景下的误差非常大。

如何解决上述问题:

  1. codebook 构建阶段
    1. 首先对数据向量进行归一化,以便将它们对齐在单位超球面上D维空间
    2. 构建一组 $2^{D}$ 坐标为的双值向量 $−1/\sqrt{D}$或者 $+1/\sqrt{D}$(即,该集合由超立方体的顶点组成,这些顶点均匀地分布在单位超球面上)
    3. 通过将每个双值向量乘以随机正交矩阵来随机旋转双值向量(即执行一种 Johnson-Lindenstrauss 变换),为了消除确定性码本对特定向量的偏好
    4. 对于每个向量,将其与码本最接近的向量作为量化向量, 最小化内积。 由于每个量化向量都是旋转的 D 维双值向量,我们将其量化码表示为长度的位串D,其中 0 和 1 表示两个不同的值。 码本构造的基本原理是它具有清晰的几何解释(即码本中的向量是单位超球面上的一组随机旋转向量) 使得可以明确地分析数据向量、它们的量化向量和查询向量之间的几何关系。
  2. 距离估计
    1. 根据上述几何关系仔细设计了数据向量和查询向量之间距离的估计器,并证明这个估计器是无偏的,而且提供了误差范围。
    2. 于此同时在估计距离时,即使使用较短的量化代码,也能以较小的经验误差估计出大约一半的优势

RaBitQ’s distance estimator:

  • 单个 data vector 使用 bitwise 按位操作
  • 批量数据使用 SIMD 加速

使用随机 codebook 避免双值 codebook 在一些特定向量表现不佳,比如 $1/\sqrt{D}$… $−1/\sqrt{D}$ 和 (1, 0, 0, 0) 的量化,我们使用随机正交矩阵去乘这个 codebook, 让 codebook 单位向量有相同的概率旋转到单位超球面上的任意位置

这里使用通俗的话讲解下 RabitQ 的核心思想:

终极解说:高维空间中的闪电搜图术 (RaBiT-Q 算法)

想象一下,我们正在为世界上最大的图片分享网站(比如Instagram或Pinterest)构建一个“以图搜图”的功能。这意味着,当用户上传一张“沙滩上的柯基犬”的照片时,我们要能从包含数十亿张图片的数据库里,瞬间找出所有其他“沙舍上的柯基犬”或者类似的图片。

这是一个巨大的挑战。计算机是如何“看懂”并比较图片的呢?

  1. 第一步:把图片变成“数字指纹”——向量化

计算机无法直接理解图片。它首先需要通过一个复杂的神经网络(比如 ResNet),将每一张图片都转换成一个由很多数字组成的“列表”,我们称之为向量 (Vector)。这个向量就是这张图片的“数字指纹”,它捕捉了图片的核心特征。

  • 小白理解: 就像我们给人办身份证,除了照片,还会记录身高、体重、血型等信息。这里的“向量”就是一张图片的“数字身份证”,包含了成百上千个描述它的“特征值”。
  • 专业说明: 我们使用预训练的深度学习模型(如CNN)提取图像的高维特征嵌入(Embedding)。例如,一张图片可以被表示为一个128维或更高维度的浮点数向量 o_r。我们的目标是,对于一个查询向量 q_r,找到数据库中与它欧几里得距离 ||o_r - q_r|| 最小的那些 o_r
  1. 第二步:数据大扫除——归一化

直接比较这些原始的“数字指纹”既慢又困难。数据可能存在整体的偏移,而且每个向量的“长度”(数值大小的综合体现)也各不相同,会干扰我们对“方向”(内容相似度)的判断。

操作:

  1. 中心化 (Centering): 找到所有图片向量的“平均位置”(即质心 c),然后让每个向量都减去这个中心。这相当于把整个坐标系的原点“搬”到了数据中心。
  2. 归一化 (Normalization): 将每个中心化后的向量,都除以它自身的长度。

结果: 经过这一步,所有的“数字指纹”都发生了两个变化:

  1. 它们不再是散乱的,而是都围绕着同一个中心。
  2. 它们的“长度”都变成了 1。它们现在都“趴”在一个巨大的、高维的“单位球”的球面上。这个操作保留了向量的方向(代表图片内容),而舍弃了其原始的长度
  • 小白理解: 想象一下,我们把所有人的身高都统一拉伸或压缩到1米8,但保持他们高矮胖瘦的“比例”不变。这样一来,大家就都站在了同一个起跑线上,比较起来更公平。
  • 专业说明: 我们将欧氏距离的计算问题,通过 ||o_r - q_r||² = ||o_r - c||² + ||q_r - c||² - 2 · ||o_r - c|| · ||q_r - c|| · <q, o> 这个公式,转化为了一个求解归一化后的单位向量 qo 之间内积 <q, o> 的问题。||o_r - c|| 可以在索引时预计算,||q_r - c|| 在查询时计算一次即可。问题的核心变成了如何快速估算 <q, o>
  1. 第三步:建立“宇宙坐标系”——码本构建与随机化

即使所有向量都在单位球面上,它们的数量依然是海量的。逐一比较内积还是太慢。我们需要一套“参考坐标系”来快速给向量定位。

操作:

  1. 码本 (Codebook): 我们在球面上建立一套“参考点”。一个绝妙的方法是,在球内部嵌入一个超立方体 (Hypercube),它的所有顶点正好落在球面上。这些顶点非常均匀、对称地分布,是理想的参考点。这些顶点的集合,就是我们的“码本”。
  2. 随机化 (Randomization): 一个固定的码本有其“盲区”。为了解决这个问题,我们不使用一个固定的立方体,而是将它进行随机旋转。我们生成M个(比如16个)不同的随机旋转,得到M个姿态各异的“码本”。
  • 小白理解: 为了快速找到地球上的某个城市,我们不会去拿尺子量。我们会先看它属于哪个大洲(亚洲、欧洲…)。这里的“码本”就是“七大洲”的划分。但固定的七大洲划分可能对某些边界城市不友好。于是我们干脆设计了M套不同的、随机的“大洲划分法”,从多个角度来给城市定位。
  • 专业说明: 码本由超立方体的顶点 {-1, 1}^D 构成。我们生成M个随机正交旋转矩阵 R_1, ..., R_M。这样我们就拥有了M个码本 C_1, ..., C_M,其中 C_i 是由 R_i 旋转后的超立方体顶点组成。
  1. 第四步:给每张图片打上“压缩编码”——索引构建

这是离线完成的工作,为整个数据库建立一个可以被闪电搜索的索引。

操作: 对于数据库中的每一张图片(即每一个向量 o),我们:

  1. 用之前生成的那M个旋转矩阵,去旋转 o
  2. 将M个旋转后的 o,分别在M个码本中找到离它最近的那个顶点
  3. 因为超立方体的顶点坐标只有+1和-1,我们可以用1个比特(0或1)来表示。比如+1记为1,-1记为0。
  4. 这样,每个 o 就被转换成了 M个D维的二进制码。

结果: 一个原本需要数千比特存储的浮点数向量,现在被压缩成了M个D比特的二进制码。存储空间大大减少,而且为后续的快速位运算比较铺平了道路。

  • 小白理解: 我们为公司里的每个员工,都从M个不同的角度拍了一张照片,并根据照片生成了一个极简的“素描码”(二进制码),存入他的档案。
  • 专业说明: 对于每个 o,我们计算 b_i = Quantize(R_i * o),其中 i=1...MQuantize 函数找到超立方体中最近的顶点,并将其表示为D比特的二进制码 b_i。同时,我们也预计算并存储 <ō, o>,即量化误差项,为后续的精确估算做准备。
  1. 第五步:闪电搜索——查询与估算

当用户上传新图片(查询向量 q)时,实时搜索开始。

  1. 查询编码: q 必须经历和数据库图片完全相同的处理:归一化、用M个相同的旋转矩阵旋转、生成M个二进制码。
  2. 粗筛 (Candidate Search): 我们拿着 q 的M个二进制码,去数据库中进行M次搜索。搜索方式不是慢速的浮点数计算,而是超高速的位运算。我们计算查询码和数据库码的汉明距离(有多少位不同),快速捞出几百个最相似的“候选图片”。
  3. 精排 (Re-ranking & Estimation): 对这几百个候选图片,我们不能再用粗糙的二进制码了。我们需要一个更精确的估算。这时,那个神奇的估算公式登场了: <o, q> ≈ <ō, q> / <ō, o>
    • 这个公式告诉我们,我们想知道的真实内积 <o, q>,可以用两个可以被高效计算或提前算好的值来近似!
    • 为什么可以这么近似? 因为我们天才地利用了高维空间的集中现象。我们证明了,在随机旋转下,那个导致公式无法简化的“麻烦项”<ō, e₁> 的值会高度集中于0,可以忽略不计。这是整个算法的理论基石和点睛之笔。
  4. 最终排序: 我们用这个估算公式,为几百个候选图片计算出近似的内积,再结合之前的第一步公式,得到最终的距离排序。将最靠前的结果返回给用户。
  • 小白理解:
    1. 新来的访客也被从M个角度拍了照,生成M个“素描码”。
    2. 安保拿着这些素描码去档案室飞快地比对(位运算),找出几百个长得最像的员工。
    3. 对于这几百个员工,安保不再看素描了,而是拿出一个“神奇估算器”,通过一个简单的除法,就估算出了访客和每个候选员工的真实“亲近度”,然后排出名次。

总结

这个算法的精髓在于,它完美地融合了多个强大的思想:

  1. 降维思想: 将复杂的欧氏距离问题,转化为更简单的单位向量内积问题。
  2. 量化压缩: 用超立方体码本将浮点向量压缩成二进制码,极大节省了存储和计算。
  3. 随机化思想: 通过随机旋转码本,避免了算法的“盲区”,并激活了高维空间的奇特性质。
  4. 高维几何: 深刻利用了“集中现象”这一反直觉的数学事实,推导出了一个极为高效的近似估算公式,完成了从“粗筛”到“精排”的最后一跃。

最终,一个原本需要在数十亿数据上进行慢速、精确计算的“不可能完成的任务”,被巧妙地分解为离线编码在线闪搜两部分,实现了在保证高精度的前提下的数量级加速。

4. 常见的向量数据库极其优劣

以下列举了一些常见的向量数据库,以及他们的优劣势, 有些是专用向量数据库,有些是对现有关系型数据库的扩展。

4.1 Milvus

Milvus 是一款非常优秀的开源向量数据库,支持多种向量搜索算法,包括 HNSW,DiskANN,IVF 等。 除了向量检索的基本功能,还提供 sharding, streaming data ingestion 以及 hybrid search 等功能。

Milvus 采用的是云原生的存算分离,shared-everthing 的架构,且控制面和数据面分离。各个组件都是独立且可横向拓展的,分别为:

  • 接入层(Access Layer):由一组无状态 proxy 组成。它对外提供用户连接的 endpoint,负责验证客户端请求并合并返回结果。
    • 它使用Nginx、Kubernetes Ingress、NodePort、LVS 等负载均衡组件提供统一的服务地址。
    • 由于 Milvus 采用大规模并行处理 (MPP) 架构,代理会聚合并后处理中间结果,然后将最终结果返回给客户端
  • 协调服务(Coordinator Service):负责分配任务给执行节点,分别有 root coord、data coord、query coord。
    • root coord: 处理数据定义语言 (DDL) 和数据控制语言 (DCL) 请求,例如创建或删除集合、分区或索引,以及管理 TSO(时间戳 Oracle)和 time ticker。
    • data coord: 管理 data 以及 index 节点拓扑,维护元数据,触发flush、compact、索引构建等后台数据操作。
    • query coord: 管理查询节点 topology ,load balancing 以及 growing segments 到 sealed segments 转换.
  • 执行节点(Worker Node):执行从协调服务分配的任务以及 proxy DML 命令。
    • Query Node:检索增量日志数据,并通过订阅 log broker 将其转换为不断增长的段,从对象存储加载历史数据,并在向量和标量数据之间运行混合搜索。
    • Data Node:通过订阅 log broker 来获取增量日志数据,处理mutation 请求,并将日志数据打包成日志快照并存储在对象存储中
    • Index Node:构建索引。索引节点不需要常驻内存,可以通过Serverless 框架来实现。
  • 存储层(Storage):对象存储负责存储数据,存储 Data files 和 Index files。
    • Meta Storage: 元存储存储元数据的快照,例如 collection schema 和 message consumption checkpoints。存储元数据需要极高的可用性、强一致性和事务支持,因此 Milvus 选择了 etcd 进行元存储。 Milvus 还使用 etcd 进行服务注册和健康检查。
    • Object Storage: 存储日志的快照文件、标量和向量数据的索引文件以及中间查询结果。 Milvus 使用 MinIO 作为对象存储,可以轻松部署在 AWS S3 和 Azure Blob。但对象存储的访问延迟较高,且按查询次数收费。为了提高性能并降低成本,Milvus 计划在基于内存或 SSD 的缓存池上实现冷热数据分离。
    • Log Broker: 发布-订阅系统,它负责流数据持久化和事件通知。当工作节点从系统故障中恢复时,它还能确保增量数据的完整性。 Milvus cluster 使用 Pulsar 作为 log broker; Milvus standalone 使用 RocksDB 作为 log broker。此外,log broker 可以很容易地替换为Kafka等流数据存储平台。

Milvus 的云原生架构是其优点,同时也给开发者带来了不小的挑战,例如新概念的学习,以及相关组件 Pulsar 或者 etcd 带来的运维管理上的挑战。

4.4 Pinecone

4.5 Qdrant

4.6 Pgvector

4.7 Pgvecto.rs

4.8 VectorChord

如何选型参考 大模型时代如何选向量数据库?Milvus(Zilliz)、LanceDB、Chroma、Pinecone四大热门技术全解析!

5. 优秀的向量搜索库以及向量数据库开源项目

6. 向量数据库商业化你需要知道什么

7. 总结

在这里我也是走马观花的介绍了一些向量搜索的基础知识,以及一些常见的向量搜索算法,向量搜索应用场景,向量搜索算法优化,常见的向量数据库极其优劣势,优秀的向量搜索库以及向量数据库开源项目,后面还是希望可以应用到实际的场景中。希望这篇文章能够帮助你更好的了解向量搜索。

8. 引用

非常感谢 Pinecone 的文章,让我对向量数据库有了更深的了解。