得物向量数据库落地实践

一,背景

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

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

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

二,认识向量数据库

向量数据来源和存储

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

向量数据库是如何工作

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

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

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

传统数据库

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

向量数据库

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

  1. 查询的不是精确匹配,而是相似度排名。

  2. 数据维度极高(通常 128-2048 维)。

  3. 数据规模庞大(可能达到十亿级别)。

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

  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 的混合部署模式 或将成为行业标准,让用户能根据业务需求灵活选择 "极致性能" 或 "最优成本" 的检索策略。

综合比较和选择

从表格中可以得到:

如果数据流比较小,并且自身对 Redis、PG、ES 比较熟悉,就可以选择 Redis、PG、ES。如 DBA 团队就比较适合。

如果数据量比较大,并且前期人力不足可以使用云托管方案。选择 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 等。

Qdrant:

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

选择 Milvus 的原因

※ 业务发展需求

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

※ 技术储备和社区良好

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

※ 契合得物 DBA 开发栈

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

五,Milvus 在得物的实践

部署架构演进

小试牛刀

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

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

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

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

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

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

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

DiskANN 索引的使用

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

性能压测

※ 集群规格

QPS

图片延时(ms)

新增 DiskANN 索引后集群架构

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

DiskANN 加载数据过程

引入 Zilliz

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

Milvus 和 Zilliz 压测

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

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

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

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

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

迁移方案

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

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

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

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

高可用架构部署

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

方案 1: 同城多机房混部

正常访问:

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

跨机房访问节点:

客户端 -> SLB

SLB -> Proxy

Proxy -> QueryNode

  • SLB 有高可用

  • Proxy 有高可用

当部分节点不可用:

  1. 当 zone 1 中的 proxy 1 不可用,不影响整个访问链路,其他 Proxy 依然可以接受请求。

  2. 当 zone 1 中的 QueryNode 1 不可用,会出现访问报错的问题。需要重建 QueryNode1,有可能在 Zone 2 新建 QueryNode 5,原本请求 QueryNode 1 的流量会重新指向 QueryNode 5。

当 Zone1 不可用:

访问会切换到 Zone 2 的备用 SLB 中。

备用 SLB 会访问本机房的 Proxy。

由于 QueryNode 1 和 QueryNode 2 已经不可用,需要重建 QueryNode,新生成 QueryNode 5、QueryNode 6 并且加载数据提供访问。

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

正常访问:

  1. 不同 zone 的客户端访问本地的 SLB。

  2. 使用了 QueryNode 多副本特性,各自 zone 的 QueryNode 都加载了所有数据。

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

当部分节点不可用:

  1. 当每个 zone 都有 1 个 Proxy 故障,并不影响业务正常访问。

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

整个 Zone 不可用:

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

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

正常访问:

  1. 每个 zone 都有单独部署的 milvus 集群。

  2. 每个集群的有同时满足 业务 1、业务 2 访问的数据。

  3. 业务访问 Proxy 的时候是有交叉访问的情况

  4. 业务改造会比较多,需要实现双写。

当部分节点不可用:

  1. 当 Zone1 中的 Proxy1 部可用,不会影响 Zone1 的整个集群访问。

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

当整个 zone 不可用

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

六,向量数据库运维沉淀

索引结构和搜索原理

HNSW 索引

※ 相关信息

※ 内存结构

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

※ HNSW 搜索过程

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

第一步:

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

第二步:

  1. 通过节点 3,从第 1 层转跳到第 2 层

  2. 在第 2 层,通过节点 3 获取到相邻的节点:节点 1、节点 2、节点 5、节点 6。

  3. 将搜索节点 N 逐个和 节点 1、节点 2、节点 5、节点 6 进行计算出各自的距离。并且选择距离最短的节点 6。

    节点 N -> 节点 1:10

    节点 N -> 节点 2:6

    节点 N -> 节点 5:9

    节点 N -> 节点 6:3

    节点 N -> 节点 3:4

  4. 将 节点 1、节点 2、节点 5、节点 6、节点 3 放入结果候选集中。

第三步:

  1. 。通过节点 6,从第二层跳到底 3 层

  2. 在第 3 层,通过节点 6 获取到相邻的节点:节点 2、节点 3、节点 6、节点 9,其中 节点 2、节点 3、节点 6 已经存在,因此只需要将 节点 9 放入候选结果集。

  3. 如果候选结果集合没有满,则继续便利 节点 2、节点 3、节点 9 的邻居,直到 节点数=ef=200。

DiskANN 索引

※ 相关信息

※ 存储结构和裁剪过程

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

  1. 初始化随机连接:DiskANN 算法会将向量数据生成一个密集的网络图,其中点和点是随机链接的,并且每个点大概有 500 个链接。

  2. 裁剪冗余链接:通过计算点和点点距离裁剪掉一些冗余的链接,留下质量高的链接。

  3. 添加快速导航链接:计算出图中若干个中心点,并且将这些中心点进行链接,并且这些链接会跳过其他点,如果图中黄色链接。

  4. 重复进行裁剪优化过程,达到最优的情况。

  5. PQ 量化操作生成索引:

  • 将向量分成多个子空间。

  • 独立对每个子空间进行聚类操作,并且计算出多个质心。

  • 将每个子向量映射到最近的质心 ID。

※ DiskANN 搜索过程

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

第一步(内存索引中搜索):

将搜索的向量进行量化。

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

第二步(内存中搜索):

通过第一个节点,寻找到它的所有相邻节点。

通过内存 PQ 代码计算搜索节点和相邻节点的近似距离。

这些邻居节点都是潜在的下次搜索候选口节点。

第三步(磁盘中搜索):

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

并且通过得到的最新节点获取到新的相邻节点。

第四步(磁盘中搜索):

反复先进二、三步骤操作,直到找到足够数量的邻居。

并不是你想的那样

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。

不能提升性能例 2:

querynode 过多,其中 1 个 querynode RT 高,导致整体客户端 RT 高。

标量索引提高性能

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

※误区原因

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

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

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

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

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

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

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

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

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

2.使用 ANNS 搜索

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

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

思考:

在第二步如果使用 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。

eltalog、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 访问超时

报错:

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

七,未来展望

数据迁移闭环

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

数据准确性校验

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

往期回顾

1.社区搜索离线回溯系统设计:架构、挑战与性能优化|得物技术

2.从 Rust 模块化探索到 DLB 2.0 实践|得物技术

3.eBPF 助力 NAS 分钟级别 Pod 实例溯源|得物技术

4.正品库拍照 PWA 应用的实现与性能优化|得物技术

5.汇金资损防控体系建设及实践 | 得物技术

文 / 呆呆 少晖

关注得物技术,每周二、四更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任

相关推荐

从中心调度到自驱协作多 Agent 架构的三次反转|得物技术

本文探讨多Agent系统中的控制权分配问题,提出三大协作架构:Master Agent(中心调度)、任务看板(共享状态)与Advisor(轻量咨询)。通过划清上下文边界与模型边界,强调应以信息相关性而非职业标签划分Agent。三种模式分别将决策权集中于中心、下放至共享状态、再下沉至执行者,形成控制权逐步下沉的演进路径。案例显示,合理设计可提升复杂任务的并行性、弹性与可维护性。

SmartCodeTech的博客 405

Jev 实战:为AI工作流引入轻量决策层 x10倍提速|得物技术

完成这三个场景后,我对适用边界有了更明确的认识:它们都可以先定义答案范围,再根据判断结果驱动下一步操作。接入时,我会优先明确输入、选项、采纳条件和兜底路径,再评估Jev是否适合承担这一环节。结合这些实践,我更容易理解Jev(TypeSafe System One)作为专用结构化决策模型不生成自然语言,而是返回带概率的判断结果。后续接入时,我会继续从脚本中边界明确的判断任务开始,分别验证准确性、耗时和费用,再决定是否扩大使用范围。下面整理了相关能力、限制和接入要点,便于查阅。

SmartCodeTech的博客 343

国产算力·昇腾 910C 大模型推理优化(下):源码修改与算子优化,提速30%|得物技术

本文延续上篇,深入vllm-ascend源码优化:通过多流重叠实现MoE共享专家并行计算,消除冗余事件开销,并解除与FUSED_MC2的互斥限制,实现c=1下TPOT从8.2ms降至8.0ms;优化decode阶段host侧开销,引入状态跟踪跳过冗余拷贝;设计Block Table CPU快路径,规避小batch下NPU kernel启动延迟;针对GDN层融合L2Norm至recurrent kernel,减少算子调用与内存往返。综合优化后推理性能显著提升,形成可复用的昇腾910C大模型推理优化方法论。

SmartCodeTech的博客 238

国产算力·昇腾 910C 大模型推理优化(上):镜像更新与参数调优,提速 40%|得物技术

文中详细给出每一项优化的动机、实现(含具体源码改动路径与关键代码片段)、验证数据与踩坑记录(包括十余项实测无效或负向的尝试),希望为同样在国产算力上做推理优化的团队提供一份可直接复用的工程参考。

SmartCodeTech的博客 217

动态规则的 Agent 实践:用确定性工程驾驭 LLM 的资产智能验收

面对财务验收中规则多变、材料非结构化、核对复杂等痛点,传统规则引擎难以应对。系统采用“规则即配置”思路,由大语言模型(LLM)负责理解合同、扫描件等非结构化材料并执行语义比对,工程系统则保障流程编排与可追溯性。通过异步处理、幂等设计、三类流量隔离及双通道(文本/图像)处理,实现高效稳定运行。结合用户反馈构建自动标准答案,辅以试跑、回测与发布门禁机制,形成闭环质量体系。底层采用确定性工作流架构,确保行为可预期、可测试,真正实现灵活规则的快速迭代与安全上线。

SmartCodeTech的博客 343

发一张图,它居然“跃“出屏幕了?一文读懂得物 3D 空间图片技术实践

3D 空间图片功能的落地,涉及模型生成、内容分发、客户端渲染之间的一系列取舍。本文重点分享如何生产出高质量、小体积的模型,以及如何在手机上流畅运行。

SmartCodeTech的博客 555

别再只卷向量检索了,得物交易搜索如何用“生成式”实现召回范式跃迁?

你搜"af1",它得给你"空军一号"。这一年,得物交易搜索把召回从"做选择题"改成了"写答案"——不再从商品池里"找"最像的,而是让模型自己"生成"你真正想要的。从 LLM 语义索引到 HLLM、语义 ID,搜索是怎么从"检索"走向"生成"的?

SmartCodeTech的博客 593

指标平台:从语义底座到智能消费的实践路径|得物技术

这篇文章想做的,不是在本体化的宏大叙事上再叠加一层理论,而是把我们在真实落地中验证过的路径讲清楚:语义底座怎么从零建立起来、怎么保证质量不失控、怎么支撑 Text2SQL 和 AI Coding 两条消费主线,以及沿着这条路径往前走,下一步该往哪个方向发力。

SmartCodeTech的博客 316

企业级 MultiAgent 落地:Plan 模式与主子 Agent 协作|得物技术

基于 AgentScope Java 的企业级 MultiAgent 平台,为研发、数据分析和知识问答等场景提供统一的模型调用、工具接入、任务编排、流式输出与运行追踪能力。

SmartCodeTech的博客 300

得物小摊 AI Native 演进实录:用 Harness 构建可控 AI 交付

AI Coding 一个人用很顺手,进了团队就漏:指令没人审、经验难沉淀、过程看不见、链路不闭环。作者团队基于 Multica 做了 EP-Harness,把 Agent 当成员管,给它套一层可执行、可观察、可治理的 Harness,并让反馈闭环。成果:可追溯决策、多 Agent 协作,高频异常从每 4 小时 2400 多条降到个位数,对得物技术补上团队级 AI 协作关键一环。

SmartCodeTech的博客 540

企业级 MultiAgent 的记忆系统:短期上下文与四层记忆架构实现|得物技术

在 MultiAgent 平台中,Agent 的"记忆"能力是实现智能对话和个性化服务的核心资产。本文深入剖析 Agent 记忆系统的设计理念与实现细节,重点解析长期记忆架构的写入与加载流程,涵盖 LLM 智能判断、语义检索、Token 预算控制、冲突处理等关键技术,为开发者提供完整的技术实现参考。

SmartCodeTech的博客 502

EP-Harness:从个人 AI Coding 到团队级 Agent 工作流|得物技术

AI Coding 个人用得爽,一到团队就崩。EP-Harness 把 Agent 从聊天工具变成团队协作成员——可管理、可追溯、可闭环。落地后高频异常从 2400+/4h 收敛到个位数。

SmartCodeTech的博客 688

得物知识问答:复合检索 Agent 的系统设计实践

从"搜到什么用什么"到"搜→筛→挖→验"自主决策闭环——我们造了一个有判断力的 RAG Agent,不是套壳。

SmartCodeTech的博客 733

实战从零开始构建一个Coding Agent:Violin |得物技术

理解了 coding agent 的构建原理,也就掌握了理解其他 agent 的一把钥匙。笔者对每天都在使用的 agent 原理很感兴趣,于是决定自己写一个:violin。

SmartCodeTech的博客 926

AI Native 交易核心系统的研发范式|得物技术

如何改造旧研发流程适配 AI 编码?本文提出五道标准化关口治理思路,依托 Claude Code Spec-Driven 插件打通需求澄清、方案设计、TDD 落地、门禁管控与埋点监控,构建带人工确认门控的完整工作流,以五层自底向上架构实现 AI 编码规范化落地。

SmartCodeTech的博客 972

推荐系统体验的数字化突破:得物自动化评测平台的技术实践|AICon 文章整理

「得物推荐 AI Harness 工程化实践系列」的末篇内容,本系列共三篇连载。本篇(末篇)详解得物推荐评测平台的完整技术体系:从主观体验量化到大模型自动化评测流水线,涵盖多维度指标设计、人机协作提示词校验闭环、分布式任务调度与成本精控等核心工程实现,以及单周百万级审计需求下实现 91% 资源成本优化的工业级落地实战细节。

SmartCodeTech的博客 1492

RAG 核心概念与原理:Chunking、Embedding、相似度、HNSW 与多路召回|得物技术

决定 RAG 效果的关键不在大模型,而是文本切块、查询改写、多路召回与精排。合理切块存好知识库内容,向量匹配抓语义、BM25 匹配关键词互补。先优化用户提问,快速召回一批相关文档,再用精排模型精细筛选,整套检索流程配合到位,问答准确度才会明显提升。

SmartCodeTech的博客 1079

从“机械应答“到“服务伙伴“:得物高可控智能客服的 Agent 工程实践|AICon 演讲整理

本次分享围绕以下核心技术展开:客服 Agent 挑战与架构设计(Single-Agent→Multi-Agent→Harness);高可控性数据飞轮(PE 自动化 + DPO 训练);RL 策略训练和人工经验对齐;多轮半双工消息流控制。

SmartCodeTech的博客 744

得物推荐系统诊断 Agent:从 “调接口” 到 “会思考”|AICon 演讲整理

本文是得物技术专家在 AICon 上海演讲整理的技术实录。AICon上海2026_全球人工智能开发与应用大会暨大模型应用生态展_InfoQ技术大会「得物推荐 AI Harness 工程化实践系列」的中篇内容,本系列共三篇连载。本篇(中篇)进一步详解得物推荐复杂业务场景下,AI代码从生成、防护校验到安全上线的完整技术体系、自研AI Harness整体架构框架、全流程安全防护机制、混合智能体核心算法实现,以及工业级工程落地的实战细节。

SmartCodeTech的博客 1361

得物 OceanBase 落地实践

得物选型OceanBase多模数据库应对业务挑战,通过验证其高可用架构、存储压缩技术及混合负载处理能力。测试显示OceanBase在复杂查询场景性能较MySQL提升130-200倍,在高并发TP/AP混合场景表现优异,存储成本降低40%+。通过DBA内部报表业务试点验证其稳定性后,逐步推广至核心业务场景,优化数据库技术栈,实现性能与成本的双重收益。

SmartCodeTech的博客 1380

AI UITester:AI Native 的 UI 自动化测试新范式|得物技术

ai_uitester 代表了 AI Native 的 UI 自动化测试新范式:这不仅是工具的升级,而是测试范式的转变 ------ 从 "代码驱动" 转向 "视觉驱动",从 "人工调试" 转向 "AI 自愈",从 "三端分离" 转向 "统一抽象"。

SmartCodeTech的博客 774

从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理

本文是得物技术专家在 AICon 上海演讲整理的技术实录。「得物推荐 AI Harness 工程化实践系列」的开篇内容,本系列共三篇连载。将系统拆解得物推荐复杂业务场景下,AI 代码从生成、防护校验到安全上线的完整技术体系,逐一详解自研 AI Harness 整体架构框架、全流程安全防护机制、混合智能体核心算法实现,以及工业级工程落地的实战细节。本篇(上篇):将整体介绍团队自研的 AI Harness 体系,包括建设思路、全生命周期防护机制、混合智能体架构及落地效果。

SmartCodeTech的博客 1080

从表单到 Agent:得物社区活动搭建的 AI 实践之路

一场营销活动从策划到上线,运营要在三个系统间跳转 10 + 次、填写 40 + 个字段。我们用 AI 重新设计了这条链路 —— 从 “AI 帮你填表单” 到 “两阶段 Agent + 聚合工作台”。这篇文章记录的不是技术细节,而是这条路上的选择和反思。

SmartCodeTech的博客 741

从埋点需求到规则资产:Hermes Agent 重构得物数仓工作流

埋点和指标需求中,消耗数据承接方的不是生成答案,而是补齐证据链、对齐历史口径、识别下游影响,并在生产写入前留下可追溯的确认记录。本文数据承接方指承接业务需求的数据同学或业务合作伙伴;后文讨论内部研发、元数据、查询和门禁能力在流程中的角色。

SmartCodeTech的博客 656

让 Claude Code 拥有自我进化和记忆系统|得物技术

本文记录了为 Claude Code 构建的一套持久化记忆与自我学习系统的设计思路与实现细节。这套系统让 AI 助手能够跨越会话边界记住用户习惯,并在每次对话结束后自动提炼行为规律和系统信息,通过向量检索快速提取关键信息,下一次对话时主动应用,逐步进化为更懂你的编程伙伴。

SmartCodeTech的博客 994

用 LLM Agent 重构告警排查流程|得物技术

告警排查需频繁切换日志、APM、链路追踪等多个平台,耗时 10~30 分钟,且高度依赖个人经验。我们做了 Troubleshooter——用 LLM Agent 自动完成数据采集、根因分析和处置建议生成。上线后中位数排查耗时从 20 分钟降到 4.4 分钟,覆盖 11 个服务和 10+ 种告警类型。

SmartCodeTech的博客 1004

HorizonVault 技术深潜:如何在 HDD 上做出 100GB/s+ 级大吞吐分布式存储|得物技术

HorizonVault是一款面向Kafka远程存储的高吞吐分布式存储引擎,通过分布式并行写入和状态感知调度解决冷数据存储的三大核心问题:吞吐瓶颈、性能抖动和恢复能力。系统采用分层架构,Meta负责全局资源调度,Broker处理请求路由,Store实现本地磁盘管理,形成完整的写入治理链路。通过将HDD磁盘提升为一等资源,实现顺序追加写入、多盘并行和慢盘隔离,在保证高吞吐的同时控制延迟。存储结构采用DiskStore组织磁盘资源,LogSegment顺序写入数据,IndexSegment定位读取,Change

SmartCodeTech的博客 890

Claude Code Harness 工程:数仓侧落地方案|得物技术

Harness 工程通过职责分层实现研发流水线可靠性提升。Claude 负责需求理解与代码生成等语义层工作;hooks 负责规范检查等确定性执行;Subagents 隔离血缘查询、自测等高 token 操作;Memory 承载跨会话状态持久化。四层协同使数仓 AI 开发从"对话式辅助"转变为"规则驱动的自动化流程"。

SmartCodeTech的博客 1092

BP Claw 破解 AI 编码输入难题 ——FlinkSpec 需求智能化实践|得物技术

本文为 FlinkSpec 系列之开篇,亦是这场工程化变革的序章。BP Claw 所立之处,不过链路之源。FlinkSpec 所图,乃以 AI 之力,将实时数仓从需求落地至验收上线的全程工序,熔铸为一套精密自洽、生生不息的智能工程体系。宏图未竟,后续系列将逐章揭幕,敬请期待!

SmartCodeTech的博客 1035

基于 Harness + SDD + 多仓管理模式的 AI 全栈开发实践|得物技术

Harness(约束)思维的本质是:给 AI 一个已有的实现作为参照,让它照着复刻一份,而不是凭空创造。就像给一个新入职的工程师说 "你照着这个模块的风格,写一个类似的",而不是 "你自由发挥"------ 前者往往能更快产出符合团队规范的代码。

SmartCodeTech的博客 1184
上一篇: Java volatile 关键字到底是什么|得物技术
下一篇: Java SPI 机制初探|得物技术
得物技术
得物技术 得物技术官方账号 得物技术官方账号
博客等级 码龄6年 2492粉丝 407原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值