Milvus 向量数据库架构手册——探索 Milvus 的向量引擎

0 阅读20分钟

本章将深入 Milvus 的核心组件之一,也就是向量引擎 Knowhere。Knowhere 是一个专为向量数据库设计的高性能向量搜索引擎,负责处理向量数据的索引构建和搜索等关键任务。通过本章,你将理解 Knowhere 的架构设计、核心功能,以及它在高维向量搜索中的独特优势。无论你是初学者还是经验丰富的实践者,本章都会为你提供一个关于 Knowhere 的全面视角,帮助你深入掌握 Milvus 向量搜索系统的核心引擎。

本章将覆盖以下主题:

  • 介绍 Milvus 的核心引擎:Knowhere
  • 使用 Knowhere 的主要 API
  • 理解 Knowhere 的功能

技术要求

在开始本章之前,我们认为你应该对什么是向量、什么是向量索引,以及什么是向量索引算法库有一个基本理解。你也应该清楚向量相似性搜索和传统精确搜索之间的区别。本章不会花篇幅介绍这些基础概念。如果你不熟悉这些概念,可以先从 Wikipedia 开始学习:

https://en.wikipedia.org/wiki/Vector_database

让我们从向量操作引擎的定义开始。

介绍 Milvus 的核心引擎:Knowhere

Milvus 和 Knowhere 的关系可以用一句话概括:如果把 Milvus 比作一辆跑车,那么 Knowhere 就是这辆跑车的发动机。跑车能跑多快,很大程度上取决于发动机的动力和效率;同样,Milvus 作为向量数据库的性能,例如 queries per second(QPS)或 latency,很大程度上取决于 Knowhere 的性能。

QPS 和 latency

QPS 指系统每秒可以处理的 queries 或 requests 数量。它是衡量系统吞吐量的指标。更高的 QPS 表示更强的处理能力。

Latency 指从发送 request 到收到 response 所花费的时间。它通常以毫秒为单位,用于表示系统响应速度。更低 latency 意味着更快响应时间和更好用户体验。高 latency 会导致长等待时间,对系统性能产生负面影响。

理解 Knowhere 的功能范围

Knowhere 的功能范围有两种:狭义和广义。Milvus 项目启动时,设计目标之一是方便集成市场上所有流行的向量索引算法库。因此,在 Milvus 的系统架构设计中,底层向量索引算法库,例如 Faiss、HNSWlib、DiskANN 等,和上层服务,例如调度,之间设计了一层封装。它为上层提供了统一接口,用于所有索引相关操作。

这样一来,上层模块可以通过统一接口选择不同 indexes,并执行不同 index operations,从而实现上层调度与具体底层索引算法之间的解耦。这一层接口封装,就是早期狭义上的 Knowhere。

Heterogeneous computing,也就是异构计算,同样由 Knowhere 的接口封装层控制。它用于管理向量索引的构建和查询到底在哪里执行:在 CPU、GPU,或者未来可能是在 data processing unit(DPU)/ tensor processing unit(TPU)上执行。这也是 Knowhere 这个名字的来源:know where。

我们将在后续 “Knowhere architecture” 小节中接触更多关于 Knowhere 层的细节。

在最早期形态中,Knowhere 只是在所有第三方索引库之上提供一层接口封装,并不修改第三方库代码。然而,随着项目演进,我们,也就是 Milvus 的核心开发和维护团队,在第三方索引库中做了越来越多功能改进。例如,我们让 Faiss 中的 Inverted File Index(IVF)系列 indexes 支持 thread-safe;增强 HNSWlib,使其支持新的 metric type:COSINE;在 DiskANN 中实现 Range Search,等等。此外,Knowhere 层本身也新增了 version control 和 thread management 等更多功能。如果你对这些主题感兴趣,可以在后文 “Understanding Knowhere's functionality” 小节中找到更详细介绍。因此,我们扩展了 Knowhere 的含义和范围。现在,从广义上看,Knowhere 不再只是第三方索引库的简单 wrapper,而是所有与 vector operations 相关模块的统称,包括第三方索引库本身。

从前面对广义 Knowhere 的定义中,我们可以理解 Knowhere 的功能范围。Knowhere 只处理与 vector operations 相关的任务。其他任务,例如 data sharding、load balancing、data storage、disaster recovery、query optimization 等,都由 Milvus 的其他模块处理,与 Knowhere 无关。

Milvus 作为 AI 专用数据库,最基础的功能之一是 data storage 和 data computation。Data computation 可以进一步分为 scalar computation 和 vector computation。

Milvus 作为专用向量数据库,主要聚焦 vector computations,scalar computations 则是辅助功能。Scalar computations 主要用于 attribute filtering。如果你的数据表只包含 scalar data 而没有 vector data,那么 Milvus 可能不适合你;你可以选择其他传统关系型数据库,例如 Oracle 或 MySQL。

Milvus 中的 scalar computations 处理一维数据,并支持以下类型的操作:

Logical operations

  • AND:组合多个必须全部为真的条件
  • OR:组合至少一个为真的条件
  • NOT:对条件取反

示例:查找所有 price 大于 100 且 stock 大于 50 的 products,可以使用:

filter = 'price > 100 AND stock > 50'

Arithmetic operations

  • +,addition,加法
  • -,subtraction,减法
  • *,multiplication,乘法
  • /,division,除法
  • %,modulus,取模
  • **,exponentiation,幂运算

示例:查找 total 等于 base_pricetax 之和的 entities,可以使用:

filter = 'total == base_price + tax'

Comparison operations

  • ==,equal to,等于
  • !=,not equal to,不等于
  • >,greater than,大于
  • <,less than,小于
  • >=,greater than or equal to,大于等于
  • <=,less than or equal to,小于等于

示例:查找所有 rating 大于等于 4 的 entities,可以使用:

filter = 'rating >= 4'

Others

  • IN,contain in,包含于
  • LIKE,match to pattern,匹配模式

示例:查找所有指定颜色的 entities,可以使用:

filter = 'color in ["white", "red", "yellow"]'

Milvus 中的 vector computations 处理高维数据,维度范围从几十到数万不等。Vector data 没有明确定义的数学顺序,因此我们不能像传统数值或字符串数据那样通过排序来加速 vector queries。我们需要特殊的 index structures 和 storage methods。此外,vector computations 是 approximate queries,而不是 exact queries,通常使用 vector L2 distance、inner product distance 等来判断是否满足查询条件。

Milvus 中所有 vector computations 都由 Knowhere 管理,而 scalar computations 则在其他模块中执行。

Milvus data model

让我们从 Milvus data model 角度理解 Knowhere 的功能范围。

image.png

图 9.1:Milvus data model

图 9.1 展示了 Milvus 的数据模型。还记得我们在第 5 章讨论过以下概念。这里结合 Milvus data model 简要回顾:

Collection:在 Milvus 中,collection 可以类比为关系型数据库中的 table。Collection 是 Milvus 中最大的数据单元。

Shard:为了在数据写入时充分利用 cluster 的分布式特性,Milvus 可以将数据写入同时分布到多个 nodes 上。每个 shard 是 collection 的物理数据分区。如图 9.2 所示,collection 分布在三个 data nodes 上,即 QueryNode0 到 QueryNode2,分别对应物理数据分区 Shard0、Shard1 和 Shard2。

Partition:每个 collection 内部的数据也可以逻辑划分为多个 partitions。常见 partitioning 方法包括按日期、性别、用户年龄等划分。在图 9.2 中,partition 是根据数据生成日期创建的。一个 partition 内的数据可以跨越多个 shards。

Segment:每个 shard 或 partition 由多个 segments 组成。Segment 是 Milvus 中最小的查询单元。对 collection 的一次 query operation,最终会被拆解为对若干 segments 的 query operations,然后将所有 segments 的 query results 合并,得到最终结果。

下面进一步讨论为什么 segments 对 Knowhere 很重要。

为了支持 streaming data insertion,segments 被分为两类:growing segments 和 sealed segments。Growing segments 允许持续添加数据。当大小或时间达到某个阈值时,growing segment 会变成 sealed segment。每个 segment 由多个 fields 组成。这里的 idtimestamp 是 segment 自带的 system-default fields。其余 fields 则由用户在创建 collection 时通过 schema 指定。用户指定的 fields 必须包含一个 primary key column 和一个 vector column。

image.png

图 9.2:Segment data model

如图 9.2 所示,Knowhere 能够处理的数据基本单元,只是 segment 中的单个 vector column。

接下来,我们来看 Knowhere 的内部架构。

Knowhere architecture

作为 Milvus 的核心向量搜索引擎,Knowhere 会与 QueryNode 和 IndexNode 交互。在 Milvus 架构中,QueryNode 负责 query execution,而 IndexNode 负责 index construction。这两个组件是 Knowhere 提供的 API interfaces 的唯一调用者。图 9.3 具体说明了 Knowhere 的含义和范围:

image.png

图 9.3:Knowhere 内部架构

如图 9.3 中实线框 KNOWHERE 所示,早期狭义 Knowhere 只为上层提供一层接口封装。当前广义 Knowhere,如图 9.3 中虚线框 KNOWHERE EXTENDED 所示,既包括狭义 Knowhere,也包括所有第三方索引库。

由于 Knowhere 处理的 vector computations 是计算密集型任务,并且对程序执行效率有严格要求,整个 Knowhere 模块都使用 C++ 编写。Knowhere 会以动态链接库 libknowhere.so 的形式编译进 Milvus binary。Milvus 的所有组件,包括 IndexNode 和 QueryNode,都是用 Go 实现的。IndexNode 和 QueryNode 通过一层 CGO 调用 Knowhere 提供的接口。

注意,CGO 在基于 Go 的系统中,例如 Milvus,充当关键 interoperability layer。它允许 Go 组件,即 IndexNode 和 QueryNode,调用 Knowhere 库中的原生 C / C++ interfaces,从而弥合 Go runtime environment 和底层 C / C++ implementations 之间的差距。

从 Milvus 2.0 开始,我们将 Knowhere 和所有第三方索引库从 Milvus 项目中分离出来,并建立了一个独立开源项目 Knowhere:

http://www.github.com/zilliztech/knowhere

Knowhere 有自己的 Python interface,即 pyknowhere,自己的 unit tests,以及自己的 performance testing framework。从那以后,向量引擎 Knowhere 和 Milvus 可以独立开发,互不影响。

现在,我们将关注 Knowhere 的 APIs。

使用 Knowhere 的主要 API

你应该已经熟悉什么是 vector index。Vector index 是一种独立于原始 vector data 的数据结构。

如本章概览中所说,Knowhere 最初被引入,是为了在所有第三方索引库之上提供一层接口封装。这允许上层模块通过统一接口选择不同 indexes,并执行各种 index operations。不同 indexes 提供的 interface names 可能不同,但它们都会提供以下类别的 interfaces:

  • 与 index construction 相关的 interfaces
  • 与 index serialization 和 deserialization 相关的 interfaces
  • 与 querying 相关的 interfaces
  • 其他 interfaces

我们将在第 10 章详细讨论这些 indexes。

下面先对 interface parameters 和这些 interface categories 做通用介绍和说明。

Knowhere parameters

由于 Knowhere 的 function interfaces 需要兼容所有第三方索引库的 function interfaces,因此 function parameters 设计采用了一种半开放形式。三个最重要的 interface parameters 如下:

DataSet:本质上,DataSet 是一个存储 key-value pairs 的 map。我们使用 DataSet 存储所有 vector-related information。DataSet 可以作为 input parameter,包含 vectors 的 row number、dimension 和 original data tensor;也可以作为 output parameter,保存 query results,例如 IDs 和 distances。

JSONJSON 只能作为 input parameter 使用,用于存储与 index 相关的 configuration parameters。不同 indexes 的 configuration parameters 差异很大。使用 JSON 这种 lightweight data interchange format,无疑是最合适的选择。这是因为 JSON 易读易写,同时具备优秀的跨语言和跨平台特性。

BitsetViewBitsetView 是二进制 bitstream,其 bit 数量与输入 vectors 的 row 数一一对应。例如,如果第 n 个 bit 是 1,表示第 n 行 vector 无效,可能已被删除,或者不满足 scalar filtering conditions,因此不会参与 query computation;这行 vector 也不会出现在 query results 中。BitsetView 只能作为 input parameter 使用。当 BitsetView 为 null 时,其效果等同于 BitsetView 中所有 bits 都为 0,表示所有 input vectors 都有效,都会参与 query。

如果你只是 Milvus 用户,不会直接遇到这三种 parameter types,因为它们不会暴露在 Milvus SDK 中。不过,如果你深入 Knowhere 代码,会发现这三种 parameter types 几乎存在于 Knowhere 的所有 interface functions 中。

你可以在这里获取所有 index-related interface definitions:

https://github.com/zilliztech/knowhere/blob/main/include/knowhere/index/index.h

下面片段是接口代码,清楚展示了这三种数据类型如何在 interface functions 中使用:

Status Build(const DataSetPtr dataset, const Json&json);
Status Train(const DataSetPtr dataset, const Json&json);
Status Add(const DataSetPtr dataset, const Json&json);
expected<DataSetPtr> Search(const DataSetPtr dataset, const Json& json, const BitsetView& bitset) const;
expected<DataSetPtr> RangeSearch(const DataSetPtr dataset, const Json& json, const BitsetView& bitset) const;
expected<DataSetPtr> GetVectorByIds(const DataSetPtr dataset) const;
bool HasRawData(const std::string& metric_type) const;
Status Serialize(BinarySet& binset) const;
Status Deserialize(const BinarySet& binset, const Json& json = {});

我们以 Knowhere 中最重要的 interface function Search() 为例。

Search() 的 input parameters 包括:dataset,保存 input vector 的所有信息,例如 rows、dimension 和 vector raw data;json,保存 index 指定的 search parameters 的所有信息,例如 IVF index 中的 nprobe;以及 bitset,表示每个 vector row 是否有效。

Search() 的 output parameter 同样是 DataSet 类型,用于保存 search result 的所有信息,例如 result IDs 和 distances。

正如本章前面提到的,所有 indexes 都会向外提供 index creation、serialization and deserialization、querying 等 interfaces。下面对这些 interfaces 做简要说明。

Train()、Add() 和 Build()

一般来说,一个 vector index 从创建到应用会经历四个步骤,如图 9.4 所示:创建 index、training、adding data 和 searching。下图中的 Create()Train()Add()Search() 函数,分别对应 Knowhere 中的四个 interface functions:

image.png

图 9.4:Knowhere 中的 index flow

Create() 函数会创建某种类型的空 index,此时它不包含任何数据。Train()Add() 函数则是真正向 index 添加真实数据的函数。在 Train()Add() 之后,我们就可以使用这个 index 执行 Search()

在一些 AI applications 中,用于 training 和 adding 的 datasets 是分开的。例如,在公共数据集 sift-128-euclidean 中:

http://ann-benchmarks.com/sift-128-euclidean.hdf5

分别有标记为 traintest 的 training 和 testing datasets。Training dataset 会先用于 training,然后基于 training results,将 testing data 插入 index。

不过,Knowhere 并不区分 training 和 testing data。图 9.4 中的 base dataset,是从 Milvus IndexNode 传入的某个 segment 的 vector column data。Knowhere 会先使用这些数据进行 training,然后基于 training results,将同一批数据插入 index。

由于 Knowhere 对所有 indexes 都是先执行 training(train),再添加同一批数据(add),因此它还提供了一个新接口:Build(),它只是把 Train()Add() interfaces 组合在一起。构建 Knowhere 中的 index 时,可以直接调用 Build() interface,其效果与分别调用 Train()Add() interfaces 相同。保留 Train()Add() interfaces,是为了维持 AI applications 的语义习惯,并出于可扩展性考虑。这也是为了支持未来可能出现的新 indexes,这些 indexes 或许需要对 Train()Add() 分别进行特殊处理。

由于这些 interfaces,即 Build()Train()Add(),由 Knowhere 和 Milvus 内部使用,用户不会直接与它们交互,因此这里不提供展示这些 interfaces 用法的代码片段。

接下来,我们学习 index serialization 和 deserialization。

Serialize() 和 Deserialize()

Index serialization 和 deserialization 对支持 indexes 的存储与传输至关重要。构建 index 是一项计算密集且耗时的任务。因此,对于 segment 中某一列 vector data,我们通常只构建一次 index,并将其序列化,作为文件持久化到 object storage 中,例如 S3 或 MinIO。当需要 index 时,我们从 object storage 读取它,然后反序列化并加载到内存中,以执行 query operations。

具体来说,在 Milvus 中,IndexNode 负责 index building 和 storage。IndexNode 调用 Knowhere 的 Create()Build() interfaces 创建 index,并使用 Knowhere 的 Serialize() interface 将 index 序列化为 index file,然后持久化到 object storage。QueryNode 负责 index loading 和 querying。QueryNode 首先从 object storage 加载 index file,然后调用 Knowhere 的 Deserialize() interface 对其反序列化。

最后,我们调用 search interface 执行 queries,下一节会讨论。

由于这些 interfaces,即 Serialize()Deserialize(),由 Knowhere 和 Milvus 内部使用,用户不会直接与它们交互,因此这里不提供展示这些 interfaces 用法的代码片段。

Search() 和 RangeSearch()

Search() interface 是 Knowhere 中最基础、也是最早实现的 interfaces 之一。Search() 的输入是 nq 参数名称所表示的 query vectors,它执行 approximate nearest neighbor search(ANNS),为每个 input vector 找到 topk 个最相似结果。最后,它返回总共 nq * topk 个结果的 IDs 及对应 distances。

Search operation 有一定限制。首先,Search 总是返回最相似结果,但有时用户并不总是想要最相似结果。其次,虽然 Knowhere 不对 topk 做任何限制,但 Milvus 出于系统开销考虑,将单次 search 中每个 vector 返回结果数量限制为最大 16,384。

由于 Search operation 的这些限制,一位在 recommendation system 上工作的用户在社区中提出了 RangeSearch 需求。他们希望 Milvus 能提供一种基于范围的 query function,返回落在某个 distance range 内的结果。这是因为一个好的 recommendation system 应该提供一定相关但又不过于相似的结果。一定相似意味着 vector distance 不应太远;但也不能太相似,因为过于相似的结果很可能是完全相同的新闻文章或商品。因此,vector distance 应该处于一个 range scope 内。我们看图 9.5,它说明了 SearchRangeSearch 的概念:

image.png

图 9.5:Search 和 RangeSearch

Search 类似于在 vector space 中画出一个最小圆,使得只有 topk results 落在圆内。相比之下,RangeSearch 则像在 vector space 中标记一个环形区域,similarity 应落在内外边界之间。

由于这些 interfaces,即 Search()RangeSearch(),由 Knowhere 和 Milvus 内部使用,用户不会直接与它们交互,因此这里不提供展示这些 interfaces 用法的代码片段。

Knowhere 的 query interfaces,即 SearchRangeSearch,会返回 result vectors 的 IDs 及对应 distance values。不过,有些用户希望 Milvus 在 query results 中同时返回原始 vector data,用于验证 query results 的准确性,或用于其他 machine learning tasks。下面看看如何做到这一点。

HasRawData() 和 GetVectorByIds()

HasRawData()GetVectorByIds() 是支持 Milvus 在 query results 中同时向用户返回原始 vector data 的两个 interfaces。

Milvus 的 query tasks 由 QueryNode 执行,而 QueryNode 只加载 index files,并不会加载原始 vector data。一种解决方案是让 DataNode 从 object storage 中加载原始 vector data,并与 QueryNode 的 query results 一起返回。虽然这个方案能满足用户需求,但性能较差。

考虑到某些 index data structures,例如 FLATIVF_FLATHNSW index type,实际上会存储原始 vector data,因此可以直接从 index 中读取原始 vectors。所以,HasRawData() interface 用于判断 index 是否存储原始 vector data。FLATIVF_FLATHNSW 等 indexes 对 HasRawData() interface 返回 TrueGetVectorByIds() interface 顾名思义,会返回指定 IDs 对应的原始 vector values。

由于这些 interfaces,即 HasRawData()GetVectorByIds(),仅由 Knowhere 和 Milvus 内部使用,用户不会直接与它们交互,因此这里不提供展示这些 interfaces 用法的代码片段。

接下来,我们将讨论一些在 Knowhere 层实现的重要功能。

理解 Knowhere 的功能

在前面几章中,我们提到过,随着项目演进,我们不仅在第三方 index libraries 中做了更多功能扩展和改进,也在 Knowhere 层新增了更多 features。

本节将介绍 Knowhere 层实现的三个 features:thread management、version management 和 Prometheus monitor。

Thread management

要理解为什么 Knowhere 需要实现 thread management,应该从 Milvus 系统层面考虑。

Knowhere 集成多个第三方 index libraries,而这些 libraries 在 ANNS 期间,通常使用 multi-threading 来最大化 CPU 利用率。例如,Faiss 中的 IVF 系列 indexes,会在 search operations 期间为每个 query vector 创建单独线程。当 Faiss 作为独立 index algorithm library 使用时,这种方法没问题。然而,当 Faiss 被集成到 Knowhere 并成为整个 Milvus 系统的一部分时,这种行为会显著影响 query performance。

在 “Milvus data model” 小节中介绍过,segment 是 Milvus 中的最小 query unit。对 collection 的一次 query operation,最终会被拆解为对若干 segments 的 query operations。

现在,假设有一个 QueryNode,其中有 n 个 segments,每个 segment 都创建了 IVF_FLAT index。当一个 query request 到达时,QueryNode 会为每个 segment 生成一个线程执行 query operation,总共形成 n 个 query threads。这 n 个 query threads 会调用 Knowhere 的 Search interface,而 Knowhere 又调用 Faiss IVF_FLAT index 的 Search interface。结果,每个 query thread 内部又为 queries 生成 nq 个 sub-threads。因此,总共会创建 (n * nq) 个线程。

每个 QueryNode 的物理资源有限。例如,一个拥有 16 cores 和 64 GB memory 的 node,最多只能同时运行 16 个线程。创建 (n * nq) 个线程会导致频繁 thread switching,从而显著降低计算效率。

Milvus 作为向量数据库,能够同时接收和处理多个 user query requests。因此,每个 QueryNode 上生成的线程数量会变成 N1 * N2 * N3,其中 N1 是某段时间内的 query requests 数量,N2 是 QueryNode 上的 segments 数量,N3 是每个 request 的平均 query vectors 数量。这么多线程会进一步加剧问题。这种现象有一个专门术语:thread explosion,如图 9.6 所示。

image.png

图 9.6:Knowhere 中的 thread explosion

为了解决 thread explosion 问题,Knowhere 在初始化时创建两个 thread pools:一个用于 building indexes,一个用于 querying。

每个 pool 中的线程数量,可以由用户根据 node resources,通过 milvus.yaml 配置文件中的配置参数指定:

  • querynode.segcore.knowhereThreadPoolNumRatio
  • common.buildIndexThreadPoolRatio

例如,下面代码片段更新了 knowhereThreadPoolNumRatio

queryNode:
  segcore:
    knowhereThreadPoolNumRatio: 4

所有第三方 index libraries 都以单线程方式运行。这样一来,每个 physical node 上的 multi-threaded concurrency 都由 Knowhere 统一管理,从而避免 thread explosion 发生。

Version management

Version management 是任何需要将数据持久化到 object storage,同时又需要从 storage 加载数据进内存的软件都会面对的挑战。随着 Milvus 和 Knowhere 不断引入新功能,indexes 的 data structures 必然会演进。Milvus 和 Knowhere 当前面临的 version compatibility issues,主要是 backward compatibility issues,也就是如何用新版 Milvus 打开旧版 Milvus 保存的 index files,例如如何用 Milvus v2.4.x 打开 Milvus v2.3.x 保存的 index files。

Knowhere 在 index data structure 中添加了 version field。借助这个 version field 中的信息,Knowhere 可以在不依赖任何额外外部信息的情况下,反序列化 index files。

Index files 的 version control 是在代码内部实现的,对用户透明。因此,本节不会深入 version control 的技术细节。感兴趣的读者可以直接参考源码了解更多信息:

https://github.com/zilliztech/knowhere/blob/main/include/knowhere/version.h

Prometheus monitor

通常,我们可以通过 logs 判断 Knowhere 是否正常运行。然而,为了给 cloud services 实现实时监控和可视化,我们在 Knowhere 中引入了 Prometheus monitoring system。

Prometheus 是一个开源 monitoring system 和 time-series database。它通过 HTTP pull model 从目标系统采集 metric data,并将这些数据存储在本地 time-series database 中。这种监控方式不仅能够实时反映系统运行状态,也能帮助我们快速识别和解决潜在问题。

在 Knowhere 中,我们使用 Prometheus C++ client 采样 performance data。这些数据包括但不限于 query latency、index build time、topk statistics、cache hit rates 等关键 metrics。通过 CGO layer,我们将这些 performance data 发送到 Milvus 侧,并与 Milvus 侧的 Prometheus data 集成。最终,整合后的数据会通过 Grafana 可视化,使用户能够在统一界面中直观查看 Knowhere 和 Milvus 的运行状态。

小结

本章介绍了 Milvus 的核心向量引擎 Knowhere。我们讨论了 Knowhere 的定义和范围、它向外提供的主要 interfaces,以及 Knowhere 层实现的功能,具体包括 thread management、version management 和 Prometheus monitor。

理解这些概念将帮助你全面掌握 Knowhere 的内部工作方式,使你能够更有效地使用 Milvus、排查底层问题,甚至扩展或自定义向量引擎以满足实际需求。

下一章中,我们将深入讨论 vector indexing,以及我们在 Knowhere 中对这些 indexes 所做的增强。