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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【最新】沃德协会管理系统源码+uniapp前端+环境教程

一.系统介绍 一款基于FastAdminThinkPHPUniapp开发的商协会系统&#xff0c;新一代数字化商协会运营管理系统&#xff0c;以“智慧化会员体系、智敏化内容运营、智能化活动构建”三大板块为基点&#xff0c;实施功能全场景覆盖&#xff0c;一站式解决商协会需求壁垒&#xff0…

STM32 软件I2C读写

单片机学习&#xff01; 目录 前言 一、软件I2C读写代码框架 二、I2C初始化 三、六个时序基本单元 3.1 引脚操作的封装和改名 3.2 起始条件执行逻辑 3.3 终止条件执行逻辑 3.4 发送一个字节 3.5 接收一个字节 3.5 发送应答&接收应答 3.5.1 发送应答 3.5.2 接…

LeetCode - Google 校招100题 第6天 回溯法(Backtracking) (8题)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144743505 LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (20题)

【PCIe 总线及设备入门学习专栏 4.5 -- PCIe Message and PCIe MSI】

文章目录 PCIe Message 与 MSIPCIe Message 和 MSI 的作用与关系MSI 的配置与寄存器MSI 和 ARM GIC 的关系示例&#xff1a;MSI 在 ARM GIC 的实际应用总结 PCIe Message 与 MSI 本文将介绍 PCIe message 的作用以及message 与 MSI 的关系&#xff0c;再介绍 MSI 如何配置以及…

ThinkPHP 8高效构建Web应用-第一个简单的MVC应用示例

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 我们先实现一…

NSSCTFpwn刷题

[SWPUCTF 2021 新生赛]nc签到 打开附件里面内容 import osart (( "####!!$$ ))#####!$$ ))(( ####!!$:(( ,####!!$: )).###!!$:##!$:#!!$!# #!$: #$#$ #!$: !!!$:\ "!$: /\ !: /"\ : /"-."-/\\\-."//.-"…

Vue多页面路由与模版解析

上篇文章中我们成功打包并输出了多页文件&#xff0c;而构建一个多页应用能够让我们进一步了解项目配置的可拓展性&#xff0c;可以对学习 Vue 和 webpack 起到强化训练的效果&#xff0c;本文将在此基础上主要针对多页路由及模板的配置进行系列的介绍。 路由配置 1. 跳转 在…

UE5材质节点SimpleGrassWind

SimpleGrassWind节点可以模拟树叶扰动&#xff0c;或小草晃动效果 用来做风格化树、风格化草效果很好 主要节点 前三个节点分别用来控制&#xff0c;风强度&#xff0c;风重力&#xff0c;风速度&#xff0c;WPO是世界位置偏移

macrodroid通过http请求控制手机运行宏

macrodroid adb命令 adb shell pm grant com.arlosoft.macrodroid android.permission.WRITE_SECURE_SETTINGS例:http请求手机播放指定MP3文件 声音素材_电量过低提醒 新建一个宏 添加触发器-连接-http服务器请求 路径随意填,最好不要有特殊符号,不然浏览器识别链接会出错,…

单片机中运行多个定时器

在单片机的裸机编程环境中&#xff0c;同时运行多个定时器是完全可行的&#xff0c;但需要注意一些关键点以确保系统的稳定性和效率。以下是一些考虑因素和实现方法&#xff1a; 1. 硬件支持 定时器数量&#xff1a;首先确认您的单片机是否具备足够的定时器资源。大多数现代…

快速上手LangChain(一)

文章目录 LangChain一、背景二、什么是langchain三、Components 组件Prompt templates 提示模板Chat models 聊天模型Messages 消息Document loadersText Spltters 文本分割Vectorstores 向量数据库 四、langchain基础概念Tool calling 工具调用&#xff08;1&#xff09; 工具…

大模型Weekly 03|OpenAI o3发布;DeepSeek-V3上线即开源!

大模型Weekly 03&#xff5c;OpenAI o3发布&#xff1b;DeepSeek-V3上线即开源&#xff01;DeepSeek-V3上线即开源&#xff1b;OpenAI 发布高级推理模型 o3https://mp.weixin.qq.com/s/9qU_zzIv9ibFdJZ5cTocOw?token47960959&langzh_CN 「青稞大模型Weekly」&#xff0c;持…

记录一下图像处理的基础知识

记录一下自己学习的图像处理的基础知识。 一、图像的文件格式以及常用的图像空间 1、文件格式 常见的图像文件格式有 jpg, png, bmp, gif &#xff08;1&#xff09;jpg&#xff1a;有损压缩算法&#xff0c;大幅减小文件大小&#xff0c;便于存储和传输&#xff0c;兼容性…

时间敏感网络中全面分析与调度的模型驱动方法

论文&#xff1a;A Model-Driven Approach for the Comprehensive Analysis and Scheduling in Time-Sensitive Networks》 背景与动机 TSN 的发展与应用领域&#xff1a;自 2012 年起&#xff0c;IEEE 802.1 TSN 任务组致力于开发通信标准&#xff0c;增强 IEEE 802 网络&…

QT---------GUI程序设计基础

代码UI化设计&#xff08;QT&#xff09; 实例功能概述 假设我们要创建一个简单的计算器应用程序。该应用程序具有以下功能&#xff1a; 包含数字按钮&#xff08;0-9&#xff09;、操作符按钮&#xff08;、-、*、/&#xff09;、等于按钮&#xff08;&#xff09;和清除按…

大模型提示词初探

大模型提示词初探 在与大模型交互的过程中&#xff0c;提示词起着至关重要的作用&#xff0c;它犹如给模型下达的精准任务指令&#xff0c;直接影响着模型生成内容的准确性、高效性与合理性。合理运用提示词&#xff0c;能够有效减少模型出现错误和幻觉的情况&#xff0c;从而…

Navicat 连接 SQL Server 详尽指南

Navicat 是一款功能强大的数据库管理工具&#xff0c;它提供了直观的图形界面&#xff0c;使用户能够轻松地管理和操作各种类型的数据库&#xff0c;包括 SQL Server。本文将详尽介绍如何使用 Navicat 连接到 SQL Server 数据库&#xff0c;包括安装设置、连接配置、常见问题排…

【Linux】进程间通信-> 共享内存

共享内存原理 在C语言/C中&#xff0c;malloc也可以在物理内存申请空间&#xff0c;将申请的物理内存空间通过页表映射到进程地址空间&#xff0c;将内存空间的起始地址&#xff08;虚拟地址&#xff09;返回&#xff0c;进而进程可以使用虚拟地址通过页表映射到物理内存的方式…

高仿CSDN编辑器,前端博客模板

高仿CSDN编辑器纯前端模板&#xff0c;使用的js、html、vue、axios等技术&#xff0c;网络请求库已进行封装&#xff0c;可以按需调整界面,需要源码联系(4k左右)。 1.支持代码高亮 2.支持目录点击定位 3.支持文件上传、图片上传&#xff08;需要自己写后端接口&#xff09; 4.M…

国产低代码框架zdppy开发笔记002 标准的接口响应

前言 通过前面的学习, 我们已经知道了zdppy_api和zdppy_req的基本用法, 接下来我们会在学习中多次用到这两个框架. 我们已经知道了该如何响应一个字符串,但是我们该如何响应json数据呢? 在zdppy_api中,我们定义了一组规范的API响应, 我们慢慢来看看. 规范的响应 首先来看…