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

作者:来自 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.mzph.cn/web/64438.shtml

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

相关文章

深度学习J6周 ResNeXt-50实战解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a; 1.阅读ResNeXt论文&#xff0c;了解作者的构建思路 2.对比之前介绍的ResNet50V2、DenseNet算法 3.复现ResNeXt-50算法 一、模型结构…

对话 Project Astra 研究主管:打造通用 AI 助理,主动视频交互和全双工对话是未来重点

Project Astra 愿景之一&#xff1a;「系统不仅能在你说话时做出回应&#xff0c;还能在持续的过程中帮助你。」 近期&#xff0c;Google DeepMind 的 YouTube 频道采访了 Google DeepMind 研究主管格雷格韦恩 (Greg Wayne)。 格雷格韦恩的研究工作为 DeepMind 的诸多突破性成…

LunarVim安装

LunarVim以其丰富的功能和灵活的定制性&#xff0c;迅速在Nvim用户中流行开来。它不仅提供了一套完善的默认配置&#xff0c;还允许用户根据自己的需求进行深度定制。无论是自动补全、内置终端、文件浏览器&#xff0c;还是模糊查找、LSP支持、代码检测、格式化和调试&#xff…

高质量 Next.js 后台管理模板源码分享,开发者必备

高质量 Next.js后台管理模板源码分享&#xff0c;开发者必备 Taplox 是一个基于 Bootstrap 5 和 Next.js 构建的现代化后台管理模板和 UI 组件库。它不仅设计精美&#xff0c;还提供了一整套易用的工具&#xff0c;适合各种 Web 应用、管理系统和仪表盘项目。无论你是初学者还是…

开发场景中Java 集合的最佳选择

在 Java 开发中&#xff0c;集合类是处理数据的核心工具。合理选择集合&#xff0c;不仅可以提高代码效率&#xff0c;还能让代码更简洁。本篇文章将重点探讨 List、Set 和 Map 的适用场景及优缺点&#xff0c;帮助你在实际开发中找到最佳解决方案。 一、List&#xff1a;有序存…

Java包装类型的缓存

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。 Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128&#xff0c;127] 的相应类型的缓存数据&#xff0c;Character 创建了数值在 [0,127] 范围的缓存数据&#xff0c;Boolean 直接返回 True or Fal…

工程师 - MinGW

MinGW Minimalist GNU for Windows&#xff0c;前身为mingw32&#xff0c;是一个免费开源的软件开发环境&#xff0c;从2010年开始项目停止并不再使用。后续提供MinGW-w64。 MinGW包括: - 移植到Windows上的GNU编译器集&#xff08;GCC&#xff09;&#xff0c;包括C、C、ADA和…

EasyExcel(读取操作和填充操作)

文章目录 1.准备Read.xlsx&#xff08;具有两个sheet&#xff09;2.读取第一个sheet中的数据1.模板2.方法3.结果 3.读取所有sheet中的数据1.模板2.方法3.结果 EasyExcel填充1.简单填充1.准备 Fill01.xlsx2.无模版3.方法4.结果 2.列表填充1.准备 Fill02.xlsx2.模板3.方法4.结果 …

CKA认证 | Day7 K8s存储

第七章 Kubernetes存储 1、数据卷与数据持久卷 为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题。 问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会…

关于JAVA方法值传递问题

1.1 前言 之前在学习C语言的时候&#xff0c;将实参传递给方法&#xff08;或函数&#xff09;的方式分为两种&#xff1a;值传递和引用传递&#xff0c;但在JAVA中只有值传递&#xff08;颠覆认知&#xff0c;基础没学踏实&#xff09; 参考文章&#xff1a;https://blog.csd…

Excel基础知识

一&#xff1a;数组 一行或者一列数据称为一维数组&#xff0c;多行多列称为二维数组&#xff0c;数组支持算术运算&#xff08;如加减乘除等&#xff09;。 行&#xff1a;{1,2,3,4} 数组中的每个值用逗号分隔列&#xff1a;{1;2;3;4} 数组中的每个值用分号分隔行列&#xf…

基于DIODES AP43781+PI3USB31531+PI3DPX1207C的USB-C PD Video 之全功能显示器连接端口方案

随着USB-C连接器和PD功能的出现&#xff0c;新一代USB-C PD PC显示器可以用作个人和专业PC工作环境的电源和数据集线器。 虽然USB-C PD显示器是唯一插入墙壁插座的交流电源输入设备&#xff0c;但它可以作为数据UFP&#xff08;上游接口&#xff09;连接到连接到TCD&#xff0…

gazebo_world 基本围墙。

如何使用&#xff1f; 参考gazebo harmonic的官方教程。 本人使用harmonic的template&#xff0c;在里面进行修改就可以分流畅地使用下去。 以下是world 文件. <?xml version"1.0" ?> <!--Try sending commands:gz topic -t "/model/diff_drive/…

解决无法在 Ubuntu 24.04 上运行 AppImage 应用

在 Ubuntu 24.04 中运行 AppImage 应用的完整指南 在 Ubuntu 24.04 中&#xff0c;许多用户可能会遇到 AppImage 应用无法启动的问题。即使你已经设置了正确的文件权限&#xff0c;AppImage 仍然拒绝运行。这通常是由于缺少必要的库文件所致。 问题根源&#xff1a;缺少 FUSE…

springboot配置oracle+达梦数据库多数据源配置并动态切换

项目场景&#xff1a; 在工作中很多情况需要跨数据库进行数据操作,自己总结的经验希望对各位有所帮助 问题描述 总结了几个问题 1.识别不到mapper 2.识别不到xml 3.找不到数据源 原因分析&#xff1a; 1.配置文件编写导致识别mapper 2.配置类编写建的格式有问题 3.命名…

html+css+js网页设计 美食 家美食1个页面

htmlcssjs网页设计 美食 家美食1个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…

【机器学习】【朴素贝叶斯分类器】从理论到实践:朴素贝叶斯分类器在垃圾短信过滤中的应用

&#x1f31f; 关于我 &#x1f31f; 大家好呀&#xff01;&#x1f44b; 我是一名大三在读学生&#xff0c;目前对人工智能领域充满了浓厚的兴趣&#xff0c;尤其是机器学习、深度学习和自然语言处理这些酷炫的技术&#xff01;&#x1f916;&#x1f4bb; 平时我喜欢动手做实…

Vue使用Tinymce 编辑器

目录 一、下载并重新组织tinymce结构二、使用三、遇到的坑 一、下载并重新组织tinymce结构 下载 npm install tinymce^7 or yarn add tinymce^7重构目录 在node_moudles里找到tinymce文件夹&#xff0c;把里面文件拷贝一份放到public下&#xff0c;如下&#xff1a; -- pub…

EMNLP'24 最佳论文解读 | 大语言模型的预训练数据检测:基于散度的校准方法

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击 阅读原文 观看作者讲解回放&#xff01; 作者简介 张伟超&#xff0c;中国科学院计算所网络数据科学与技术重点实验室三年级直博生 内容简介 近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;的…

大数据技术-Hadoop(一)Hadoop集群的安装与配置

目录 一、准备工作 1、安装jdk&#xff08;每个节点都执行&#xff09; 2、修改主机配置 &#xff08;每个节点都执行&#xff09; 3、配置ssh无密登录 &#xff08;每个节点都执行&#xff09; 二、安装Hadoop&#xff08;每个节点都执行&#xff09; 三、集群启动配置&a…