Canonical、Elastic 和 Google 联手防止 Linux 中出现数据损坏
在 Elastic,我们一直在不断创新并发布新功能。 在我们发布新功能的同时,也在努力确保让这些功能经过测试,并达到安全、稳定和可靠的运行状态,但有时确实会出现这样或那样的问题。
在测试一项新功能时,我们发现了一个 Linux 内核故障,会影响到某些 Linux 内核上的 SSD 磁盘。在这篇博客文章中,我们将介绍这项调查的来龙去脉,以及我们是如何与 Google Cloud 和 Canonical 这两个密切合作伙伴携手展开调查的。
调查结果是发布了新的 Ubuntu 内核来解决这个问题。
调查的来龙去脉
早在 2019 年 2 月,我们使用标准的 Elasticsearch 基准测试工具 Rally,对跨集群复制 (CCR) 进行了为期 10 天的稳定性测试,期间使用的是更新量繁重的采集工作负载。Elasticsearch 版本是(当时)尚未发布的 7.0 分支提交的最新版本。几天后,测试失败,Elasticsearch 日志报告显示:
… Caused by: org.apache.lucene.index.CorruptIndexException: checksum failed (hardware problem?)
一旦检测到[1]索引损坏,Elasticsearch 将尝试从副本中恢复。在许多情况下,恢复是无缝进行的,除非用户查看日志,否则不会注意到损坏。即使副本提供了冗余,在某些情况下也可能造成数据丢失。因此,我们希望了解损坏发生的原因,并探究如何补救这一问题。
起初,我们怀疑是 CCR/软删除这项新的 Elasticsearch 功能导致了这个问题。但这种怀疑很快就被打消了,因为我们发现在使用单个集群并禁用 CCR/软删除功能的情况下也可以重现索引损坏问题。经过进一步的研究,我们发现,在 CCR/软删除公测版启动之前的 Elasticsearch 早期版本中,我们可以重现这个问题,这说明 CCR/软删除并不是导致索引损坏的原因。
不过,我们也确实注意到,出现索引损坏的测试和研究是在 Google Cloud Platform (GCP) 上进行的,使用的是 Canonical Ubuntu Xenial 映像和本地 SSD,内核为 4.15-0-*-gcp
,并且我们无法在具有相同操作系统和 SSD 磁盘的裸机环境(使用 4.13 或 4.15 HWE 内核)中重现这一问题。
接下来我们的注意力立即转向了 Elastic Cloud,希望获取有关该问题及其影响的更多数据。在并行工作流中,我们通过测试不同的文件系统、禁用 RAID,最后使用较新的主线内核,排除了许多疑点,但这些都不能避免发生损坏。
在 Google Cloud 的支持下进一步挖掘
此时,我们创建了一套简单的复制脚本来简化复制,并与我们的合作伙伴 Google 一起开始深入研究环境问题。
GCP 支持人员能够使用提供的脚本可靠地重现问题,并提供了一条错误消息:
org.apache.lucene.index.CorruptIndexException: codec footer mismatch (file truncated?): actual footer=-2016340526 vs expected footer=-1071082520 (resource=BufferedChecksumIndexInput(MMapIndexInput(path="/home/dl/es/data/nodes/0/indices/IF1vmFH6RY-MZuNfx2IO4w/0/index/_iya_1e_Lucene80_0.dvd")))
在问题发生的时间段内,IO 吞吐量超过 160MB/s,IOPS 为 16,000。在好几次测试中都出现了上述观察结果。由于 Elasticsearch(默认情况下)使用内存映射文件来存储部分内容,因此,我们怀疑某些文件访问导致了更多严重的页面错误,进而导致磁盘的 IO 操作量增加,并最终触发了该问题。为了减少页面错误的发生,我们尝试将 GCP 实例的内存从 32GB 增加到 52GB。随着内存的增加,这个问题不再出现,IO 吞吐量为 50MB/s,IOPS 为 4,000。
当我们观察到 GCP 和裸机环境之间的差异时,这个问题首次有了突破:GCP 内核启用了一个叫作多队列块层 (blk_mq) 的功能,而裸机内核没有[2]。更复杂的是,在特定版本[3]之后,无法再通过内核选项在 Ubuntu Linux -gcp 映像上禁用 blk_mq[4]。GCP 支持人员向我们展示了如何通过导出 GCP 的 Ubuntu 映像并在不使用 VIRTIO_SCSI_MULTIQUEUE guestOS 功能(该功能可启用多队列 SCSI[5])的情况下,重新创建它来禁用 blk_mq。
第二个突破是设法在裸机上重现损坏:只有通过显式启用 blk_mq
,甚至使用旧的内核 4.13.0-38-generic
,才有可能实现。我们还验证了 NVMe 磁盘,确认不会显示此问题。
至此,我们知道,当同时满足以下两个条件时,就会发生损坏:
- SSD 驱动器使用 SCSI 接口(NVMe 磁盘不受影响)
- 启用了 blk_mq
GCP 支持人员分享了(除了仅使用 NVMe 磁盘外的)另外两种解决方法:增加实例内存或在禁用多队列 SCSI 的情况下创建自定义实例映像。
Canonical 加入了我们的调查行列
尽管我们有了一些解决方法,但我们仍不满意:
- 这个问题并不是 GCP 上的 Ubuntu 映像特有的问题,它也发生在裸机上。
- 我们不知道是哪个内核提交导致了这个问题。
- 我们不知道较新的内核中是否已经有了修复程序。
为了解决这些问题,我们联系了我们的合作伙伴 Canonical 一道进行深入的研究。
Canonical 使用 Elastic 复制脚本开始了大规模的测试工作,首先确认了在使用 SSD 驱动器(不使用或使用 mq-deadline 多队列 I/O 调度程序)且版本 >=5.0 的 Ubuntu 主线内核上没有发生损坏。
下一步是倒推内核版本,找出出现损坏的内核和没有损坏的内核之间的最小增量。通过使用多个并行测试环境(因为整个测试运行最多可能需要五天),Canonical 发现 4.19.8 是第一个包含损坏修复程序的 Ubuntu 主线内核[6]。
Canonical 的故障跟踪器在 LP#1848739 下描述了 4.15.0 内核及其派生版本缺少的反向端口,更多细节可以在本文和 kernel.org bug 中找到。
在 Elastic 和 Canonical 确认打已修补的 GCP 内核(包含所有必要的反向端口)修复这个问题之后,它们被合并到 Ubuntu 4.15.0 主内核中,因此,所有派生内核(包括 -gcp)都收到了修复程序。
结论
Elastic 始终致力于开发新的 Elastic Stack 功能,不断改进我们的三大主要解决方案。这些努力得到了一些才华横溢的工程师和合作伙伴的鼎力支持,我们时刻保持警惕,所以您不必过于担心。如果我们在测试过程中发现问题,请记住,Elastic 及其密切的合作伙伴网络将不遗余力地查证问题,确保您拥有最好的体验。
通过与 Google 和 Canonical 的密切合作,我们得以弄清问题的真相,从而发布了以下装有修复程序的 HWE Ubuntu 内核:
- AWS:从
linux-aws - 4.15.0-1061.65
开始,发布于 2020 年 2 月 21 日 - Azure:从
linux-azure - 4.15.0-1066.71
开始,发布于 2020 年 1 月 6 日 - GCP:从
linux-gcp - 4.15.0-1053.57
开始,发布于 2020 年 2 月 5 日 - 常规:从
linux - 4.15.0-88.88
开始,发布于 2020 年 2 月 17 日
如果在启用 SCSI blk-mq 的情况下使用 SSD 磁盘,使用上述或更高版本可避免发生损坏。
如果您想保护自己的环境不受数据损坏,那么请试试我们的 Elastic Cloud 吧,我们的用户已经受到很好地保护了。
脚注
[1] Elasticsearch 不会一直验证校验和,因为这是一项昂贵的操作。某些操作可能会更频繁地触发校验和验证,例如分片重定位或在拍摄快照时,使潜在的静默损坏看起来好像是由这些操作引起的。
[2] 要检查 blk_mq 是否正在用于 SCSI 或设备映射器磁盘,请使用 cat /sys/module/{scsi_mod,dm_mod}/parameters/use_blk_mq
[3] 在 https://patchwork.kernel.org/patch/10198305 之后,blk_mq 被强制用于 SCSI 设备,并且无法通过内核选项禁用。这个补丁在 Ubuntu linux-gcp 上进行了向后移植。
[4] 要禁用 blk_mq,需要通过 grub 将以下参数传递给内核:GRUB_CMDLINE_LINUX="scsi_mod.use_blk_mq=N dm_mod.use_blk_mq=N"
。可以通过将这些选项设置为 N 来启用,但要注意 [3]。
[5] 通过重新创建 Ubuntu 映像来禁用 VIRTIO_SCSI_MULTIQUEUE guestOS 功能的示例 gcloud 命令:
# gcloud compute images export --image-project ubuntu-os-cloud --image-family ubuntu-1604-lts --destination-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz # gcloud compute images create ubuntu-1604-test --family ubuntu-1604-lts --source-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz
[6] 反向端口
- blk-mq: quiesce queue during switching io sched and updating nr_requests - blk-mq: move hctx lock/unlock into a helper - blk-mq: factor out a few helpers from __blk_mq_try_issue_directly - blk-mq: improve DM's blk-mq IO merging via blk_insert_cloned_request feedback - dm mpath: fix missing call of path selector type->end_io - blk-mq-sched: remove unused 'can_block' arg from blk_mq_sched_insert_request - blk-mq: don't dispatch request in blk_mq_request_direct_issue if queue is busy - blk-mq: introduce BLK_STS_DEV_RESOURCE - blk-mq:Rename blk_mq_request_direct_issue() into blk_mq_request_issue_directly() - blk-mq: don't queue more if we get a busy return - blk-mq: dequeue request one by one from sw queue if hctx is busy - blk-mq: issue directly if hw queue isn't busy in case of 'none' - blk-mq: fix corruption with direct issue - blk-mq: fail the request in case issue failure - blk-mq: punt failed direct issue to dispatch list