Milvus向量库Java对接使用指南

文章目录

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

介绍

官方文档:https://milvus.io/docs/zh/

知识点介绍

表结构字段设计

模式解释:https://milvus.io/docs/zh/schema.md

一个 Collection Schema 有一个主键、最多四个向量字段和几个标量字段

索引

介绍

官方文档:https://milvus.io/docs/zh/index-explained.md

索引是建立在数据之上的附加结构。其内部结构取决于所使用的近似近邻搜索算法 。索引可以加快搜索速度,但在搜索过程中会产生额外的预处理时间、空间和 RAM。此外,使用索引通常会降低召回率(虽然影响可以忽略不计,但仍然很重要)。因此,本文将解释如何最大限度地减少使用索引的成本,同时最大限度地提高索引的效益。

字段数据类型与索引关系

字段数据类型 适用索引类型
FLOAT_VECTORFLOAT16_VECTORbfloat16_vector****INT8_VECTOR 平面IVF_FLAT IVF_SQ8IVF_PQ IVF_RABITQGPU_IVF_FLAT GPU_IVF_PQHNSWDISKANN
二进制向量 BIN_FLATBIN_IVF_FLATMINHASH_LSH
稀疏浮点矢量 稀疏反转索引
VARCHAR 反转(推荐)BITMAPTrie
BOOL **BITMAP(推荐)**反转
INT8INT16INT32****INT64 反转****STL_SORT
FLOAT****DOUBLE 反转
数组**(BOOL、INT8/16/32/64 和 VARCHAR 类型的元素)** BITMAP(推荐)
ARRAY**(BOOL、INT8/16/32/64、FLOAT、DOUBLE 和 VARCHAR 类型的元素)** 反转
JSON 反转

向量索引解析

数据结构

数据结构是索引的基础层。常见类型包括

  • 反转文件(IVF)

IVF 系列索引类型允许 Milvus 通过基于中心点的分区将向量聚类到桶中。一般可以安全地假设,如果桶的中心点接近查询向量,那么桶中的所有向量都有可能接近查询向量。基于这个前提,Milvus 只扫描那些中心点靠近查询向量的桶中的向量 Embeddings,而不是检查整个数据集。这种策略既能降低计算成本,又能保持可接受的精确度。

这种索引数据结构非常适合需要快速吞吐量的大规模数据集

  • 基于图的结构

基于图的向量搜索数据结构,如分层导航小世界 (HNSW,构建了一个分层图,其中每个向量都连接到其最近的邻居。查询可以浏览这个层次结构,从粗略的上层开始,然后切换到下层,从而实现高效的对数时间搜索复杂性。

这种索引数据结构适用于高维空间和要求低延迟查询的场景。

量化

量化通过更粗略的表示来减少内存占用和计算成本:

  • 标量量化 (如SQ8)使 Milvus 能够将每个向量维度压缩为单字节(8 位),与 32 位浮点数相比,内存使用量减少了 75%,同时保持了合理的精度。
  • 乘积量化**(PQ**)使 Milvus 能够将向量分割成子向量,并使用基于编码本的聚类进行编码。这可以实现更高的压缩率(例如 4-32 倍),但代价是召回率略有降低,因此适用于内存受限的环境。

比如索引表格中的这几个:

精炼器

量化本身就是有损的。为了保持召回率,量化始终会产生比所需数量更多的前 K 个候选结果,这使得精炼器可以使用更高的精度从这些候选结果中进一步选择前 K 个结果,从而提高召回率。

例如,FP32 精炼器通过使用 FP32 精度而不是量化值重新计算距离,对量化返回的候选搜索结果进行操作符操作。

这对于需要在搜索效率和精度之间做出权衡的应用(如语义搜索或推荐系统)来说至关重要,因为在这些应用中,微小的距离变化都会对结果质量产生重大影响。

性能权衡

在评估性能时,平衡构建时间每秒查询次数(QPS)召回率至关重要。一般规则如下:

  • QPS 而言,基于图形的索引类型 通常优于IVF 变体
  • IVF 变体 尤其适用于topK 较大的情况**(例如,超过 2,000 个)**。
  • SQ****相比,PQ通常能在相似的压缩率下提供更好的召回率,但后者的性能更快。
  • 将硬盘用于部分索引(如DiskANN)有助于管理大型数据集,但也会带来潜在的 IOPS 瓶颈。
容量

容量通常涉及数据大小与可用 RAM 之间的关系。在处理容量问题时,请考虑以下几点:

  • 如果有四分之一的原始数据适合存储在内存中,则应考虑使用延迟稳定的 DiskANN
  • 如果所有原始数据都适合在内存中存储,则应考虑基于内存的索引类型和 mmap。
  • 您可以使用量化应用索引类型和 mmap 来换取最大容量的准确性。
召回率

召回率通常涉及过滤率,即搜索前过滤掉的数据。在处理召回问题时,应考虑以下几点:

  • 如果过滤率小于 85%,则基于图的索引类型优于 IVF 变体。
  • 如果过滤比在 85% 到 95% 之间,则使用 IVF 变体。
  • 如果过滤率超过 98%,则使用 "蛮力"(FLAT)来获得最准确的搜索结果。
性能

搜索性能通常涉及 top-K,即搜索返回的记录数。在处理性能问题时,请考虑以下几点:

  • 对于 Top-K 较小的搜索(如 2,000),需要较高的召回率,基于图的索引类型优于 IVF 变体。
  • 对于 top-K 较大的搜索(与向量嵌入的总数相比),IVF 变体比基于图的索引类型是更好的选择。
  • 对于 top-K 中等且过滤率较高的搜索,IVF 变体是更好的选择。
决策矩阵:选择最合适的索引类型

下表是一个决策矩阵,供您在选择合适的索引类型时参考。

方案 推荐索引 注释
原始数据适合内存 HNSW、IVF + 精炼 使用 HNSW 实现低 **k**/ 高召回率。
磁盘、固态硬盘上的原始数据 磁盘ANN 最适合对延迟敏感的查询。
磁盘上的原始数据,有限的 RAM IVFPQ/SQ + mmap 平衡内存和磁盘访问。
高过滤率(>95) 强制(FLAT) 避免微小候选集的索引开销。
大型 **k** (≥ 数据集的 1) IVF 簇剪枝减少了计算量。
极高的召回率(>99) 蛮力 (FLAT) + GPU --

内存使用估算

IVF 索引内存使用量

举例:使用 IVF 变体索引的 100 万个 128 维向量所使用的内存明细。

下表列出了不同配置下的内存用量估算:

配置 内存估算 总内存
IVF-PQ(无细化) 1.0 MB + 2.0 MB + 8.0 MB 11.0 MB
IVF-PQ + 10% 原始细化 1.0 MB + 2.0 MB + 8.0 MB + 51.2 MB 62.2 MB
IVF-SQ8 (无细化) 1.0 MB + 2.0 MB + 128 MB 131.0 MB
IVF-FLAT(全原始向量) 1.0 MB + 2.0 MB + 512 MB 515.0 MB
基于图形的索引内存使用情况

基于图的索引类型(如 HNSW)需要大量内存来存储图结构和原始向量嵌入。下面是使用 HNSW 索引类型索引的 100 万个 128 维向量所消耗内存的详细明细

  • 当您使用 HNSW 对 100 万 128 维向量嵌入进行索引时,使用的总内存为128 MB(图形)+ 512 MB(向量)= 640 MB
  • 与原始向量嵌入相比,这实现了 64 倍的压缩率,HNSWPQ 索引类型使用的总内存将为128 MB(图)+ 8 MB(压缩向量)= 136 MB

搜索方面

基本ANN(向量)搜索【search方法】

基础参数介绍

官方文档:https://milvus.io/docs/zh/single-vector-search.md

java 复制代码
// 下面参数来源:SearchReq
int topK:搜索请求中携带的参数limit 决定了搜索结果中包含的实体数量。该参数指定了单次搜索中返回实体的最大数量。【支持topK或者limit形式】
    限制+偏移:topK字段+offset组合搜索
        .topK(3)
        .offset(10)

// 度量类型
IndexParam.MetricType metricType
    INVALID,
    L2,
    IP,
    COSINE,
    HAMMING,
    JACCARD,
    MHJACCARD,
    BM25;

// 向量集合,支持批次向量查询
List<BaseVector> data

// 指定输出字段,返回记录的字段信息值
List<String> outputFields;

相似度:Milvus 根据搜索结果与查询向量的相似度得分从高到低排列搜索结果。相似度得分也称为与查询向量的距离,其值范围随使用的度量类型而变化。

度量类型:

度量类型 特征 距离范围
**L2** 值越小表示相似度越高。 [0, ∞)
**IP** 数值越大,表示相似度越高。 [-1, 1]
**COSINE** 数值越大,表示相似度越高。 [-1, 1]
**JACCARD** 值越小,表示相似度越高。 [0, 1]
**HAMMING** 值越小,表示相似度越高。 [0,dim(向量)] 批量向量搜索
单向量搜索

单向量搜索:在 ANN 搜索中,单向量搜索指的是只涉及一个查询向量的搜索。根据预建索引和搜索请求中携带的度量类型,Milvus 将找到与查询向量最相似的前 K 个向量

搜索请求携带单个查询向量,要求 Milvus 使用内积(IP)计算查询向量与 Collections 中向量的相似度,并返回三个最相似的向量。

java 复制代码
FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
        .collectionName("quick_setup")
        .data(Collections.singletonList(queryVector))
        .topK(3)
        .build();
SearchResp searchResp = client.search(searchReq);
// Output
// TopK results:
// SearchResp.SearchResult(entity={}, score=0.95944905, id=5)
// SearchResp.SearchResult(entity={}, score=0.8689616, id=1)
// SearchResp.SearchResult(entity={}, score=0.866088, id=7)
批向量搜索

**批向量搜索:**可以多向量批次去搜索

java 复制代码
List<BaseVector> queryVectors = Arrays.asList(
        new FloatVec(new float[]{0.041732933f, 0.013779674f, -0.027564144f, -0.013061441f, 0.009748648f}),
        new FloatVec(new float[]{0.0039737443f, 0.003020432f, -0.0006188639f, 0.03913546f, -0.00089768134f})
);
SearchReq searchReq = SearchReq.builder()
        .collectionName("quick_setup")
        .data(queryVectors)
        .topK(3)
        .build();

SearchResp searchResp = client.search(searchReq);
// 返回的是两组数据
分区ANN搜索

分区ANN搜索 :Collections 中创建了多个分区,您可以将搜索范围缩小到特定数量的分区。在这种情况下,您可以在搜索请求中包含目标分区名称,将搜索范围限制在指定的分区内。减少搜索所涉及的分区数量可以提高搜索性能。

java 复制代码
SearchReq searchReq = SearchReq.builder()
        .collectionName("quick_setup")
        // 分区字段名
        .partitionNames(Collections.singletonList("partitionA"))
        .data(Collections.singletonList(queryVector))
        .topK(3)
        .build();

增强 ANN(向量)搜索【包括过滤等】

导航梳理

AUTOINDEX 极大地平滑了 ANN 搜索的学习曲线。但是,随着 top-K 的增加,搜索结果可能并不总是正确的。通过缩小搜索范围、提高搜索结果的相关性和搜索结果的多样化,Milvus 实现了以下搜索增强功能。

1)过滤搜索 :可以在搜索请求中包含过滤条件,这样 Milvus 就会在进行 ANN 搜索前进行元数据过滤,将搜索范围从整个 Collections 缩小到只搜索符合指定过滤条件的实体。

2)**范围搜索:特定范围内限制返回实体的距离或得分,从而提高搜索结果的相关性。在 Milvus 中,范围搜索包括以与查询向量最相似的嵌入向量为中心**,画两个同心圆。搜索请求指定了两个圆的半径,Milvus 会返回所有属于外圆但不属于内圆的向量嵌入。

3)分组搜索:如果返回的实体在特定字段中持有相同的值,搜索结果可能无法代表向量空间中所有向量嵌入的分布情况。要使搜索结果多样化,可以考虑使用分组搜索。

4)混合搜索:一个 Collections 最多可以包含四个向量场,以保存使用不同嵌入模型生成的向量嵌入。通过这种方式,可以使用混合搜索对这些向量场的搜索结果进行 Rerankers,从而提高召回率。

过滤搜索(条件过滤)

过滤搜索根据应用过滤的阶段分为两种类型--标准过滤迭代过滤

**标准过滤:**Collections 同时包含向量嵌入及其元数据,您可以在 ANN 搜索之前过滤元数据,以提高搜索结果的相关性。Milvus 收到携带过滤条件的搜索请求后,会将搜索范围限制在符合指定过滤条件的实体内。

示范如下 :搜索请求携带**chunk like "%red%"** 作为过滤条件,表明 Milvus 应在**chunk** 字段中包含**red** 的所有实体内进行 ANN 搜索。具体来说,Milvus 会执行以下操作:

  1. 过滤符合搜索请求中过滤条件的实体。
  2. 在过滤后的实体中进行 ANN 搜索。
  3. 返回前 K 个实体。

迭代过滤:标准过滤过程能有效地将搜索范围缩小到很小的范围。但是,过于复杂的过滤表达式可能会导致非常高的搜索延迟。在这种情况下,迭代过滤可以作为一种替代方法,帮助减少标量过滤的工作量。

大数据量会有性能问题

过滤条件写法:

java 复制代码
// 搜索请求中的过滤条件为color like "red%" and likes > 50 。它使用 and 操作符包含两个条件:第一个条件要求在color 字段中查找值以red 开头的实体,其他条件要求在likes 字段中查找值大于50 的实体。符合这些要求的实体只有两个。当 top-K 设置为3 时,Milvus 将计算这两个实体与查询向量的距离,并将它们作为搜索结果返回。
SearchReq searchReq = SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(queryVector))
        .topK(5)
        // 过滤表达式
        .filter("color like \"red%\" and likes > 50")
        .outputFields(Arrays.asList("color", "likes"))
        .build();


// 使用迭代过滤器,就是补充了个searchParams字段
SearchReq searchReq = SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(queryVector))
        .topK(5)
        .filter("color like \"red%\" and likes > 50")
        .outputFields(Arrays.asList("color", "likes"))
        .searchParams(new HashMap<>("hints", "iterative_filter"))
        .build();

过滤条件文档:https://milvus.io/docs/zh/boolean.md

标量字段:

sql 复制代码
// 查找具有三原色(红色、绿色或蓝色)的实体
filter='color in ["red", "green", "blue"]'

JSON 字段中引用键:

java 复制代码
// 带有键price 和model 的 JSON 字段product ,并想查找具有特定模型且价格低于 1,850 的产品
filter='product["model"] == "JSN-087" AND product["price"] < 1850'

过滤数组字段:

java 复制代码
// 2000 年以来各观测站报告的平均气温记录,要查找 2009 年(第 10 次记录)气温超过 23°C 的观测站
filter='history_temperatures[10] > 23'

过滤表达式模板文档:https://milvus.io/docs/zh/filtering-templating.md

过滤表达式优化:

json 复制代码
要查找居住在 "北京"(北京)或 "上海"(上海)的 25 岁以上的个人,请使用以下模板表达式:
  filter = "age > 25 AND city IN ['北京', '上海']"

为提高性能,可使用这种带参数的变体:
  filter = "age > {age} AND city in {city}",
  filter_params = {"age": 25, "city": ["北京", "上海"]}
这种方法可减少解析开销,提高查询速度。

支持一些函数:

shell 复制代码
# JSON data: {"tags": ["electronics", "sale", "new"]}
filter='json_contains(tags, "sale")'

# JSON data: {"tags": ["electronics", "sale", "new", "discount"]}
filter='json_contains_all(tags, ["electronics", "sale", "new"])'

# JSON data: {"tags": ["electronics", "sale", "new"]}
filter='json_contains_any(tags, ["electronics", "new", "clearance"])'

混合搜索

官方文档:https://milvus.io/docs/zh/multi-vector-search.md

支持一个collection中创建多个向量,默认4个,可以修改配置参数,最多10个一个collection中

示范场景:

为了演示各种搜索向量字段的功能,我们将使用一个示例查询构建三个**AnnSearchRequest** 搜索请求。在此过程中,我们还将使用其预先计算的密集向量。搜索请求将针对以下向量场:

  • **text_dense** 语义文本搜索,允许基于意义而非直接关键词匹配进行上下文理解和检索。
  • **text_sparse**全文搜索或关键词匹配,侧重于文本中精确匹配的单词或短语。
  • **image_dense**多模态文本到图片搜索,根据查询的语义内容检索相关产品图片。

示范举例:

java 复制代码
float[] queryDense = new float[]{-0.0475336798f,  0.0521207601f,  0.0904406682f, ...};
float[] queryMultimodal = new float[]{0.0158298651f, 0.5264158340f, ...}

List<BaseVector> queryTexts = Collections.singletonList(new EmbeddedText("white headphones, quiet and comfortable");)
List<BaseVector> queryDenseVectors = Collections.singletonList(new FloatVec(queryDense));
List<BaseVector> queryMultimodalVectors = Collections.singletonList(new FloatVec(queryMultimodal));

List<AnnSearchReq> searchRequests = new ArrayList<>();
searchRequests.add(AnnSearchReq.builder()
        .vectorFieldName("text_dense")
        .vectors(queryDenseVectors)
        .params("{\"nprobe\": 10}")
        .topK(2)
        .build());
searchRequests.add(AnnSearchReq.builder()
        .vectorFieldName("text_sparse")
        .vectors(queryTexts)
        .params("{\"drop_ratio_search\": 0.2}")
        .topK(2)
        .build());
searchRequests.add(AnnSearchReq.builder()
        .vectorFieldName("image_dense")
        .vectors(queryMultimodalVectors)
        .params("{\"nprobe\": 10}")
        .topK(2)
        .build());

参数**limit** 设置为 2 时,每个**AnnSearchRequest** 会返回 2 个搜索结果。在本示例中,创建了 3 个**AnnSearchRequest** 实例,总共产生了 6 个搜索结果。

默认milvus还支持重排:

  • 加权排名:如果结果需要强调某个向量场,请使用该策略。WeightedRanker 可以为某些向量场赋予更大的权重,使其更加突出。
  • RRFRanker(互易排名融合排名器):在不需要特别强调的情况下选择此策略。RRFRanker 能有效平衡每个向量场的重要性。

针对重排文档可查看https://milvus.io/docs/zh/weighted-ranker.md

java 复制代码
// RRFRanker(互易排名融合排名器)
BaseRanker reranker = new RRFRanker(100);

混合查询的效果如下:

java 复制代码
// 在为混合搜索指定limit=2 参数后,Milvus 将对三次搜索得到的六个结果进行 Rerankers 排序。最终,它们将只返回最相似的前两个结果。
HybridSearchReq hybridSearchReq = HybridSearchReq.builder()
        .collectionName("my_collection")
        .searchRequests(searchRequests)
        .ranker(reranker)
        .topK(2)
        .build();

SearchResp searchResp = client.hybridSearch(hybridSearchReq);

版本差异记录

2.6.0

体现在sdk包中,如果服务端版本比较低,sdk用的高,服务端会报错类似如下;

在2.6.0及之后MilvusClientV2开始支持addCollectionField:新增集合字段。

相关疑问

MilvusClientV2与MilvusServiceClient 有什么区别?

特性 新版客户端 (推荐) 旧版客户端 (过时)
Maven 依赖 io.milvus:milvus-sdk-java io.milvus:milvus-sdk-java
包路径 io.milvus.v2.client.** io.milvus.client.**
核心类 **MilvusClientV2** MilvusClient
引入版本 SDK 2.3.0+ (对应 Milvus 2.3.x+) SDK 2.2.x 及以前
设计理念 更简洁、更现代、Fluent API 更接近 gRPC 底层 API,略显冗长
连接方式 使用 ConnectConfig 构建配置 使用 ConnectParam 构建参数
推荐程度 强烈推荐,是未来的方向 ⚠️ 已过时,新项目不应使用

推荐使用MilvusClientV2

初始建立表可以建立多个向量字段?

是的,官方介绍,可在建立表的时候最多加四个向量字段。

模式解释:https://milvus.io/docs/zh/schema.md

一个 Collection Schema 有一个主键、最多四个向量字段和几个标量字段

在官方文档中介绍到可以修改配置项,来提高更多的向量?

创建完表后是否可以新增字段?

以milvus 2.6.3版本为例子

**是否支持新增向量字段?**不可以,向量字段一开始建立完表之后无法新增

**新增其他标量字段?**可以,但是必须设置可为空

其他标量字段需要设置可以为空,否则会报错:

java 复制代码
// 新增字段
AddCollectionFieldReq addCollectionFieldReq = AddCollectionFieldReq.builder()
        .databaseName(database)
        .collectionName(collectionName)
        .fieldName("test_param")
        .dataType(DataType.VarChar)
        // 新增的字段必须加这个为空的
        .isNullable(true)
        .maxLength(65535).build();

MilvusClient.getInstance().addCollectionField(addCollectionFieldReq);

向量查询时是否需要和索引强一致的度量类型?

关于建立表的注意点

建立表的强制点:

1、建立的表必须要强制手动loadCollection ,后续才能查询。【内存计算架构要求数据预先加载到内存,Miluvs是内存计算架构

  • 在 Milvus 2.3+ 的 release notes 中强调:"All search operations require explicit collection loading for better resource management."
java 复制代码
// 注意最后要加载集合到内存
LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
        .databaseName(database)
        .collectionName(collectionName)
        .build();
instance.loadCollection(loadCollectionReq);

2、加载到内存loadCollection前必须要给表建立索引与度量类型

如果不设置索引就会报错,在loadCollection的时候**。**

如果不建立度量就会报错,在loadCollection的时候**。**

**下面是创建索引+度量:**初始的时候就指定好了

java 复制代码
// 一般要设置缓存给向量字段
CreateIndexReq createIndexReq = CreateIndexReq.builder()
        .databaseName(database)
        .collectionName(collectionName)
        .indexParams(Arrays.asList(
                IndexParam.builder()
                        .fieldName(DEFAULT_VECTOR_FIELD_NAME)
                        // 索引类型
                        .indexType(IndexParam.IndexType.IVF_FLAT)
                        // 度量类型:后续同样需要使用这个度量去查询
                        .metricType(IndexParam.MetricType.COSINE)
                        .build()
        )).build();
instance.createIndex(createIndexReq);

索引方面

索引方面

1)一旦向量索引创建后,那么就无法删除,亲测如下:

在一开始建立向量索引的时候,必须要选择索引+度量

关于搜索和索引向量的关系

搜索的时候选择的度量必须和对应表的度量一致,否则会查询报错!

报错如下如果度量选择不一致的话:

官方search查询的结果

entity是这样子的一组查询的结果:

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

相关推荐
坐吃山猪1 天前
ChromaDB02-代码实战
数据库·向量数据库·chromadb
jiayong232 天前
model.onnx 深度分析报告(第2篇)
人工智能·机器学习·向量数据库·向量模型
福大大架构师每日一题3 天前
milvus v2.6.8 发布:搜索高亮上线,性能与稳定性全面跃升,生产环境强烈推荐升级
android·java·milvus
坐吃山猪3 天前
ChromaDB01-运行向量数据库
数据库·向量数据库·chromadb
Clarence Liu5 天前
Milvus学习(1) 架构和部署
学习·架构·milvus
托尼吴6 天前
milvus 向量数据库学习笔记-基础认识
数据库·学习·milvus
liuc03176 天前
调用embedding生成向量并存储到milvus中,进行查询
embedding·milvus
西柚小萌新8 天前
【大模型:RAG】--向量数据库Milvus详解2
数据库·milvus
程序员柒叔9 天前
Dify 集成-向量数据库
数据库·milvus·向量数据库·工作流·dify·向量库