向量搜索数据库milvus入门教程

岁月带走的是记忆,但回忆会越来越清晰

Posted by yishuifengxiao on 2022-12-27

Milvus创建于2019年,其唯一目标是存储、索引和管理由深度神经网络和其他机器学习(ML)模型生成的大量嵌入向量。

作为一个专门设计用于处理输入向量查询的数据库,它能够在万亿规模上索引向量。与现有的关系数据库不同,Milvus主要按照预定义的模式处理结构化数据,它是自下而上设计的,用于处理从非结构化数据转换而来的嵌入向量。

随着互联网的发展和演变,非结构化数据变得越来越常见,包括电子邮件、论文、物联网传感器数据、Facebook照片、蛋白质结构等等。为了让计算机理解和处理非结构化数据,这些数据使用嵌入技术转换为矢量。Milvus存储并索引这些向量。Milvus能够通过计算两个向量的相似距离来分析它们之间的相关性。如果两个嵌入向量非常相似,则意味着原始数据源也相似。

一 基本概念

1.1 基础概念

1.1.1 Bitset

在Milvus中,位集是位数的数组,可以用来紧凑高效地表示某些数据,而不是用int、float或chars表示。默认情况下,位数仅在满足某些要求时设置为.0``1``0``1


1.1.2 Channel

在Milvus中有两种不同的Channel,分别称为 PChannelVChannel,其中每个PChannel对应于日志存储的主题,而每个VChannel对应于集合中的碎片。

  • PChannel

PChannel代表物理信道。每个PChannel对应于日志存储的主题。默认情况下,将分配一组256个PChannel来存储日志,这些日志记录Milvus集群启动时的数据插入、删除和更新。

  • VChannel

VChannel代表逻辑通道。每个VChannel表示集合中的一个碎片。每个集合将分配一组用于记录数据插入、删除和更新的VChannel。VChannel在逻辑上分开,但在物理上共享资源


1.1.3 Collection

Milvus中的collection相当于关系数据库管理系统(RDBMS)中的表table。在Milvus,集合(collection)用于存储和管理实体。


1.1.4 Dependency

依赖项是另一个程序赖以工作的程序。Milvus的依赖项包括etcd(存储元数据)、MinIO或S3(对象存储)和Pulsar(管理快照日志)。


1.1.5 Entity

实体(entity)由一组表示真实世界对象的字段(fields)组成。Milvus中的每个实体(entity)都由一个唯一的主键表示。

您可以自定义主键。如果不手动配置,Milvus会自动为实体分配主键。如果您选择配置自己的自定义主键,请注意Milvus目前不支持主键重复数据消除。因此,同一集合中可能存在重复的主键。


1.1.6 Field

字段是构成实体的单位。字段可以是结构化数据(例如数字、字符串)或向量。

从Milvus 2.0开始,scalar过滤可用!


1.1.7 Partition

分区(partition)是集合(collection)的一部分。Milvus支持在物理存储中将收集数据分成多个部分。这个过程称为分区,每个分区可以包含多个segment


1.1.8 Schema

模式(Schema)是定义数据类型和数据属性的元信息。每个集合(collection)都有自己的集合模式,该模式定义了集合的所有字段、自动ID(主键)分配启用和集合描述。集合模式中还包括定义字段的名称、数据类型和其他属性的字段模式。


1.1.9 Segment

Segment是Milvus自动创建的用于保存插入数据的数据文件。一个collection可以有多个Segment,而一个segment可以具有多个实体。在向量相似性搜索期间,Milvus扫描每个segment并返回搜索结果。一个segment可以是成长的,也可以是封闭的。一个不断增长的segment不断接收新插入的数据,直到它被密封。密封segment不再接收任何新数据,并将被刷新到对象存储,留下新数据插入新创建的增长segment。增长段将被密封,因为它持有的实体数量达到预定义的阈值,或者因为“增长”状态的范围超过指定的限制。


1.1.10 Sharding

分片是指将写操作分配给不同的节点,以充分利用Milvus集群的并行计算潜力来写数据。默认情况下,单个集合包含两个碎片。Milvus采用基于主键哈希的分片方法。Milvus的开发路线图包括支持更灵活的分片方法,如随机分片和自定义分片。

分区通过指定分区名称来减少读取负载,而分片在多个服务器之间分散写入负载。


1.1.11 Vector index

矢量索引是从原始数据导出的重组数据结构,可以大大加快矢量相似性搜索的过程。Milvus支持多种向量索引类型。


1.1.12 Embedding Vector

嵌入向量是非结构化数据的特征抽象,如电子邮件、物联网传感器数据、Instagram照片、蛋白质结构等。从数学上讲,嵌入向量是浮点数或二进制数的数组。现代嵌入技术用于将非结构化数据转换为嵌入向量。


1.1.13 Unstructured data

非结构化数据,包括图像、视频、音频和自然语言,是不遵循预定义模型或组织方式的信息。这种数据类型占世界数据的80%左右,可以使用各种人工智能(AI)和机器学习(ML)模型转换为向量。


1.1.14 Normalization

归一化是指将嵌入(向量)转换为范数等于1的过程。如果内积(IP)用于计算嵌入相似性,则所有嵌入都必须标准化。归一化后,内积等于余弦相似性。

1.2 Vector index

1.2.1 In-memory Index

本主题列出了Milvus支持的各种类型的内存索引、每种索引最适合的场景以及用户可以配置的参数,以实现更好的搜索性能。

索引是高效组织数据的过程,它通过显著加快大型数据集上耗时的查询,在使相似性搜索变得有用方面发挥着重要作用。

为了提高查询性能,可以为每个向量字段指定索引类型。

目前,矢量字段只支持一种索引类型。Milvus在切换索引类型时自动删除旧索引。

1.2.2 ANNS vector indexes

Milvus支持的大多数向量索引类型使用近似最近邻搜索(ANNS)算法。与通常非常耗时的精确检索相比,ANNS的核心思想不再局限于返回最准确的结果,而是只搜索目标的邻居。ANNS通过在可接受范围内牺牲精度来提高检索效率。
根据实现方法,ANNS矢量索引可分为四类:

  • Tree-based index
  • Graph-based index
  • Hash-based index
  • Quantization-based index

1.2.3 On-disk Index

为了提高查询性能,可以为每个向量字段指定索引类型。

目前,矢量字段只支持一种索引类型。Milvus在切换索引类型时自动删除旧索引。

前提条件

  • 确保已经设置了make disk_index=ON
  • Milvus 运行在 Ubuntu 18.04.6 或更新的版本上
  • 路径${MILVUS_ROOT_path}/MILVUS/data已装载到NVMe SSD以获得完全性能。

限制性

  • 数据中只能使用至少32维的浮点向量。
  • 仅使用欧氏距离(L2)或内积(IP)来测量向量之间的距离。

二 连接Milvus

2.1 使用的端口

Milvus 使用了19530 和 9091 两个端口

  • 19530端口用于gRPC。当您使用不同的Milvus SDK连接到Milvus服务器时,它是默认端口
  • 9091端口用于RESTful API。当您使用HTTP客户端连接到Milvus服务器时使用

2.2 创建连接Milvus

创建连接的代码如下

1
2
3
4
5
6
7
8
9
final MilvusServiceClient milvusClient = new MilvusServiceClient(
ConnectParam.newBuilder()
.withHost("localhost")
.withPort(19530)
.build()
);

// 释放连接
milvusClient.close()

参数解释如下:

  • Host :IP address of the Milvus server.
  • Port: Port of the Milvus server

三 集合管理

3.1 创建集合

集合(collection)由一个或多个分区组成。在创建新集合时,Milvus会创建默认分区_default

以下示例构建了一个名为book的双碎片集合,其中主键字段名为book_id,一个名为word_count 类型为 INT64 scalar 字段(field)和一个名为book_intro二维浮点向量字段。

要创建的集合(collection)必须包含主键字段和矢量字段。INT64和String是主键字段上支持的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FieldType fieldType1 = FieldType.newBuilder()
.withName("book_id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(false)
.build();
FieldType fieldType2 = FieldType.newBuilder()
.withName("word_count")
.withDataType(DataType.Int64)
.build();
FieldType fieldType3 = FieldType.newBuilder()
.withName("book_intro")
.withDataType(DataType.FloatVector)
.withDimension(2)
.build();

CreateCollectionParam createCollectionReq = CreateCollectionParam.newBuilder()
.withCollectionName("book")
.withDescription("Test book search")
.withShardsNum(2)
.addFieldType(fieldType1)
.addFieldType(fieldType2)
.addFieldType(fieldType3)
.build();
Parameter Description Option
Name Name of the field to create. N/A
Description Description of the field to create. N/A
DataType Data type of the field to create. For primary key field:entity.FieldTypeInt64 (numpy.int64)For scalar field:entity.FieldTypeBool (Boolean)entity.FieldTypeInt64 (numpy.int64)entity.FieldTypeFloat (numpy.float32)entity.FieldTypeDouble (numpy.double)For vector field:entity.FieldTypeBinaryVector (Binary vector)entity.FieldTypeFloatVector (Float vector)
PrimaryKey (Mandatory for primary key field) Switch to control if the field is primary key field. True or False
AutoID Switch to enable or disable Automatic ID (primary key) allocation. True or False
Dimension (Mandatory for vector field) Dimension of the vector. [1, 32768]
CollectionName Name of the collection to create. N/A
Description (Optional) Description of the collection to create. N/A
ShardsNum Number of the shards for the collection to create. [1,64]

3.2 使用约束创建集合

1
milvusClient.createCollection(createCollectionReq);

3.3 检查集合信息

3.3.1 判断集合是否存在

1
2
3
4
5
6
7
8
R<Boolean> respHasCollection = milvusClient.hasCollection(
HasCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);
if (respHasCollection.getData() == Boolean.TRUE) {
System.out.println("Collection exists.");
}

3.3.2 检查集合详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
R<DescribeCollectionResponse> respDescribeCollection = milvusClient.describeCollection(
// Return the name and schema of the collection.
DescribeCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);
DescCollResponseWrapper wrapperDescribeCollection = new DescCollResponseWrapper(respDescribeCollection.getData());
System.out.println(wrapperDescribeCollection);

R<GetCollectionStatisticsResponse> respCollectionStatistics = milvusClient.getCollectionStatistics(
// Return the statistics information of the collection.
GetCollectionStatisticsParam.newBuilder()
.withCollectionName("book")
.build()
);
GetCollStatResponseWrapper wrapperCollectionStatistics = new GetCollStatResponseWrapper(respCollectionStatistics.getData());
System.out.println("Collection row count: " + wrapperCollectionStatistics.getRowCount());

3.3.3 遍历所有集合

1
2
3
4
R<ShowCollectionsResponse> respShowCollections = milvusClient.showCollections(
ShowCollectionsParam.newBuilder().build()
);
System.out.println(respShowCollections);

3.4 删除集合

删除集合会不可逆地删除其中的所有数据。

1
2
3
4
5
milvusClient.dropCollection(
DropCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

3.5 集合别名

集合别名是全局唯一的,因此不能将相同的别名分配给不同的集合。但是,您可以将多个别名分配给一个集合。

3.5.1 设置集合别名

1
2
3
4
5
6
milvusClient.createAlias(
CreateAliasParam.newBuilder()
.withCollectionName("book")
.withAlias("publication")
.build()
);
Parameter Description
CollectionName Name of the collection to create alias on.
Alias Collection alias to create.

3.5.2 删除集合别名

1
2
3
4
5
milvusClient.dropAlias(
DropAliasParam.newBuilder()
.withAlias("publication")
.build()
);

3.5.3 修改集合别名

1
2
3
4
5
6
milvusClient.alterAlias(
AlterAliasParam.newBuilder()
.withCollectionName("book")
.withAlias("publication")
.build()
);
Parameter Description
CollectionName Name of the collection to alter alias to.
Alias Collection alias to alter.

3.6 加载集合

本主题描述如何在搜索或查询之前将集合加载到内存中。Milvus中的所有搜索和查询操作都在内存中执行。

Milvus2.1允许用户将集合加载为多个副本,以利用额外查询节点的CPU和内存资源。此功能可提高整体QPS和吞吐量,无需额外硬件。在当前版本中,PyMilvus支持它。

注意:

  • 在当前版本中,要加载的数据量必须低于所有查询节点总内存资源的90%,以便为执行引擎保留内存资源。
  • 在当前版本中,所有联机查询节点将根据用户指定的副本编号划分为多个副本组。所有副本组应具有最小的内存资源来加载所提供集合的一个副本。否则,将返回错误。
1
2
3
4
5
milvusClient.loadCollection(
LoadCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

存在的限制性条件如下:

  • 当父集合已加载时,尝试加载分区时将返回错误。未来的版本将支持从加载的集合中释放分区,然后(如果需要)加载其他分区。
  • 尝试加载已加载的集合时,将返回“加载成功”。
  • 当子分区已加载时,尝试加载集合时将返回错误。未来的版本将支持在某些分区已加载时加载集合。
  • 不允许通过单独的RPC加载同一集合中的不同分区。

3.7 释放集合

1
2
3
4
5
milvusClient.releaseCollection(
ReleaseCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

四 分区管理

Milvus允许您将大量向量数据划分为少量分区。然后可以将搜索和其他操作限制在一个分区内,以提高性能。
集合由一个或多个分区组成。

在创建新集合时,Milvus会创建默认分区_default

4.1 创建分区

1
2
3
4
5
6
milvusClient.createPartition(
CreatePartitionParam.newBuilder()
.withCollectionName("book")
.withPartitionName("novel")
.build()
);
Parameter Description
CollectionName Name of the collection to create a partition in.
PartitionName Name of the partition to create.

4.2 检查分区信息

4.2.1 判断分区是否存在

1
2
3
4
5
6
7
8
9
R<Boolean> respHasPartition = milvusClient.hasPartition(
HasPartitionParam.newBuilder()
.withCollectionName("book")
.withPartitionName("novel")
.build()
);
if (respHasPartition.getData() == Boolean.TRUE) {
System.out.println("Partition exists.");
}

4.2.2 遍历所有分区

1
2
3
4
5
6
R<ShowPartitionsResponse> respShowPartitions = milvusClient.showPartitions(
ShowPartitionsParam.newBuilder()
.withCollectionName("book")
.build()
);
System.out.println(respShowPartitions);

4.3 删除分区

删除分区会不可逆地删除其中的所有数据。

1
2
3
4
5
6
milvusClient.dropPartition(
DropPartitionParam.newBuilder()
.withCollectionName("book")
.withPartitionName("novel")
.build()
);

4.4 加载分区

将分区而不是整个集合加载到内存可以显著减少内存使用。Milvus中的所有搜索和查询操作都在内存中执行。

Milvus2.1允许用户将分区加载为多个副本,以利用额外查询节点的CPU和内存资源。此功能通过额外的硬件提高了整体QPS和吞吐量。在当前版本中,PyMilvus支持它。

注意如下:

  • 在当前版本中,要加载的数据量必须低于所有查询节点总内存资源的90%,以便为执行引擎保留内存资源。
  • 在当前版本中,所有联机查询节点将根据用户指定的副本编号划分为多个副本组。所有副本组应具有最小的内存资源来加载所提供集合的一个副本。否则,将返回错误。
1
2
3
4
5
6
milvusClient.loadPartitions(
LoadPartitionsParam.newBuilder()
.withCollectionName("book")
.withPartitionNames(["novel"])
.build()
);
arameter Description
CollectionName Name of the collection to load partitions from.
PartitionNames List of names of the partitions to load.

约束性条件如下:

  • 当父集合已加载时,尝试加载分区时将返回错误。未来的版本将支持从加载的集合中释放分区,然后(如果需要)加载其他分区。
  • 尝试加载已加载的集合时,将返回“加载成功”。
  • 当子分区已加载时,尝试加载集合时将返回错误。未来的版本将支持在某些分区已加载时加载集合。
  • 不允许通过单独的RPC加载同一集合中的不同分区。

4.5 释放分区

1
2
3
4
5
6
milvusClient.releasePartitions(
ReleasePartitionsParam.newBuilder()
.withCollectionName("book")
.withPartitionNames(["novel"])
.build()
);
Parameter Description
CollectionName Name of the collection to release partition.
PartitionNames List of names of the partitions to release.

五 数据管理

您还可以使用MilvusDM将数据迁移到Milvus,这是一个专门为Milvus导入和导出数据而设计的开源工具。

Milvus 2.1支持标量字段上的VARCHAR数据类型。为VARCHAR类型标量字段构建索引时,默认索引类型为字典树。

以下示例插入2000行随机生成的数据作为示例数据(Milvus CLI示例使用包含类似数据的预构建远程CSV文件)。实际应用程序可能使用比示例高得多的维向量。您可以准备自己的数据来替换示例。

5.1 插入实体

5.1.1 准备数据

首先,准备要插入的数据。要插入的数据的数据类型必须与集合的模式匹配,否则Milvus将引发异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
Random ran = new Random();
List<Long> book_id_array = new ArrayList<>();
List<Long> word_count_array = new ArrayList<>();
List<List<Float>> book_intro_array = new ArrayList<>();
for (long i = 0L; i < 2000; ++i) {
book_id_array.add(i);
word_count_array.add(i + 10000);
List<Float> vector = new ArrayList<>();
for (int k = 0; k < 2; ++k) {
vector.add(ran.nextFloat());
}
book_intro_array.add(vector);
}

5.1.2 插入数据

将数据插入集合时,通过指定partition_name,您可以选择决定将数据插入到哪个分区。

1
2
3
4
5
6
7
8
9
10
11
List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("book_id", DataType.Int64, book_id_array));
fields.add(new InsertParam.Field("word_count", DataType.Int64, word_count_array));
fields.add(new InsertParam.Field("book_intro", DataType.FloatVector, book_intro_array));

InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName("book")
.withPartitionName("novel")
.withFields(fields)
.build();
milvusClient.insert(insertParam);
Parameter Description
fieldName Name of the field to insert data into.
DataType Data type of the field to insert data into.
data Data to insert into each field.
CollectionName Name of the collection to insert data into.
PartitionName (optional) Name of the partition to insert data into.

注意:插入的向量的最大数量为 32,768

5.2 通过文件插入实体

Milvus2.2现在支持从文件中插入一批实体。与insert()方法相比,此功能减少了Milvus客户端、代理、Pulsar和数据节点之间的网络传输。现在,只需一行代码就可以将文件中的一批实体导入到集合中。

本主题描述如何从JSON文件在批处理中插入多个实体。

5.2.1 准备json文件

组织要插入到基于行的JSON文件中的数据。您可以根据需要命名文件,但文件中的根键必须是“行”。

在文件中,每个实体对应一个字典。字典的键是主字段,字典的值包含其余字段。文件中的实体必须与集合架构匹配。

对于二进制向量,请使用uint8数组。每个uint8值表示8个维度,并且该值必须介于[0,255]之间。例如,[1,0,0,2,0,1,1]是一个16维二进制向量,应该在JSON文件中写成[128,7]。

文件大小不应大于1 GB。

实例文件内容如下:

1
2
3
4
5
6
7
8
9
{
"rows":[
{"book_id": 101, "word_count": 13, "book_intro": [1.1, 1.2]},
{"book_id": 102, "word_count": 25, "book_intro": [2.1, 2.2]},
{"book_id": 103, "word_count": 7, "book_intro": [3.1, 3.2]},
{"book_id": 104, "word_count": 12, "book_intro": [4.1, 4.2]},
{"book_id": 105, "word_count": 34, "book_intro": [5.1, 5.2]}
]
}

5.2.2 插入实体

1
2
3
4
5
6
from pymilvus import utility
task_id = utility.do_bulk_insert(
collection_name="book",
partition_name="2022",
files=["test.json"]
)

5.3 删除实体

Milvus支持通过使用布尔表达式过滤的主键删除实体。

注意如下:

  • 如果一致性级别设置为低于“强”,则删除后仍可立即检索已删除的实体。
  • 无法再次检索在预先指定的时间跨度之外删除的实体。
  • 频繁的删除操作会影响系统性能。

5.3.1 准备布尔表达式

准备筛选要删除的实体的布尔表达式。

Milvus只支持删除具有明确指定主键的实体,这只需使用中的术语表达式即可实现。其他运算符只能用于向量搜索中的查询或标量过滤。有关详细信息,请参见布尔表达式规则。

以下示例过滤主键值为0和1的数据。

1
2
3
4
5
6
7
8
private static final String DELETE_EXPR = "book_id in [0,1]";

milvusClient.delete(
DeleteParam.newBuilder()
.withCollectionName("book")
.withExpr(DELETE_EXPR)
.build()
);

使用创建的布尔表达式删除实体。Milvus返回已删除实体的ID列表。

Parameter Description
CollectionName Name of the collection to delete entities from.
expr Boolean expression that specifies the entities to delete.
PartitionName (optional) Name of the partition to delete entities from.

5.4 压缩数据

默认情况下,Milvus支持自动数据压缩。您可以配置Milvus以启用或禁用压缩和自动压缩。

如果禁用了自动压缩,您仍然可以手动压缩数据。

为了确保时间旅行搜索的准确性,Milvus在common.retensionDuration中指定的范围内保留数据操作日志。因此,在此期间内操作的数据不会被压缩。

5.4.1 手动压缩数据

1
2
3
4
5
6
R<ManualCompactionResponse> response = milvusClient.manualCompaction(
ManualCompactionParam.newBuilder()
.withCollectionName("book")
.build()
);
long compactionID = response.getData().getCompactionID();

5.4.2 检查压缩状态

1
2
3
4
milvusClient.getCompactionState(GetCompactionStateParam.newBuilder()
.withCompactionID(compactionID)
.build()
);

六 索引管理

矢量索引是用于加速矢量相似性搜索的元数据的组织单位。如果没有建立在向量上的索引,Milvus将默认执行暴力搜索。
注意如下:

  • Milvus 2.1 supports index on scalar fields.
  • By default, Milvus does not index a segment with less than 1,024 rows. To change this parameter, configure rootCoord.minSegmentSizeToEnableIndex in milvus.yaml.

6.1 准备索引参数

准备索引参数(如果希望为标量字段构建索引,则不需要索引构建参数,默认索引类型是字典树)。

1
2
final IndexType INDEX_TYPE = IndexType.IVF_FLAT;   // IndexType
final String INDEX_PARAM = "{\"nlist\":1024}"; // ExtraParam
Parameter Description Options
IndexType Type of index used to accelerate the vector search. For floating point vectors:FLAT (FLAT)IVF_FLAT (IVF_FLAT)IVF_SQ8 (IVF_SQ8)IVF_PQ (IVF_PQ)HNSW (HNSW)ANNOY (ANNOY)DISKANN* (DISK_ANN)For binary vectors:BIN_FLAT (BIN_FLAT)BIN_IVF_FLAT (BIN_IVF_FLAT)
ExtraParam Building parameter(s) specific to the index. See In-memory Index and On-disk Index for more information.

6.2 创建索引

1
2
3
4
5
6
7
8
9
10
milvusClient.createIndex(
CreateIndexParam.newBuilder()
.withCollectionName("book")
.withFieldName("book_intro")
.withIndexType(INDEX_TYPE)
.withMetricType(MetricType.L2)
.withExtraParam(INDEX_PARAM)
.withSyncMode(Boolean.FALSE)
.build()
);

6.3 删除索引

删除索引会不可逆地删除所有相应的索引文件。

1
2
3
4
5
6
milvusClient.dropIndex(
DropIndexParam.newBuilder()
.withCollectionName("book")
.withFieldName("book_intro")
.build()
);
Parameter Description
CollectionName Name of the collection to drop index on.
FieldName Name of the vector field to drop index on.

七 搜索和查询

7.1 搜索

进行向量相似性搜索

Milvus中的向量相似性搜索使用指定的相似性度量计算查询向量与集合中向量之间的距离,并返回最相似的结果。通过指定筛选标量字段或主键字段的布尔表达式,可以执行混合搜索,甚至可以使用“时间旅行”进行搜索。

下面的示例显示了如何对2000行的图书ID(主键)、字数(标量字段)和图书简介(矢量字段)数据集执行矢量相似性搜索,模拟了基于矢量化介绍搜索特定图书的情况。Milvus将根据您定义的查询向量和搜索参数返回最相似的结果。

7.1.1 加载集合

Milvus中的所有搜索和查询操作都在内存中执行。在执行向量相似性搜索之前,将集合加载到内存中。

1
2
3
4
5
milvusClient.loadCollection(
LoadCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

7.1.2 准备搜索参数

准备适合搜索场景的参数。以下示例定义了搜索将使用欧几里德距离计算距离,并从由IVF_FLAT索引构建的十个最近的簇中检索向量。

1
2
final Integer SEARCH_K = 2;                       // TopK
final String SEARCH_PARAM = "{\"nprobe\":10, \”offset\”:5}"; // Params
Parameter Description Options
TopK Number of the most similar results to return. N/A
Params Search parameter(s) specific to the index. See Vector Index for more information.

Search parameters

Parameter Description Range
nprobe Number of units to query CPU: [1, nlist] GPU: [1, min(2048, nlist)]

通过调整nprobe,可以在给定场景下找到准确度和速度之间的理想平衡。IVF_FLAT性能测试的结果表明,随着目标输入向量的数量(nq)和要搜索的簇的数量(nprobe)的增加,查询时间急剧增加。

7.1.3 进行矢量搜索

用Milvus搜索矢量。要搜索特定分区,请指定分区名称列表。

Milvus支持专门为搜索设置一致性级别。本主题中的示例将一致性级别设置为“强”。还可以将一致性级别设置为Bounded, Session或者 Eventually。有关Milvus四个一致性级别的更多信息,请参阅一致性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<String> search_output_fields = Arrays.asList("book_id");
List<List<Float>> search_vectors = Arrays.asList(Arrays.asList(0.1f, 0.2f));

SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName("book")
.withConsistencyLevel(ConsistencyLevelEnum.STRONG)
.withMetricType(MetricType.L2)
.withOutFields(search_output_fields)
.withTopK(SEARCH_K)
.withVectors(search_vectors)
.withVectorFieldName("book_intro")
.withParams(SEARCH_PARAM)
.build();
R<SearchResults> respSearch = milvusClient.search(searchParam);
Parameter Description Options
CollectionName Name of the collection to load. N/A
MetricType Metric type used for search. This parameter must be set identical to the metric type used for index building.
OutFields Name of the field to return. Vector field is not supported in current release.
Vectors Vectors to search with. N/A
VectorFieldName Name of the field to search on. N/A
Expr Boolean expression used to filter attribute. See Boolean Expression Rules for more information.
ConsistencyLevel The consistency level used in the query. STRONG, BOUNDED, andEVENTUALLY.

检查最相似向量的主键值及其距离。

1
2
3
SearchResultsWrapper wrapperSearch = new SearchResultsWrapper(respSearch.getData().getResults());
System.out.println(wrapperSearch.getIDScore(0));
System.out.println(wrapperSearch.getFieldData("book_id", 0));

当搜索完成时,释放Milvus中加载的集合以减少内存消耗。

1
2
3
4
milvusClient.releaseCollection(
ReleaseCollectionParam.newBuilder()
.withCollectionName("book")
.build());

限制如下:

Feature Maximum limit
Length of a collection name 255 characters
Number of partitions in a collection 4,096
Number of fields in a collection 256
Number of shards in a collection 256
Dimensions of a vector 32,768
Top K 16,384
Target input vectors 16,384

7.1.4 搜索API

7.1.4.1 SearchParam

Use the to construct a object.SearchParam.Builder``SearchParam

1
2
import io.milvus.param.SearchParam;
SearchParam.Builder builder = SearchParam.newBuilder();

Methods of :SearchParam.Builder

Method Description Parameters
withCollectionName(collectionName) 设置集合名称。集合名称不能为空或null。 collectionName: The name of the collection to query.
withConsistencyLevel(ConsistencyLevelEnum consistencyLevel) 设置查询中使用的一致性级别。如果未指定一致性级别,则默认级别为.ConsistencyLevelEnum.BOUNDED consistencyLevel: The consistency level used in the query.
withPartitionNames(List<String> partitionNames) 设置分区名称列表以指定查询范围(可选)。 partitionNames: The name list of the partitions to query.
addPartitionName(String partitionName) 添加分区以指定查询范围(可选)。 partitionName: The name of the partition to query.
withTravelTimestamp(Long ts) 指定查询中的绝对时间戳,以在指定时间点基于数据视图获取结果(可选)。默认值为,服务器使用该值在完整数据视图上执行查询。
withOutFields(List<String> outFields) 指定输出标量字段(可选)。如果指定了输出字段,则返回的将包含这些字段的值查询结果 outFields: The name list of output fields.
addOutField(String fieldName) 指定输出标量字段(可选)。 fieldName: The name of an output field .
withExpr(String expr) 设置表达式以在搜索之前过滤标量字段(可选) expr: The expression used to filter scalar fields.
withMetricType(MetricType metricType) Sets the metric type of ANN search. The default value is .MetricType.L2 metricType: The metric type.
withVectorFieldName(String vectorFieldName) 按名称设置目标向量场。字段名不能为空或null。 vectorFieldName: A vector field name.
withTopK(Integer topK) 设置ANN搜索的topK值。可用范围为[116384]。 topK: The topK value.
withVectors(List<?> vectors) 设置目标向量。最多允许16384个矢量。 vectors:If target field type is float vector, is required.List< List<Float>>If target field type is binary vector, is required.List<ByteBuffer>
withRoundDecimal(Integer decimal) 指定返回距离的小数位数。可用范围为[-1,6]。默认值为,方法返回所有数字-1 decimal:小数点后保留的位数。
withParams(String params) 以JSON格式指定搜索参数。有效键如下:与索引相关的特殊参数,例如nprobe``ef``search_k度量类型,作为键,或作为值metric_type ``L2``IP分页的偏移量,关键字为,值为整数offset以关键字和整数作为值的分页限制`极限
build() Constructs a object.SearchParam N/A

The can throw the following exceptions:SearchParam.Builder.build()

  • ParamException: error if the parameter is invalid.

7.1.4.2 Returns

此方法捕获所有异常并返回一个对象R<SearchResults>

  • 如果API在服务器端失败,它将从服务器返回错误代码和消息。
  • 如果API因RPC异常而失败,则返回异常的错误消息R.Status.Unknow
  • 如果API成功,则返回R模板保存的有效值。您可以使用获取结果搜索结果SearchResultsSearchResultsWrapperr

7.1.4.3 SearchResultsWrapper

A tool class to encapsulate the .SearchResults

1
2
import io.milvus.response.SearchResultsWrapper;
SearchResultsWrapper wrapper = new SearchResultsWrapper(searchResults);

Methods of :SearchResultsWrapper

Method Description Parameters Returns
getFieldData(String fieldName, int indexOfTarget) 获取指定的输出字段的数据。如果字段不存在或非法,则引发SearchParam``ParamException``indexOfTarget fieldName: A field name which is specified by the of (the order number of a target vector).withOutFields()``SearchParam.indexOfTarget
getIDScore(int indexOfTarget) 获取返回的ID分数对。如果非法,则抛出。如果返回的结果非法,则抛出.search()``ParamException``indexOfTarget``IllegalResponseException indexOfTarget: Order number of a target vector. List<IDScore>

getFieldData的返回值含义如下:

  • Returns for float vector field.List<List<Float>>
  • Returns for binary vector field.List<ByteBuffer>
  • Returns for int64 field.List<Long>
  • Returns for int32/int16/int8 field.List<Integer>
  • Returns for boolean field.List<Boolean>
  • Returns for float field.List<Float>
  • Returns for double field.List<Double>
  • Returns for VARCHAR field.List<String>

7.1.4.4 IDScore

A tool class to hold a pair of ID and distance.

Methods of :SearchResultsWrapper.IDScore

Method Description Returns
getLongID() Gets the integer ID if the primary key type is Int64. long
getStrID() Gets the string ID if the primary key type is VarChar. String
getScore() Gets the distance value. float

Whether is an accurate distance or not depands on the index type:getScore()

  • FLAT

    The score is is accurate distance.

  • IVF_FLAT

    The score is accurate distance.

  • IVF_SQ8/IVF_PQ

    The score is not accurate.

  • HNSW/ANNOY

    The score is accurate.

7.1.4.5 实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import io.milvus.param.*;
import io.milvus.response.SearchResultsWrapper;
import io.milvus.grpc.SearchResults;

SearchParam param = SearchParam.newBuilder()
.withCollectionName("collection1")
.withMetricType(MetricType.IP)
.withTopK(10)
.withVectors(targetVectors)
.withVectorFieldName("field1")
.withConsistencyLevel(ConsistencyLevelEnum.EVENTUALLY)
.withParams("{\"nprobe\":10,\"offset\":2, \"limit\":3}")
.build();
R<SearchResults> response = client.search(param)
if (response.getStatus() != R.Status.Success.getCode()) {
System.out.println(response.getMessage());
}

SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData());
System.out.println("Search results:");
for (int i = 0; i < targetVectors.size(); ++i) {
List<SearchResultsWrapper.IDScore> scores = results.getIDScore(i);
for (int j = 0; j < scores.size(); ++j) {
SearchResultsWrapper.IDScore score = scores.get(j);
System.out.println("Top " + j + " ID:" + score.getLongID() + " Distance:" + score.getScore());
}
}

7.1.5 数据插入API

MilvusClient接口。此方法将实体插入到指定的集合中。

1
R<MutationResult> insert(InsertParam requestParam);

7.1.5.1 InsertParam

1
2
import io.milvus.param.InsertParam;
InsertParam.Builder builder = InsertParam.newBuilder();
Method Description Parameters
withCollectionName(String collectionName) 设置目标集合名称。集合名称不能为空或null。 collectionName: 要向其中插入数据的集合的名称。
withPartitionName(String partitionName) 设置目标分区名称(可选)。 partitionName: 要插入数据的分区的名称。
withFields(List<InsertParam.Field> fields) 设置要插入的数据。字段列表不能为空。请注意,如果启用了自动ID,则主键字段不需要输入。 fields: “字段”对象的列表,每个对象表示一个字段。
build() Constructs an InsertParam object. N/A

InsertParam.Builder.build()可能会抛出以下异常

  • ParamException:参数无效时出错。

7.1.5.2 Field

Method Description
Field(String name, List<?> values) This class only provides a Constructorto create a Field object.

参数解释如下:

name: The name of the data field.

values:

  • If data type is Bool, values is List of Boolean;
  • If data type is Int64, values is List of Long;
  • If data type is Float, values is List of Float;
  • If data type is Double, values is List of Double;
  • If data type is VARCHAR, values is List of String;
  • If data type is FloatVector, values is List of List Float;
  • If data type is BinaryVector, values is List of ByteBuffer.

7.1.5.3 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import io.milvus.param.*;
import io.milvus.grpc.MutationResult;

List<InsertParam.Field> fields = new ArrayList<>();
int rowCount = 10000;
List<Long> ids = new ArrayList<>();
for (long i = 0L; i < rowCount; ++i) {
ids.add(i);
}
List<List<Float>> vectors = generateFloatVectors(rowCount);

List<InsertParam.Field> fieldsInsert = new ArrayList<>();
fieldsInsert.add(new InsertParam.Field("id", ids));
fieldsInsert.add(new InsertParam.Field("vec", vectors));

InsertParam param = InsertParam.newBuilder()
.withCollectionName(collectionName)
.withFields(fieldsInsert)
.build();
R<MutationResult> response = client.insert(param);
if (response.getStatus() != R.Status.Success.getCode()) {
System.out.println(response.getMessage());
}

7.2 混合搜索

混合搜索本质上是具有属性过滤的矢量搜索。通过指定筛选标量字段或主键字段的布尔表达式,可以使用某些条件限制搜索。

下面的示例演示如何在常规矢量搜索的基础上执行混合搜索。假设您要根据矢量化介绍搜索某些书籍,但您只需要特定字数范围内的书籍。然后,您可以指定布尔表达式以筛选搜索参数中的字段。Milvus 只会在与表达式匹配的实体中搜索相似的向量。word_count

通过指定布尔表达式,可以在矢量搜索期间过滤实体的标量字段。以下示例将搜索范围限制为指定值范围内的矢量。word_count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
milvusClient.loadCollection(
LoadCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

final Integer SEARCH_K = 2;
final String SEARCH_PARAM = "{\"nprobe\":10, \”offset\”:5}";
List<String> search_output_fields = Arrays.asList("book_id");
List<List<Float>> search_vectors = Arrays.asList(Arrays.asList(0.1f, 0.2f));

SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName("book")
.withMetricType(MetricType.L2)
.withOutFields(search_output_fields)
.withTopK(SEARCH_K)
.withVectors(search_vectors)
.withVectorFieldName("book_intro")
.withExpr("word_count <= 11000")
.withParams(SEARCH_PARAM)
.build();
R<SearchResults> respSearch = milvusClient.search(searchParam);
Parameter Description Options
CollectionName Name of the collection to load. N/A
MetricType Metric type used for search. This parameter must be set identical to the metric type used for index building.
OutFields Name of the field to return. Vector field is not supported in current release.
TopK Number of the most similar results to return. N/A
Vectors Vectors to search with. N/A
VectorFieldName Name of the field to search on. N/A
Expr Boolean expression used to filter attribute. See Boolean Expression Rules for more information.
Params Search parameter(s) specific to the index. See Vector Index for more information.

检查返回的结果

1
2
3
SearchResultsWrapper wrapperSearch = new SearchResultsWrapper(respSearch.getData().getResults());
System.out.println(wrapperSearch.getIDScore(0));
System.out.println(wrapperSearch.getFieldData("book_id", 0));

7.3 矢量查询

与向量相似性搜索不同,向量查询通过基于布尔表达式的标量过滤来检索向量。Milvus支持标量字段中的许多数据类型和各种布尔表达式。布尔表达式对标量字段或主键字段进行筛选,并检索与筛选匹配的所有结果。

下面的示例显示了如何对图书ID(主键)、字数(标量字段)和图书简介(矢量字段)的2000行数据集执行矢量查询,模拟了根据图书ID查询特定图书的情况。

以下示例使用某些book_id值过滤向量,并返回结果的book_id字段和book_intro。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
milvusClient.loadCollection(
LoadCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);

List<String> query_output_fields = Arrays.asList("book_id", "word_count");
QueryParam queryParam = QueryParam.newBuilder()
.withCollectionName("book")
.withConsistencyLevel(ConsistencyLevelEnum.STRONG)
.withExpr("book_id in [2,4,6,8]")
.withOutFields(query_output_fields)
.withOffset(0L)
.withLimit(10L)
.build();
R<QueryResults> respQuery = milvusClient.query(queryParam);
Parameter Description Options
CollectionName Name of the collection to load. N/A
OutFields Name of the field to return. Vector field is not supported in current release.
Expr Boolean expression used to filter attribute. See Boolean Expression Rules for more information.
ConsistencyLevel The consistency level used in the query. STRONG, BOUNDED, andEVENTUALLY.

检查执行结果

1
2
3
QueryResultsWrapper wrapperQuery = new QueryResultsWrapper(respQuery.getData());
System.out.println(wrapperQuery.getFieldWrapper("book_id").getFieldData());
System.out.println(wrapperQuery.getFieldWrapper("word_count").getFieldData());

八 完整实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package com.whty.common.graph.milvus;

import io.milvus.Response.SearchResultsWrapper;
import io.milvus.client.MilvusServiceClient;
import io.milvus.grpc.DataType;
import io.milvus.grpc.SearchResults;
import io.milvus.param.ConnectParam;
import io.milvus.param.IndexType;
import io.milvus.param.MetricType;
import io.milvus.param.R;
import io.milvus.param.collection.CreateCollectionParam;
import io.milvus.param.collection.FieldType;
import io.milvus.param.collection.LoadCollectionParam;
import io.milvus.param.collection.ReleaseCollectionParam;
import io.milvus.param.dml.InsertParam;
import io.milvus.param.dml.SearchParam;
import io.milvus.param.index.CreateIndexParam;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
* @author yishui
* @version 1.0.0
* @since 1.0.0
*/
public class DemoTest {

private static MilvusServiceClient connect() {
final MilvusServiceClient milvusClient = new MilvusServiceClient(
ConnectParam.newBuilder()
.withHost("192.168.171.132")
.withPort(19530)
.build()
);
return milvusClient;
}

private static CreateCollectionParam createCollectionReq() {
FieldType fieldType1 = FieldType.newBuilder()
.withName("book_id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(false)
.build();
FieldType fieldType2 = FieldType.newBuilder()
.withName("word_count")
.withDataType(DataType.Int64)
.build();
FieldType fieldType3 = FieldType.newBuilder()
.withName("book_intro")
.withDataType(DataType.FloatVector)
.withDimension(2)
.build();

CreateCollectionParam createCollectionReq = CreateCollectionParam.newBuilder()
.withCollectionName("book")
.withDescription("Test book search")
.withShardsNum(2)
.addFieldType(fieldType1)
.addFieldType(fieldType2)
.addFieldType(fieldType3)
.build();

return createCollectionReq;
}

private static void createCollection(MilvusServiceClient milvusClient, CreateCollectionParam createCollectionReq) {
milvusClient.createCollection(createCollectionReq);
}


private static void insertData(MilvusServiceClient milvusClient) {
Random ran = new Random();
List<Long> book_id_array = new ArrayList<>();
List<Long> word_count_array = new ArrayList<>();
List<List<Float>> book_intro_array = new ArrayList<>();
for (long i = 0L; i < 2000; ++i) {
book_id_array.add(i);
word_count_array.add(i + 10000);
List<Float> vector = new ArrayList<>();
for (int k = 0; k < 2; ++k) {
vector.add(ran.nextFloat());
}
book_intro_array.add(vector);
}

List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("book_id", DataType.Int64, book_id_array));
fields.add(new InsertParam.Field("word_count", DataType.Int64, word_count_array));
fields.add(new InsertParam.Field("book_intro", DataType.FloatVector, book_intro_array));

InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName("book")
.withPartitionName("novel")
.withFields(fields)
.build();
milvusClient.insert(insertParam);
}

static final IndexType INDEX_TYPE = IndexType.IVF_FLAT; // IndexType
static final String INDEX_PARAM = "{\"nlist\":1024}"; // ExtraParam

private static void buildIndex(MilvusServiceClient milvusClient) {
milvusClient.createIndex(
CreateIndexParam.newBuilder()
.withCollectionName("book")
.withFieldName("book_intro")
.withIndexType(INDEX_TYPE)
.withMetricType(MetricType.L2)
.withExtraParam(INDEX_PARAM)
.withSyncMode(Boolean.FALSE)
.build()
);
}


public static void main(String[] args) {

MilvusServiceClient milvusClient = connect();
CreateCollectionParam createCollectionReq = createCollectionReq();
createCollection(milvusClient, createCollectionReq);
insertData(milvusClient);
buildIndex(milvusClient);
milvusClient.loadCollection(
LoadCollectionParam.newBuilder()
.withCollectionName("book")
.build()
);
final Integer SEARCH_K = 2; // TopK
final String SEARCH_PARAM = "{\"nprobe\":10}"; // Params
List<String> search_output_fields = Arrays.asList("book_id");
List<List<Float>> search_vectors = Arrays.asList(Arrays.asList(0.1f, 0.2f));

SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName("book")
.withMetricType(MetricType.L2)
.withOutFields(search_output_fields)
.withTopK(SEARCH_K)
.withVectors(search_vectors)
.withVectorFieldName("book_intro")
.withParams(SEARCH_PARAM)
.build();

R<SearchResults> respSearch = milvusClient.search(searchParam);
SearchResultsWrapper wrapperSearch = new SearchResultsWrapper(respSearch.getData().getResults());
System.out.println("wrapperSearch.getIDScore" + wrapperSearch.getIDScore(0));
System.out.println("wrapperSearch.getFieldData" + wrapperSearch.getFieldData("book_id", 0));
milvusClient.releaseCollection(
ReleaseCollectionParam.newBuilder()
.withCollectionName("book")
.build());
}
}

依赖如下:

1
2
3
4
5
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.2.1</version>
</dependency>