给fsync()惊喜PostgreSQL

由于需要,DBMS开发人员担心数据安全地落入永久存储中。 因此,当PostgreSQL社区发现内核处理I / O错误的方式可能导致数据丢失而没有将任何错误报告给用户空间时,引起了很多不满。 PostgreSQL执行缓冲的I / O的事实使问题更加严重,该问题不是Linux独有的,即使在那里也很难解决。

Craig Ringer于3月下旬首先向pgsql-hackers邮件列表报告了此问题 。 简而言之,PostgreSQL假定成功的fsync()调用指示自上次成功调用以来记录的所有数据已安全地传输到持久性存储中。 当缓冲的I / O写入由于硬件错误而失败时,文件系统的反应会有所不同,但是这种行为通常涉及删除相应页面上的数据并将其标记为干净。 因此,刚刚写入的读取块很可能会返回其他内容,但不会记录数据。

错误报告呢? 一年前,Linux文件系统,存储和内存管理峰会(LSFMM)峰会包括一个错误报告会议,在该会议中所有会议都被称为“混乱”。 错误很容易丢失,因此没有应用程序可以看到它们。 在开发周期中,4.13中包含的某些修补程序在某种程度上改善了这种情况(并且在4.16中进行了一些更改以进一步改善它),但是,有一些丢失错误通知的方法,如下所述。 如果这在PostgreSQL服务器上发生,则可能导致数据库自动损坏。

PostgreSQL开发人员不满意。 汤姆·莱恩 Tom Lane) 将其描述为“ 对核的大脑损害 ”,而罗伯特·哈斯 Robert Haas) 则称其为100%愚蠢” 。 在讨论开始时,PostgreSQL开发人员非常清楚地理解了内核应该如何工作:无法写入的页面应以“脏”状态(用于后续尝试)存储在内存中,并且相应的文件描述符应转换为永久错误状态,因此PostgreSQL服务器无法跳过该问题。

哪里出了问题


但是,即使在内核社区参与讨论之前,也很清楚,情况并不像看起来那样简单。 Thomas Munro Linux在这种行为上不是唯一的。 OpenBSD和NetBSD也可能不会报告用户空间中的写入错误。 而且,事实证明,PostgreSQL处理缓冲的I / O操作的方式使情况变得更加复杂。

Haas 已详细描述了此机制。 PostgreSQL服务器是一组进程,其中许多进程可以对数据库文件执行I / O。 但是, fsync()调用fsync()是在单个检查点(“检查点”过程)过程中处理的,该过程旨在使磁盘存储保持一致状态以从故障中恢复。 Checkpointer通常不会打开所有相关文件,因此通常必须在调用fsync()之前打开文件。 这就是问题所在:即使在内核4.13和更高版本中,checkpointer也不会看到打开文件之前发生的任何错误。 如果在调用open() checkpointer-a之前发生了问题,那么下一次对fsync()将返回成功。 有几种导致fsync()之外的I / O错误的方法; 例如,内核在执行后台写回操作时可能会遇到其中之一。 有人调用sync()可能还会遇到I / O错误,并“吸收”结果错误状态。

Haas将这种行为描述为无法满足PostgreSQL的期望:
您(或某人)拥有的一切基本上是未经证实的假设,即
哪个文件描述符可能与特定错误相关,但是碰巧PostgreSQL从未与之匹配。 您可以继续说问题出在我们的猜测上,但是在我看来,假设我们是唯一做到这一点的程序是错误的。

结果,Joshua Drake 将对话移到了ext4的开发列表中,包括内核开发社区的一部分。 Dave Chinner很快这种行为描述为“ 灾难秘诀,尤其是在跨平台代码中,在这种情况下,每个OS平台的行为都不同,并且几乎与预期不符。” 取而代之的是,Ted Tso 解释了为什么在发生I / O错误后将受影响的页面标记为干净的页面。 简而言之,最常见的I / O错误原因是用户在错误的时间弹出USB驱动器。 如果某个进程将大量数据复制到该磁盘上,则结果将导致脏页在内存中的积累,可能会导致系统没有足够的内存来执行其他任务。 因此,如果用户希望系统在此类事件后保持可用状态,则无法保存这些页面并将其清除。

Chinner和Tso以及其他人都说PostgreSQL有正确的解决方案-切换到直接I / O(DIO)。 通常,使用DIO可以更好地控制写回和I / O。 这包括访问有关哪些I / O操作可能失败的信息。 与其他许多PostgreSQL开发人员一样,Andres Freund也承认 DIO是最好的长期解决方案。 但是他还指出,不应期望开发人员会为执行此任务而投入很多精力。 同时,他还有其他程序(他提到了dpkg)也很容易出现这种现象。

寻求短期解决方案


在讨论过程中,人们极大地关注了写入失败应导致受影响的页将以其脏状态存储在内存中这一事实。 但是PostgreSQL开发人员迅速放弃了这个想法,并没有要求它。 最终,他们真正需要的是一种可靠的方法来找出是否出了问题。 考虑到这一点,通常的PostgreSQL错误处理机制可以解决这个问题。 但是,在他缺席的情况下,无能为力。

在讨论的某个时刻,左宗棠提到 Google拥有自己的I / O错误处理机制。 指示内核通过netlink套接字报告I / O错误。 专用进程接收这些通知并做出相应响应。 然而,这种机制从未在入口处做到这一点。 Freind 指出 ,这种机制对于PostgreSQL是“理想的”,因此它可能会在不久的将来出现在公共领域。

同时,Jeff Leighton在考虑另一个想法:在发生I / O错误时在文件系统的超级块中设置一个标志。 然后,调用syncfs()将清除此标志并返回一个错误(如果已设置)。 PostgreSQL检查syncfs()可以定期调用syncfs()来轮询包含数据库的文件系统上的错误。 弗洛因德(Freund) 同意这可能是解决该问题的可行方法。

当然,任何这样的机制都只会出现在新内核中。 同时,PostgreSQL安装通常在企业发行版支持的较旧内核上运行。 在这些内核中,似乎甚至没有4.13中包含的那些改进。 对于这些系统,几乎没有什么能帮助PostgreSQL检测I / O错误。 启动一个守护程序来扫描系统日志并在其中查找I / O错误消息可能就足够了。 通常,这不是最优雅的解决方案,而且由于不同的块驱动程序和文件系统通常以不同的方式报告错误,这使情况变得复杂,但这可能是最好的选择。

下一步可能是4月23日举行的LSFMM 2018活动上的讨论。 如果幸运的话,将会有一些对感兴趣的参与者有用的解决方案。 但是,不会改变的一件事是简单的事实,即错误处理很难正确进行。

Source: https://habr.com/ru/post/zh-CN472684/


All Articles