共计 2439 个字符,预计需要花费 7 分钟才能阅读完成。
QueryNode 日志中频繁报错?对象存储数据离奇隐没 [1]?
令人震惊的数据失落事件就这样产生了,一位来自 BOSS 直聘的 AI 研发工程师无心卷入到此次的风波中,他和 Milvus 社区的搭档通过层层排查、抽丝剥茧,胜利找出了问题所在——GC。
风波未然平息,不过,提起当日事件,这位工程师仍心惊肉跳,于是将本人的亲身经历记录下来,以求为其余搭档提供借鉴。
对象存储数据离奇隐没始末
那是一个风和日丽的下午,我像平常一样部署了一套 Milvus 集群,并写入了一批数据,欢快地进行着检索,相安无事。
好景不长,第二天我突然发现 QueryNode 日志中频繁呈现 No Such Key 的 ERROR,这个谬误意味着节点无奈从对象存储的对应门路下获取数据文件。已知并没有进行过任何删除操作与 TTL 设置,同时通过测试在 insert 后,对象存储中也的确可能失常产生相应的 Log 文件,数据隐没产生在写入后的肯定工夫距离之后。
最终通过社区帮助,能够确定,这是因为门路配置反复,GC 操作造成的。
Milvus 的 GC 流程
咱们晓得,Milvus 的删除是软删除,即标记删除策略。在调用 drop 相干的办法时,会先在 meta 中将被删除项标记为 dropped,待到 GC 触发时,再进行具体的清理。
GC 的指标次要蕴含两局部,一是 Etcd 中保留的元数据,二是对象存储的存储空间。
GC 行为由 DataCoord 发动。当 DataCoord 启动时,随之产生 Garbage Collector,执行周期性的 GC 工作,由 milvus.yaml 中的参数 dataCoord.gc.interval 指定 GC 周期。
浏览 master 分支代码 internal/datacoord/garbage_collector.go 可知,每一次的 GC 分 5 步进行。
第一步是 clearEtcd,在这一步中,次要关注已标记为 Dropped 的 segment。
a. 通过遍历 meta 中的 segment 信息,统计出所有应该删除的 segment 信息,再逐个执行删除操作。
b. 对于每个待删除的 segment,首先进行删除校验。
- 判断 segment 是否满足过期工夫,该工夫由 dataCoord.gc.dropTolerance [2] 参数指定;
- 判断 channel checkpoint 是否为最新,在 dml 地位之后;
- 判断 compact 后的旧 segment 是否能够删除。(在 compact 过后,旧的 segment 会被标记为 Dropped,但若新的 segment 并未实现索引构建,则不能进行清理。)
c. 校验通过后,则执行对象存储回收动作,先清理 log 数据,再清理 meta 数据。
- 删除对象存储的 log 数据;
- 删除 meta 中的段信息;
- 删除空 channel 及对应 checkpoint。
第二步是 recycleUnusedIndexes,清理已标记删除的 index 的 meta 信息。
第三步是 recycleUnusedSegIndexes,同样是清理 index 的 meta 信息,这一步次要关注没有对应 segment 信息的 index。
第四步是 scan,顾名思义,这一步中扫描所有的 segment 的 meta 数据与对象存储门路下文件(insetLog/statsLog/deltaLog),如果存储中的数据与元数据不匹配,在容忍一段时间后,执行清理,容忍工夫由参数 dataCoord.gc.missingTolerance 指定。
最初是 recycleUnusedIndexFiles,执行 index file 的清理。
流程与 scan 步骤相似,扫描对象存储的 index_file 门路,清理所有在 meta 中已删除的 index 的索引文件。其中,为放慢清理速度,若该门路对应的 segment 已删除时,则间接删除所有以该 segment ID 为前缀的所有 index file。
复盘小结
回到咱们的问题上。通过排查,我发现该集群与另一集群配置了雷同的 bucket 与 rootPath。通过对 GC 流程的理解,可知问题呈现在第四步 scan。当集群 A 写入实现后,数据失常长久化到对象存储中,此时集群 B 的 garbage Collector 扫描存储门路,发现这一批数据在 meta 中不存在,然而因为仍在容忍工夫内,所以暂不回收。而等到容忍工夫一过,数据便被逐步回收掉了。
如果不小心触发了此问题,配置到反复 bucket 存储门路的集群将相互烦扰,相互 GC,相互损坏数据。当咱们将其中的烦扰集群下线后,问题便天然进行了。但创痕难以抚平,若从新 load 那些受到清理的 collection 时,咱们会发现仍然无奈胜利加载。这也很容易了解,在加载时,querynode 会依照 meta 中的 segment 存储门路寻找数据文件,一旦产生 No Such Key 的谬误,便会加载失败。很惋惜,这种数据的毁坏是不可逆的(除非所依赖的对象存储有数据恢复的能力),不过如果是曾经加载到内存的 collection,不会受到对象存储数据文件的隐没的影响,仍可进行失常的检索操作。
为了防止此类乌龙事件的产生,咱们须要保障一个独立的 Milvus 集群领有独立的存储资源,若是依赖于内部的 Etcd 与对象存储服务,须要分外留神配置中 etcd.rootPath 与 minio.rootPath 的全局惟一。为什么会呈现不同集群应用雷同的存储门路的问题呢?这不能以粗枝大叶一言蔽之。主观上,这是手动部署的必然缺点,若是将集群部署纳入到自动化流程中,进行对立的配置管理,能够防患于未然;主观角度,在集群启动时没有对存储门路的校验环节,一个简略的解决办法是对 rootPath 加锁,从而防止抵触配置的集群可能启动。
(本文作者马秉政系 BOSS 直聘 AI 研发工程师)
🌟【相干链接】🌟
[1] 问题链接
[2] dataCoord.gc.dropTolerance)
- 如果在应用 Milvus 或 Zilliz 产品有任何问题,可增加小助手微信“zilliz-tech”退出交换群。
- 欢送关注微信公众号“Zilliz”,理解最新资讯。
本文由 mdnice 多平台公布