找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

428

积分

0

好友

53

主题
发表于 前天 16:43 | 查看: 6| 回复: 0

一、背景

信息通信技术(ICT)正经历着前所未有的变革浪潮,以大模型和生成式人工智能(GenAI)为代表的技术突破,正在成为驱动企业技术架构革新和商业模式转型的关键引擎。

得物是广受年轻人喜爱的品质生活购物社区。在AI鉴别、图搜、算法、安全风控等场景下都广泛使用了GenAI技术。

向量数据库作为GenAI的基础设施之一,通过量化的高维空间数据结构(如HNSW算法),实现对嵌入向量(Embeddings Vector)的高效存储、索引和最近邻搜索(ANN),支撑包括多模态数据表征在内的复杂智能应用。

二、认识向量数据库

向量数据来源和存储

向量数据处理流程图

一般向量数据库中向量的来源是将图片、音频、视频、文本等非结构化数据,将这些非结构化数据通过对应的量化算法计算出一个多维度的向量(生产使用一般向量维度会大于512),并且将向量数据持久化在特定的存储上。

向量数据库是如何工作

向量数据库查询工作原理图

向量数据库在查询的时候一般会将需要查询的非结构化数据通过量化,计算成一个多维度向量数据,然后在数据库中搜索出和查询向量相似的数据。(需要注意的是这边查询的是相似的数据而不是相同的数据)。

三、向量数据库对比传统数据库

关系型数据库与向量数据库对比图

传统数据库与向量数据库特点对比表格

向量数据库在数据结构、检索方法、擅长领域与传统数据库有很大的不同。

传统数据库

结构是处理离散的标量数据类型(例如数字和字符串),并通过行和列来表达组织数据(就是一个表格)。传统数据库主要为了解决结构化数据的精确管理和高效查询问题。并且传统数据库通过B树索引、哈希索引等数据结构,能够快速定位到精确匹配的记录。更重要的是,传统数据库通过ACID事务特性(原子性、一致性、隔离性、持久性)确保了在数据中数据的绝对准确性。

向量数据库

为了解决非结构化数据的语义搜索问题,解决如何在海量的高维向量数据中,快速找到与查询向量最相似的结果。比如在推荐系统中找到与用户喜好相似的物品,或在图像库中检索出与查询图片最相近的图片。这类问题的特点是:

  1. 查询的不是精确匹配,而是相似度排名。
  2. 数据维度极高(通常128-2048维)。
  3. 数据规模庞大(可能达到十亿级别)。

传统数据库的精确查询方式在这种场景下完全失效,因为:

  1. 无法为高维向量建立有效的B树索引。
  2. 计算全量数据的精确相似度代价过高。
  3. 无法支持“相似但不完全相同”的搜索需求。

四、如何选择向量数据库

向量数据库比较

下面我们通过10个不同维度来比较一下不同向量数据库的区别:

主流向量数据库多维对比表格

从上面表格可以看到:

  1. 自 2016 年起 ,向量数据库逐渐崭露头角,成为 AI 和大数据领域的重要基础设施。而到了 2021 年之后 ,随着深度学习、大模型和推荐系统的迅猛发展,向量数据库正式迈入爆发式增长时代 ,成为现代数据架构中不可或缺的核心组件。
  2. 超过半数的向量数据库均采用分布式架构设计,并且这些支持分布式部署的系统普遍具备弹性扩缩容能力,能够根据业务需求实现资源的动态调整。
  3. 当业务需要处理亿级甚至更高规模的向量数据时,推荐以下高性能、可扩展的向量数据库:Vespa、Milvus/Zilliz、Vald、Qdrant。
  4. 当前主流的向量数据库普遍采用模块化、插件式的设计理念。其核心引擎大多基于 C/C++ 开发,以追求极致的性能表现。与此同时,Go 和 Rust 也正在这一领域崭露头角。
  5. 在向量数据库领域,HNSW(Hierarchical Navigable Small-World)和 DiskANN 正逐渐成为主流索引方案。其中HNSW主要以内存搜索为主,DiskANN主要以磁盘搜索为主。值得注意的是,Qdrant 在优化 HNSW 的基础上,进一步实现了 基于磁盘的 HNSW 检索能力。

选择流行的索引

在向量数据库技术领域,有HNSW 和 DiskANN 作为两大主流索引方案,各自展现了独特的技术优势。我们从以下关键维度进行专业对比分析。

HNSW与DiskANN索引性能对比表格

从上表格我们可以得到,HNSW和DiskANN适用于不同的场景:

  • HNSW :以 内存优先 的设计实现高性能搜索,适合 低延迟、高吞吐 要求严格的场景,如实时推荐、广告检索等。
  • DiskANN :以 磁盘存储优化 为核心,在保证较高召回率的同时 显著降低硬件成本 ,适用于大规模数据下的经济型检索需求。

随着数据规模的持续增长,HNSW 和 DiskANN 的混合部署模式 或将成为行业标准,让用户能根据业务需求灵活选择 “极致性能” 或 “最优成本” 的检索策略。

综合比较和选择

不同数据量下向量数据库选型参考表格

从表格中可以得到:

  1. 如果数据流比较小,并且自身对Redis、PG、ES比较熟悉,就可以选择Redis、PG、ES。如DBA团队就比较适合。
  2. 如果数据量比较大,并且前期人力不足可以使用云托管方案。选择Zilliz、Pinecone、Vespa或者Qdrant,如果后期计划从云上迁移到自建可以选择Zilliz、Vespa或者Qdrant。

得物选择Milvus作为向量数据库

我们的需求

社区图搜和AI鉴别需要大量的数据支持,得物业务场景要求能支持十亿级向量数据搜索,有如下要求:

  1. 大数据量高性能搜索,RT需要在90ms以内。
  2. 大数据量但是性能要求不高时,RT满足500ms以内。

需要支持快速扩缩容:

满足上面2点就已经锁定在Milvus、Qdrant这两个向量数据库。如果从架构复杂度和维护/学习成本的角度考虑,我们应该优先选择Qdrant,因为它的架构相比Milvus没有那么复杂,并且维护/学习成本没有Milvus高,重要的Qdrant可以单独集群部署,不需要k8s技术栈的支撑。

Milvus 和 Qdrant 架构比较

Milvus架构
Milvus部署依赖许多外部组件,如存储元信息的ETCD、存储使用的MinIO、消息存储Pulasr 等。

Milvus社区版架构图

Qdrant
Qdrant完全独立开发,支持集群部署,不需要借助ETCD、Pulsar等组件。

Qdrant集群架构图

选择Milvus的原因

※ 业务发展需求

业务属于快速发展阶段,数量的变化导致扩缩容频繁,使用支持k8s的Milvus在扩缩容方面会比Qdrant快的多。

※ 技术储备和社区良好

对DBA而言,向量数据库领域需要持续的知识更新和技术支持。从问题解决效率来看,国内技术社区对Milvus的支持体系相较于Qdrant更为完善。

※ 契合得物DBA开发栈

Milvus使用的开发语言是Go,契合DBA团队技术栈,在部分运维场景下,通过二次开发满足运维需求。例如:使用milvus-backup工具做迁移,部分的segment有问题需要跳过。自行修改一下代码编译运行即可满足需求。

Go代码片段:跳过错误Segment

jobIds := make([]int64, 0)
for _, segment := range segments {
 log := log.With(zap.Int64( key:“collection_id”, segment.GetCollectionId()),
 zap.Int64( key:“partition_id”, segment.GetPartitionId()),
 zap.Int64( key:“segment_id”, segment.GetSegmentId()),
 zap.Int64( key: “group_id”, segment.GetGroupId()))
 log.Debug(msg:“copy segment”)
 _, err := b.fillSegmentBackupInfo(ctx, segment)
 if err != nil {
  log.Error(msg:“Fail to fill segment backup info”, zap.Error(err))
  continue
 }
}

五、Milvus在得物的实践

部署架构演进

小试牛刀

初始阶段,我们把Milvus部署在K8S上,默认使用HNSW索引。架构图如下,Milvus整个架构较为复杂,外部依赖的组件多,每个集群需要部署自己的 ETCD、ZK、消息队列模块,多套集群共享着同一个存储。

Milvus初期部署架构图

存储拆分,每个集群独立存储

共享存储瓶颈导致稳定性问题凸显。

随着业务规模扩展,集群数量呈指数级增长,我们观测到部分集群节点出现异常重启现象,经诊断确认该问题源于底层共享存储存在性能瓶颈。

ETCD访问超时错误日志截图

共享存储瓶颈时期的Milvus架构图

独立资源池迁移至共享资源池

通过混布的方式提升资源利用率。

前期为了在性能和稳定性上更好的服务业务,Milvus部署的底层机器都是独立的,目的就是为了和其他应用隔离开,不相互影响。但是随着集群的越来越多,并不是所有的集群对稳定性和性能要求那么高,从监控上看Milvus集群池的资源使用不超过10%。为了提高公司资源利用率,我们将独立部署的Milvus迁移高共享资源池中,和大数据、业务应用等K8S部署相关服务进行混合部署。

Milvus集群从独立资源池迁移至共享资源池示意图

DiskANN索引的使用

数据量大且搜索QPS小时选择DiskANN 作为索引。 通过监控发现有很多集群数据量比较大,但是QPS并不是那么高,这时候就考虑对这些性能要求不高的集群是否有降本的方案。通过了解我们默认使用的HNSW索引需要将所有数据都加载到内存中进行搜索,第一反应就是它的内存查询和Redis一样,那是否有类似pika的方案内存只存少部分数据大部分数据存在磁盘上。这时候发现DiskANN就能达到这样的效果。

性能压测

※ 集群规格

HNSW与DiskANN压测集群规格对比表

HNSW与DiskANN在不同并发下的QPS对比折线图

※ QPS

HNSW与DiskANN在不同并发下的响应时间对比折线图

※ 延时(ms)

新增DiskANN索引后集群架构

增加DiskANN后我们需要对相关服务器上挂载 NVME SSD 磁盘,用于在磁盘上搜索最终数据。

引入DiskANN索引后的Milvus集群架构图

DiskANN 加载数据过程

DiskANN索引数据加载过程示意图

引入Zilliz

经过大规模生产验证,Milvus在实际业务场景中展现出卓越的性能表现和稳定性,获得业务方的高度认可。并且也吸引来了C端核心业务系统的使用。在使用前,我们使用了业务真实流量充分的对Milvus进行了压测,发现Milvus在亿级别数据量的情况下满足不了业务,因此对于部分核心场景我们使用了Zilliz。

Milvus和Zilliz 压测

业务的要求是集群返回的RT不能操过90ms。

使用真实的业务数据(亿级别)和业务请求对Milvus进行压测,发现Milvus并不能满足业务的需求。

类型 QPS 平均RT(ms) 客户端性能图
Milvus 110 200 Milvus压测客户端性能监控图
zilliz 350 65 Zilliz压测客户端性能监控图

Milvus RT 200不满足业务需求,并且QPS一直上不去,无论我们对QueryNode扩容多大,其中还发生过,将Query扩容到60个后,反而RT上升的问题,排查后是因为有的QueryNode和Proxy交互的时候网络会抖动影响了整体的RT。

从上面可以看出就算业务能容忍RT=200ms的,Milvus也需要创建3个相同的集群提供业务访问,并且业务需要改造代码实现多写、多读的功能,最终还会发现3个集群的成本远高于Zilliz。

通过成本和性能上的考虑,对于大数据量并且性能和稳定性要求高场景,我们将选用Zilliz。

迁移方案

对于不同业务场景,我们分别制定了以下3种迁移方案:

方案1:业务自行导入数据使用

迁移方案1:业务自行导入数据流程图

方案2:备份恢复 + 业务增量

迁移方案2:备份恢复加增量同步流程图

方案3:全量 + 增量 + 业务双写/回滚

迁移方案3:全量增量同步与业务双写回滚流程图

高可用架构部署

随着业务关键性持续提升,Milvus对应的SLA变得越来越重要。在此背景下,构建完善的Milvus高可用架构与灾备体系已成为系统设计的核心考量要素。比如:主从、多zone部署,Proxy高可用,Minio高可用,一个zone完全挂了怎么办等问题?

方案1:同城多机房混部

正常访问:

  • 该方案会有客户端会有跨机房访问的情况。

跨机房访问节点:
客户端 -> SLB
SLB -> Proxy
Proxy -> QueryNode

  • SLB有高可用
  • Proxy有高可用

方案1正常访问架构图

当部分节点不可用:

  1. 当zone 1中的proxy 1不可用,不影响整个访问链路,其他Proxy依然可以接受请求。
  2. 当 zone 1 中的 QueryNode 1 不可用,会出现访问报错的问题。需要重建QueryNode1,有可能在 Zone 2 新建QueryNode 5,原本请求QueryNode 1 的流量会重新指向 QueryNode 5。

方案1部分节点不可用架构图

当 Zone1 不可用:

  1. 访问会切换到 Zone 2 的备用SLB中。
  2. 备用SLB会访问本机房的Proxy。
  3. 由于 QueryNode 1 和 QueryNode 2 已经不可用,需要重建QueryNode,新生成 QueryNode 5、QueryNode 6并且加载数据提供访问。

方案1整个Zone不可用架构图

方案2:同城多zone多副本就近访问

正常访问:

  1. 不同zone的客户端访问本地的SLB。
  2. 使用了QueryNode多副本特性,各自zone的QueryNode都加载了所有数据。

Proxy -> QueryNode 的访问,目前Proxy只能随机访问所有zone的QueryNode(这是Milvus的限制)

方案2正常访问架构图

当部分节点不可用:

  1. 当每个zone 都有1个Proxy故障,并不影响业务正常访问。
  2. 由于QueryNode开启了副本,只要每个zone不相同的QueryNode故障,集群还是能正常运行。需要注意的是这时候需要考虑剩下的QueryNode性能是否满足需求。所以一般业务需要有限流功能,在剩余的QueryNode不满足需求时,业务需要限流,直到其他QueryNode恢复。

方案2部分节点不可用架构图

整个Zone不可用:

  1. 当Zone1整个不可用,不影响Zone2的访问。

方案2整个Zone不可用架构图

方案3:同城多zone单独部署业务交叉访问互相backup

正常访问:

  1. 每个zone都有单独部署的milvus集群。
  2. 每个集群的有同时满足 业务1、业务2 访问的数据。
  3. 业务访问Proxy的时候是有交叉访问的情况
  4. 业务改造会比较多,需要实现双写。

方案3正常访问架构图

当部分节点不可用:

  1. 当Zone1中的Proxy1部可用,不会影响Zone1的整个集群访问。
  2. 当Zone1的QueryNode1不可用,会影响到线路1、2的正常访问,这时候业务需要切换不访问Zone1的SLB。

方案3部分节点不可用架构图

当整个zone不可用

  1. 整个zone1不可用,由于线路1会访问到zone1的SLB,因此线路1访问会报错,业务需要将线路1切换成线路2

方案3整个Zone不可用架构图

六、向量数据库运维沉淀

索引结构和搜索原理

HNSW 索引

※ 相关信息

HNSW索引初始化参数与数据结构示例

※ 内存结构

由于空间问题,图中并没有完全按 M=16、ef=200 参数进行画图。

HNSW索引多层内存结构示意图

※ HNSW搜索过程

现在需要搜索向量N = [....]

第一步:
在第一层随机选择一个节点,如:3。

HNSW搜索第一步:选择入口节点

第二步:

  1. 通过节点3,从第1层转跳到第2层
  2. 在第2层,通过节点3获取到相邻的节点:节点1、节点2、节点5、节点6。
  3. 将搜索节点N逐个和 节点1、节点2、节点5、节点6 进行计算出各自的距离。并且选择距离最短的节点6。
    1. 节点N -> 节点1:10
    2. 节点N -> 节点2:6
    3. 节点N -> 节点5:9
    4. 节点N -> 节点6:3
    5. 节点N -> 节点3:4
  4. 将 节点1、节点2、节点5、节点6、节点3 放入结果候选集中。

HNSW搜索第二步:在第二层寻找更近节点

第三步:

  1. 。通过节点6,从第二层跳到底3层
  2. 在第3层,通过节点6获取到相邻的节点:节点2、节点3、节点6、节点9,其中 节点2、节点3、节点6 已经存在,因此只需要将 节点9 放入候选结果集
  3. 如果候选结果集合没有满,则继续便利 节点2、节点3、节点9 的邻居,直到 节点数=ef=200。

HNSW搜索第三步:在底层数据层完成搜索

DiskANN 索引

※ 相关信息

DiskANN索引初始化参数与数据结构示例

※ 存储结构和裁剪过程

由于画图空间问题,没办法将 聚类数=10、100/内存聚类、1w/磁盘聚类 信息画全。

DiskANN索引构建与量化过程示意图

  1. 初始化随机连接: DiskANN算法会将向量数据生成一个密集的网络图,其中点和点是随机链接的,并且每个点大概有500个链接。
  2. 裁剪冗余链接: 通过计算点和点点距离裁剪掉一些冗余的链接,留下质量高的链接。
  3. 添加快速导航链接: 计算出图中若干个中心点,并且将这些中心点进行链接,并且这些链接会跳过其他点,如果图中黄色链接
  4. 重复进行裁剪优化过程,达到最优的情况。
  5. PQ量化操作生成索引:
    1. 将向量分成多个子空间。
    2. 独立对每个子空间进行聚类操作,并且计算出多个质心。
    3. 将每个子向量映射到最近的质心ID。

※ DiskANN搜索过程

现在需要搜索向量N = [....]

第一步(内存索引中搜索):
将搜索的向量进行量化。
将量化后的数据在内存中索引寻找到离自己较近的质心为入口进行下一步搜索。

DiskANN搜索第一步:在内存量化索引中寻找入口

第二步(内存中搜索):
通过第一个节点,寻找到它的所有相邻节点
通过内存PQ代码计算搜索节点相邻节点的近似距离。
这些邻居节点都是潜在的下次搜索候选口节点。

DiskANN搜索第二步:在内存中扩展搜索候选集

第三步(磁盘中搜索):
通过计算搜索节点相邻节点的真实距离,并且得到距离最近的一个节点(需要从磁盘上读取证实的节点数据并计算)。
并且通过得到的最新节点获取到新的相邻节点。

DiskANN搜索第三步:从磁盘读取精确数据计算距离

第四步(磁盘中搜索):
反复先进二、三步骤操作,直到找到足够数量的邻居。

DiskANN搜索第四步:迭代搜索直至满足条件

并不是你想的那样

querynode 越多越快?

querynode 越多,查询越快,并发越高?

※误区原因

将querynode看成redis cluster,增加节点数能提高查询并发,然而并不是。redis cluster 增加节点,数据量会尽可能的打散到每个节点中,所以增加节点和性能提升是相对成正比。但是milvus不一样,milvus打散的基本单位是segment,一般segment大小(1G/个),他的粒度比redis cluster要大。理论上的理想情况是1个segment对应1个querynode,但是实际情况会收到多因素的干扰,会导致querynode越多出现不稳定的概率越大,如某个querynode网络抖动会影响整体的查询RT。

不能提升性能例1:
部分segment数 < querynode数,或部分querynode没有任何segment。

QueryNode无Segment导致无效示意图

不能提升性能例2:
querynode 过多,其中1个querynode RT 高,导致整体客户端RT高。

单个QueryNode高延迟影响整体RT示意图

标量索引提高性能

在标量上创建索引,搜索带上标量过来能提高性能?

※误区原因

使用传统关系型数据库的索引查询来理解Milvus的索引查询,字段上创建了索引能使用到索引扫描进行数据查询,比全表扫描快。然而并不是,关系型数据库的属于精确查询,Milvus属于近似最近邻搜索(ANNS),milvus的查询是不保证绝对精确,使用了标量索引查询反而会导致数据变稀疏查询会变慢。

使用标量索引筛选不一定快原因,如下示例:

通过标量搜索后再使用ANNS搜索过程:

1.Collection A 中的数据如下,其中is_delete 是标量,其中的值有0和1。

含标量字段的数据分布示意图

2.使用标量搜索is_delete=1后剩余的数据。

使用标量过滤后的数据分布示意图

3.再使用 ANNS 搜索获取最终数据。

在标量过滤结果上进行ANNS搜索示意图

通过标量is_delete=1搜索成功之后,有可能结果集会很大,结果集数据如果内存存放不下需要使用到磁盘存储,之后再在结果集中通过ANNS获取到最终需要的数据,如果需要频繁进行磁盘交互则搜索性能会很差。

通过ANNS搜索后再使用标量搜索,过程如下:

1.Collection A 中的数据如下,其中is_delete 是标量,其中的值有0和1。

含标量字段的数据分布示意图

2.使用ANNS搜索
使用ANNS搜索能直接很快地获取到满足条件的数据。

先进行ANNS搜索示意图

3.再使用标量过滤,获取到最终的数据。

在ANNS结果上进行标量过滤示意图

思考:
在第二步如果使用ANNS搜索完成之后到底是否需要使用标量索引进行搜索。

如果需要使用标量索引进行搜索那边在ANNS搜索后的结果集需要额外的进行索引构建,然后再进行过滤。构建构建过程其实也是需要便利结果集,那么是不是可以直接在便利的时候直接进行结果集的筛选。

那么其实在某种程度上是不是标量索引没那么好用。

大量单行dml不批量写入能提高数据库性能

大量单行dml,不使用批量写入操作,能提高数据库性能。

※误区原因

使用传统关系型数据库为了让系统尽量少的大事务,减少锁问题并且提高数据库性能。然而实际上Milvus如果有很多的小事务反而会影响到数据库的性能。因为Milvus进行dml操作会生成deltalog、insertlog,当dml都是小事务就会生成大量的相对较小的deltalog和insertlog文件,deltalog和insertlog在和segment做合并的时候会增加打开和关闭文件次数,并且增加做合并次数,导致io一直处于繁忙状态。

deltalog 和 insertlog 生成的契机有2种:

  1. 当数据量达到了一定的阈值会进行生成deltalog 或 insertlog。
  2. Milvus会定时进行生成deltalog 或 insertlog。

deltalog、insertlog 和 segment 合并过程

deltalog、insertlog与segment合并过程示意图

人为让 deltalog、segment 执行时机可预测

如果业务对数据实现要求不是那么高,建议使用定时批量的方式对数据进行写入,比如可以通过监控获取到每天的波谷时间段,在波谷时间段内进行集中式数据写入。原因是如果不停的在做写入,无法判断进行合并segment的时间点,要是在高峰期进行了合并操作,很有可能会影响到集群性能。

错误处理

※ 2.2.6 批量删除数据bug,导致业务无法查询

报错:

pymilvus.exceptions.MilvusException: <MilvusException: (code=1, message=syncTimestamp Failed:err: find no available rootcoord, check rootcoord state
, /go/src/github.com/milvus-io/milvus/internal/util/trace/stack_trace.go:51 github.com/milvus-io/milvus/internal/util/trace.StackTrace
/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:329 github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).ReCall
/go/src/github.com/milvus-io/milvus/internal/distributed/rootcoord/client/client.go:421 github.com/milvus-io/milvus/internal/distributed/rootcoord/client.(*Client).AllocTimestamp
/go/src/github.com/milvus-io/milvus/internal/proxy/timestamp.go:61 github.com/milvus-io/milvus/internal/proxy.(*timestampAllocator).alloc
/go/src/github.com/milvus-io/milvus/internal/proxy/timestamp.go:83 github.com/milvus-io/milvus/internal/proxy.(*timestampAllocator).AllocOne
/go/src/github.com/milvus-io/milvus/internal/proxy/task_scheduler.go:172 github.com/milvus-io/milvus/internal/proxy.(*baseTaskQueue).Enqueue
/go/src/github.com/milvus-io/milvus/internal/proxy/impl.go:2818 github.com/milvus-io/milvus/internal/proxy.(*Proxy).Search
/go/src/github.com/milvus-io/milvus/internal/distributed/proxy/service.go:680 github.com/milvus-io/milvus/internal/distributed/proxy.(*Server).Search
/go/pkg/mod/github.com/milvus-io/milvus-proto/go-api@v0.0.0-20230324025554-5bbe6698c2b0/milvuspb/milvus.pb.go:10560 github.com/milvus-io/milvus-proto/go-api/milvuspb._MilvusService_Search_Handler.func1
/go/src/github.com/milvus-io/milvus/internal/proxy/rate_limit_interceptor.go:47 github.com/milvus-io/milvus/internal/proxy.RateLimitInterceptor.func1
)>

解决: 将集群升级到2.2.16,并且让业务 批量删除和写入数据。

※ find no available rootcoord, check rootcoord state

报错:

[2024/09/26 08:19:14.956 +00:00] [ERROR] [grpcclient/client.go:158] ["failed to get client address"] [error="find no available rootcoord, check rootcoord state"] [stack="github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).connect
/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:158
github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).GetGrpcClient
/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:131
github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).callOnce
/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:256
github.com/milvus-io/milvus/internal/util/grpcclient.(*ClientBase[...]).ReCall
/go/src/github.com/milvus-io/milvus/internal/util/grpcclient/client.go:312
github.com/milvus-io/milvus/internal/distributed/rootcoord/client.(*Client).GetComponentStates
/go/src/github.com/milvus-io/milvus/internal/distributed/rootcoord/client/client.go:129
github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentStates.func1
/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:65
github.com/milvus-io/milvus/internal/util/retry.Do
/go/src/github.com/milvus-io/milvus/internal/util/retry/retry.go:42
github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentStates
/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:89
github.com/milvus-io/milvus/internal/util/funcutil.WaitForComponentHealthy
/go/src/github.com/milvus-io/milvus/internal/util/funcutil/func.go:104
github.com/milvus-io/milvus/internal/distributed/datanode.(*Server).init
/go/src/github.com/milvus-io/milvus/internal/distributed/datanode/service.go:275
github.com/milvus-io/milvus/internal/distributed/datanode.(*Server).Run
/go/src/github.com/milvus-io/milvus/internal/distributed/datanode/service.go:172
github.com/milvus-io/milvus/cmd/components.(*DataNode).Run
/go/src/github.com/milvus-io/milvus/cmd/components/data_node.go:51
github.com/milvus-io/milvus/cmd/roles.runComponent[...].func1
/go/src/github.com/milvus-io/milvus/cmd/roles/roles.go:102"]

问题: rootcoord和其他pod通信出现了问题。

解决: 先重建rootcoord,再依次重建相关的querynode、indexnode、queryrecord、indexrecord。

※ 页面查询报错 (Search 372 failed, reason Timestamp lag too large lag)

[2024/09/26 09:14:13.063 +00:00] [WARN] [retry/retry.go:44] ["retry func failed"] ["retry time"=0] [error="Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:529] ["QueryNode search result error"] [traceID=62505beaa974c903] [msgID=452812354979102723] [nodeID=372] [reason="Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:132] ["failed to do query with node"] [traceID=62505beaa974c903] [nodeID=372] [dmlChannels="[by-dev-rootcoord-dml_6_442659379752037218v0,by-dev-rootcoord-dml_7_442659379752037218v1]"] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:159] ["retry another query node with round robin"] [traceID=62505beaa974c903] [Nexts="{\"by-dev-rootcoord-dml_6_442659379752037218v0\":-1,\"by-dev-rootcoord-dml_7_442659379752037218v1\":-1}"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:60] ["no shard leaders were available"] [traceID=62505beaa974c903] [channel=by-dev-rootcoord-dml_6_442659379752037218v0] [leaders="[<NodeID: 372>]"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_policies.go:119] ["failed to search/query with round-robin policy"] [traceID=62505beaa974c903] [error="Channel: by-dev-rootcoord-dml_7_442659379752037218v1 returns err: code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)Channel: by-dev-rootcoord-dml_6_442659379752037218v0 returns err: code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:412] ["failed to do search"] [traceID=62505beaa974c903] [Shards="map[by-dev-rootcoord-dml_6_442659379752037218v0:[<NodeID: 372>] by-dev-rootcoord-dml_7_442659379752037218v1:[<NodeID: 372>]]"] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_search.go:425] ["first search failed, updating shardleader caches and retry search"] [traceID=62505beaa974c903] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [INFO] [proxy/meta_cache.go:767] ["clearing shard cache for collection"] [collectionName=xxx]
[2024/09/26 09:14:13.063 +00:00] [WARN] [retry/retry.go:44] ["retry func failed"] ["retry time"=0] [error="code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)"]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/task_scheduler.go:473] ["Failed to execute task: "] [error="fail to search on all shard leaders, err=All attempts results:\nattempt #1:code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)\nattempt #2:context canceled\n"] [traceID=62505beaa974c903]
[2024/09/26 09:14:13.063 +00:00] [WARN] [proxy/impl.go:2861] ["Search failed to WaitToFinish"] [traceID=62505beaa974c903] [error="fail to search on all shard leaders, err=All attempts results:\nattempt #1:code: UnexpectedError, error: fail to Search, QueryNode ID=372, reason=Search 372 failed, reason Timestamp lag too large lag(28h44m48.341s) max(24h0m0s) err %!w(<nil>)\nattempt #2:context canceled\n"] [role=proxy] [msgID=452812354979102723] [db=] [collection=xxx] [partitions="[]"] [dsl=] [len(PlaceholderGroup)=4108] [OutputFields="[id,text,extra]"] [search_params="[{\"key\":\"params\",\"value\":\"{\\\"ef\\\":250}\"},{\"key\":\"anns_field\",\"value\":\"vector\"},{\"key\":\"topk\",\"value\":\"100\"},{\"key\":\"metric_type\",\"value\":\"L2\"},{\"key\":\"round_decimal\",\"value\":\"-1\"}]"] [travel_timestamp=0] [guarantee_timestamp=0]

问题: pulsar 组件对应相关pod问题导致不进行消费。

解决: 将pulsar 组件相关pod进行重建,查看日志,并且等待消费pulsar完成。

※ Query Node 限制内存不足 (memory quota exhausted)

报错:

<MilvusException: (code=53,message=deny to write, reason: memory quota exhausted, please allocate more resources, req: /milvus.proto.milvus.MilvusService/Insert)>

原因: 配置中Query Node配置内存上线达到瓶颈。

解决: 增加Query Node配置或者增加QueryNode节点数。

※ 底层磁盘瓶颈导致ETCD访问超时

报错:

ETCD因磁盘瓶颈导致访问超时的错误日志

解决: 从架构方面上进行解决,在集群维度将磁盘进行隔离,每个集群使用独立磁盘。

七、未来展望

数据迁移闭环

数据迁移闭环:对于业务数据加载到向量数据库的场景,业务只关心数据的读取和使用,不需要关心数据的量化和写入。DBA侧建立数据迁移闭环(下图绿色部分)。

数据准确性校验

对于上游数据(如MySQL)和下游向量数据库数据库一致性校验问题,DBA业将协同业务、Milvus进行共建校验工具,保障数据的准确性。

向量数据库数据迁移与校验闭环流程图

本文分享的技术实践来自于得物DBA团队在KafkaETCDPulsar等组件与Milvus协同工作中的深度总结,更多关于数据库、运维/DevOps/SRE的深入探讨,欢迎在云栈社区交流。




上一篇:深入解析Java SPI机制:工作原理、ServiceLoader源码与JDBC/Spring/Dubbo实战
下一篇:Elasticsearch索引规划全指南:从分片策略到映射优化
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 19:46 , Processed in 0.221141 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表