Lucene 漏洞历险记:修复损坏的索引异常

news2025/7/9 9:59:30

作者:来自 Elastic  Benjamin Trent

有时,一行代码需要几天的时间才能写完。在这里,我们可以看到工程师在多日内调试代码以修复潜在的 Apache Lucene 索引损坏的痛苦。

做好准备

这篇博客与往常不同。它不是对新功能或教程的解释。这是关于花了三天时间编写的一行代码。我希望你能从中学到一些要点:

  • 只要有足够的时间和正确的工具,所有不稳定的测试都是可重复的
  • 多层测试是稳健系统的关键。但是,更高级别的测试变得越来越难以调试和重现。
  • Sleep 是一个出色的调试器

Elasticsearch 如何测试

在 Elastic,我们有大量针对 Elasticsearch 代码库的测试。有些是简单而有针对性的功能测试,有些是单节点 “快乐路径 - happy path” 集成测试,还有一些试图破坏集群以确保在故障情况下一切正常运行。当测试不断失败时,工程师或工具自动化将创建一个 github 问题并将其标记为特定团队进行调查。这个特定的错误是由最后一种测试发现的。这些测试很棘手,有时只有在多次运行后才能重复。

这个测试实际上在测试什么?

这个特定的测试很有趣。它将创建一个特定的映射并将其应用于主分片。然后尝试创建副本。关键的区别在于,当副本尝试解析文档时,测试会注入异常,从而导致恢复以令人惊讶(但意料之中)的方式失败。

一切都按预期进行,但有一个重大问题。在测试清理期间,我们验证了一致性,并且在那里,这个测试遇到了障碍。

这个测试未能以预期的方式失败。在一致性检查期间,我们将验证所有复制和主 Lucene 段文件是否一致。意思是,未损坏且完全复制。部分数据或损坏的数据比完全失败更糟糕。以下是失败的可怕且简短的堆栈跟踪。

Caused by: org.apache.lucene.index.CorruptIndexException: Problem reading index from store(ByteSizeCachingDirectory(ElasticsearchMockDirectoryWrapper(HybridDirectory@/opt/buildkite-agent/builds/bk-agent-prod-gcp-1707109485745743789/elastic/elasticsearch-periodic/server/build/testrun/internalClusterTest/temp/org.elasticsearch.indices.recovery.IndexRecoveryIT_40853F21F419B395-001/tempDir-005/node_t0/indices/ZNwxG7VvShuwYV78RTjknA/0/index lockFactory=org.apache.lucene.store.NativeFSLockFactory@2c169f59))) (resource=store(ByteSizeCachingDirectory(ElasticsearchMockDirectoryWrapper(HybridDirectory@/opt/buildkite-agent/builds/bk-agent-prod-gcp-1707109485745743789/elastic/elasticsearch-periodic/server/build/testrun/internalClusterTest/temp/org.elasticsearch.indices.recovery.IndexRecoveryIT_40853F21F419B395-001/tempDir-005/node_t0/indices/ZNwxG7VvShuwYV78RTjknA/0/index lockFactory=org.apache.lucene.store.NativeFSLockFactory@2c169f59))))

    at org.apache.lucene.index.SegmentCoreReaders.<init>(SegmentCoreReaders.java:165)
    at org.apache.lucene.index.SegmentReader.<init>(SegmentReader.java:96)
    at org.apache.lucene.index.ReadersAndUpdates.getReader(ReadersAndUpdates.java:178)
    at org.apache.lucene.index.ReadersAndUpdates.getLatestReader(ReadersAndUpdates.java:243)
    at org.apache.lucene.index.SoftDeletesRetentionMergePolicy.keepFullyDeletedSegment(SoftDeletesRetentionMergePolicy.java:82)
    at org.apache.lucene.index.FilterMergePolicy.keepFullyDeletedSegment(FilterMergePolicy.java:118)
    at org.apache.lucene.index.FilterMergePolicy.keepFullyDeletedSegment(FilterMergePolicy.java:118)
    at org.apache.lucene.index.ReadersAndUpdates.keepFullyDeletedSegment(ReadersAndUpdates.java:822)
    at org.apache.lucene.index.IndexWriter.isFullyDeleted(IndexWriter.java:6078)
    <snip>

    Caused by: java.io.FileNotFoundException: No sub-file with id .kdi found in compound file "_0.cfs" (fileName=_0.kdi files: [_0.pos, .nvm, .fnm, _0.tip, _Lucene90_0.dvd, _0.doc, _0.tim, _Lucene90_0.dvm, _ES87BloomFilter_0.bfm, .fdm, .nvd, _ES87BloomFilter_0.bfi, _0.tmd, .fdx, .fdt])

      at org.apache.lucene.codecs.lucene90.Lucene90CompoundReader.openInput(Lucene90CompoundReader.java:170)
      at org.apache.lucene.codecs.lucene90.Lucene90PointsReader.<init>(Lucene90PointsReader.java:63)
      at org.apache.lucene.codecs.lucene90.Lucene90PointsFormat.fieldsReader(Lucene90PointsFormat.java:74)
      at org.apache.lucene.index.SegmentCoreReaders.<init>(SegmentCoreReaders.java:152)
      <snip>

不知何故,在强制复制失败期间,复制的分片最终被损坏了!让我用通俗易懂的英语解释一下错误的关键部分。

Lucene 是一种基于段(segment)的架构,这意味着每个段都知道并管理自己的只读文件。这个特定的段正在通过其 SegmentCoreReaders 进行验证,以确保一切都是一致的。每个核心读取器都存储了元数据,指示给定段存在哪些字段类型和文件。但是,在验证 Lucene90PointsFormat 时,缺少某些预期文件。对于段 _0.cfs 文件,我们期望一个名为 kdi 的点格式文件。cfs 代表 “复合文件系统 - compound file system”,Lucene 有时会将所有字段类型和所有小文件组合成一个更大的文件,以实现更高效的复制和资源利用。事实上,所有三个点文件扩展名:kdd、kdi 和 kdm 都丢失了。我们怎么会遇到 Lucene 段期望找到一个点文件但却丢失的情况!?!看起来像是一个可怕的损坏错误!

修复每个错误的第一步都是复制它

复制这个特定错误的故障非常痛苦。虽然我们利用了 Elasticsearch 中的随机值测试(randomized value testing),但我们一定会为每个故障提供一个(希望)可重现的随机种子,以确保可以调查所有故障。好吧,除了由竞争条件(race condition)引起的故障外,这对所有故障都非常有用。

./gradlew ':server:internalClusterTest' --tests "org.elasticsearch.indices.recovery.IndexRecoveryIT.testDoNotInfinitelyWaitForMapping" -Dtests.seed=40853F21F419B395 -Dtests.jvm.argline="-Des.concurrent_search=true" -Dtests.locale=id-ID -Dtests.timezone=Asia/Jerusalem -Druntime.java=21

无论我尝试多少次,特定种子都不会在本地重复失败。但是,有办法执行测试并推动更可重复的失败。

我们的特定测试套件允许通过 -Dtests.iters 参数在同一命令中多次运行给定测试。但这还不够,我需要确保执行线程正在切换,从而增加发生这种竞争条件的可能性。系统的另一个障碍是测试最终运行时间太长,测试运行器会超时。最后,我使用以下噩梦般的 bash 来重复运行测试:

for run in {1..10}; do ./gradlew ':server:internalClusterTest' --tests "org.elasticsearch.indices.recovery.IndexRecoveryIT.testDoNotInfinitelyWaitForMapping" -Dtests.jvm.argline="-Des.concurrent_search=true" -Dtests.iters=10 ; done || exit 1

压力测试来了。这可以让你快速启动一个进程,该进程只会占用 CPU 核心。在运行失败测试的多次迭代时随机发送压力测试终于让我能够复制失败。更近了一步。要对系统施加压力,只需打开另一个终端窗口并运行:

stress-ng --cpu 16

揭示问题

现在测试失败已经基本可以重复出现,是时候尝试找到问题的根源了。这次测试奇怪的地方在于,Lucene 抛出了异常,原因是它期望有点值(point values),但测试中并未直接添加任何点值,只添加了文本值。这让我开始考虑最近对乐观并发控制字段 _seq_no_primary_term 的更改:这两个字段都被索引为点值,并存在于每个 Elasticsearch 文档中。

果然,有一个提交更改了 _seq_no 的映射器(mapper)!是的!这一定是原因!但我的兴奋很快被浇灭了。这个更改仅仅调整了字段添加到文档的顺序。在此之前,_seq_no 字段是最后添加到文档的;之后,它们是最先添加的。而字段添加顺序不可能导致 Lucene 文档的这种失败吧……

然而,事实证明,字段添加顺序的确引发了这个问题。这令人意外,最终发现这是 Lucene 本身的一个 Bug!解析字段的顺序改变,不应该影响解析文档的行为。

Lucene 中的错误

事实上,Lucene 中的错误主要集中在以下情况:

  • 索引点值字段(例如 _seq_no)
  • 尝试索引文本字段在分析过程中抛出
  • 在这种奇怪的状态下,我们从遇到文本索引分析异常的写入器打开近实时读取器

但无论我尝试多少种方法,都无法完全复制。我直接在整个 Lucene 代码库中添加了暂停点以进行调试。我尝试在异常路径中随机打开读取器。我甚至打印出数兆字节的日志,试图找到发生此故障的确切路径。我就是做不到。我花了一整天的时间战斗并失败。

然后我睡着了。

第二天我重新阅读了原始堆栈跟踪并发现了以下行:

    at org.apache.lucene.index.SoftDeletesRetentionMergePolicy.keepFullyDeletedSegment(SoftDeletesRetentionMergePolicy.java:82)

在我所有的重现尝试中,我从未专门设置保留合并策略。Elasticsearch 使用 SoftDeletesRetentionMergePolicy,以便我们可以准确地复制副本中的删除,并确保我们所有的并发控制都负责实际删除文档的时间。否则 Lucene 将完全控制并会在任何合并时删除它们。

一旦我添加了此策略并复制了上述最基本的步骤,故障就会立即复制。

我从来没有像现在这样高兴地在 Lucene 中发现一个 bug。

但这是值得的。

还不是结束

希望你和我一起享受这段疯狂的旅程!编写软件,尤其是像 Elasticsearch 和 Apache Lucene 这样广泛使用且复杂的软件,是值得的。然而,有时,它非常令人沮丧。我既爱又恨软件。错误修复永远不会结束!

Elasticsearch 包含新功能,可帮助你为你的用例构建最佳搜索解决方案。深入了解我们的示例笔记本以了解更多信息,开始免费云试用,或立即在你的本地机器上试用 Elastic。

原文:Lucene bug adventures: Fixing a corrupted index exception - Elasticsearch Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2269715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何提升可视化大屏的用户体验?

一、什么是可视化大屏的用户体验 可视化大屏的用户体验是指用户在使用大屏幕可视化系统时所感受到的整体体验。这包括系统的易用性、交互性、视觉效果、信息展示方式等方面。一个好的可视化大屏用户体验应该能够让用户轻松地获取所需的信息&#xff0c;快速理解数据&#xff0…

overscroll-behavior-解决H5在ios上过度滚动的默认行为

1. 问题 开发H5的过程中&#xff0c;经常会有android和ios两边系统需要兼容的情况。在ios上一直有个问题是当H5内容触及到页面顶部或底部时&#xff0c;还是可以被人为的往下或往下拉动界面。当然可能有的情况是比较适用的&#xff0c;比如你往下拉动&#xff0c;然后在导航栏…

【无线传感网】无线传感器网络拓扑控制技术

文章目录 拓扑控制的意义影响整个网络的生存时间减小节点间通信干扰&#xff0c;提高网络通信效率为路由协议、时间同步提供基础影响数据融合弥补节点失效的影响 拓扑控制的设计目标能量消耗覆盖度连通性算法的分布式程度网络延迟&#x1f6a9;干扰和竞争对称性鲁棒性和可扩展性…

使用pandas把数据库中的数据转成csv文件

使用pandas把数据库中的数据转成csv文件 1、效果图 2、流程 1、连接数据库,获取数据 2、把一些中文字符转成gbk,忽略掉无法转化的 3、把数据转成csv 3、代码 import pymysql import pandas as pddef get_database(databasename):

点击锁定按钮,锁定按钮要变成解锁按钮,然后状态要从待绑定变成 已锁定(升级版)

文章目录 1、updateInviteCodeStatus2、handleLock3、InviteCodeController4、InviteCodeService5、CrudRepository 点击锁定按钮&#xff0c;锁定按钮要变成解锁按钮&#xff0c;然后状态要从待绑定变成 已锁定&#xff1a;https://blog.csdn.net/m0_65152767/article/details…

活动报名系统源码:JAVA同城服务系统活动报名同城圈子商家商城城市代理躲猫猫

JAVA同城服务系统&#xff1a;打造多元化社交与娱乐新体验 在数字化时代&#xff0c;同城服务系统已成为连接城市生活的重要桥梁。我们精心打造的JAVA同城服务系统&#xff0c;不仅融合了活动报名、同城圈子、商家商城、城市代理等多重功能&#xff0c;还特别加入了创新的“躲…

【大模型实战篇】LLaMA Factory微调ChatGLM-4-9B模型

1. 背景介绍 虽然现在大模型微调的文章很多&#xff0c;但纸上得来终觉浅&#xff0c;大模型微调的体感还是需要自己亲自上手实操过&#xff0c;才能有一些自己的感悟和直觉。这次我们选择使用llama_factory来微调chatglm-4-9B大模型。 之前微调我们是用两块3090GPU显卡&…

数势科技:解锁数据分析 Agent 的智能密码(14/30)

一、数势科技引领数据分析变革 在当今数字化浪潮中&#xff0c;数据已然成为企业的核心资产&#xff0c;而数据分析则是挖掘这一资产价值的关键钥匙。数势科技&#xff0c;作为数据智能领域的领军者&#xff0c;以其前沿的技术与创新的产品&#xff0c;为企业开启了高效数据分析…

[卫星遥感] 解密卫星目标跟踪:挑战与突破的深度剖析

目录 [卫星遥感] 解密卫星目标跟踪&#xff1a;挑战与突破的深度剖析 1. 卫星目标跟踪的核心挑战 1.1 目标的高速与不确定性 1.2 卫星传感器的局限性 1.3 数据处理与融合问题 1.4 大尺度与实时性要求 2. 当前卫星目标跟踪的主流技术 2.1 卡尔曼滤波&#xff08;Kalman …

骑行解压:身心的奇妙之旅,VELO Angel Revo坐垫

在快节奏的都市生活中&#xff0c;骑行不仅是一种健康的生活方式&#xff0c;更是一种心灵的释放。从心理生理学的角度来看&#xff0c;骑行能够促使身体分泌内啡肽&#xff0c;带来愉悦感&#xff0c;同时&#xff0c;它还能转移注意力&#xff0c;缓解焦虑。在这场身心的奇妙…

Agent系列:AppAgent v2-屏幕智能Agent(详解版)

引言 简介 方法 Agent 框架 Agent 交互 探索阶段 部署阶段 文档生成 高级功能 实验结果 总结 局限性 未来工作 1. 引言 大语言模型&#xff08;LLM&#xff09;如 ChatGPT 和 GPT-4 显著提升了自然语言处理能力&#xff0c;并且推动了智能体在自主决策中的应用。…

高等数学学习笔记 ☞ 无穷小与无穷大

1. 无穷小 1. 定义&#xff1a;若函数当或时的极限为零&#xff0c;那么称函数是当或时的无穷小。 备注&#xff1a; ①&#xff1a;无穷小描述的是自变量的变化过程中&#xff0c;函数值的变化趋势&#xff0c;绝不能认为无穷小是一个很小很小的数。 ②&#xff1a;说无穷小时…

【网络安全实验室】SQL注入实战详情

如果额头终将刻上皱纹&#xff0c;你只能做到&#xff0c;不让皱纹刻在你的心上 1.最简单的SQL注入 查看源代码&#xff0c;登录名为admin 最简单的SQL注入&#xff0c;登录名写入一个常规的注入语句&#xff1a; 密码随便填&#xff0c;验证码填正确的&#xff0c;点击登录…

Hive性能调优考量

Hive作为大数据领域常见的数据仓库组件&#xff0c;在设计和开发阶段需要注意效率。影响Hive效率的不仅仅是数据量过大&#xff0c;数据倾斜、job&#xff08;小文件过多&#xff09;或者磁盘I/O过多、MapReduce分配不合理等因素都会对Hive的效率有影响。对Hive的调优可以从架构…

在CodeBlocks搭建SDL2工程构建TFT彩屏模拟器虚拟TFT彩屏幕显示

在CodeBlocks搭建SDL2工程构建TFT彩屏模拟器虚拟TFT彩屏幕显示 参考文章源码下载地址一、SDL2的创建、初始化、退出二、系统基本Tick、彩屏刷新、按键事件三、彩屏获取与设置颜色四、彩屏填充颜色及清屏五、彩屏显示中文和英文字符串六、彩屏显示数字七、彩屏初始化八、主函数测…

ESLint+Prettier的配置

ESLintPrettier的配置 安装插件 ​​​​​​ 在settings.json中写下配置 {// tab自动转换标签"emmet.triggerExpansionOnTab": true,"workbench.colorTheme": "Default Dark","editor.tabSize": 2,"editor.fontSize": …

Springboot使用RabbitMQ实现关闭超时订单的一个简单示例

1.maven中引入rabbitmq的依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency> 2.application.yml中进行rabbitmq相关配置&#xff1a; # rabbit…

复杂对象的创建与组装 - 建造者模式(Builder Pattern)

建造者模式&#xff08;Builder Pattern&#xff09; 建造者模式&#xff08;Builder Pattern&#xff09;建造者模式&#xff08;Builder Pattern&#xff09;概述建造者模式结构图代码 talk is cheap&#xff0c; show you my code总结 建造者模式&#xff08;Builder Patter…

云计算课程报告实验-WordCount算法实验 过程记录

内容描述 本实验指导书通过在华为鲲鹏上&#xff0c;编译运行WordCount程序。完成实验操作后&#xff0c;读者会掌握简单的程序编写&#xff0c;如WordCount中的getWords、countWords、treeMerge。 实验环境 华为鲲鹏云主机、openEuler 20.03操作系统&#xff1b;安装mpich-3…

springboot533图书管理系统(论文+源码)_kaic

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;图书信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…