我们愿意相信每个数据库从一开始就配置良好,具有最佳日志轮换、高 CPU 消耗的正确警报和缓存命中率监控。但情况并非总是如此。数据库中可能发生的一件令人沮丧的事情与性能和查询无关。相反,它的磁盘空间不足,无法存储数据。今天,我们将深入探讨一些可以采取的良好做法,以帮助防止此类情况发生。
任何计划外数据库中断的最终目标是减少数据丢失。因此,这里的建议针对的是在生产情况下优先考虑最大程度地减少数据丢失的人。
备份:最重要的事情
可以为任何数据库系统做的最重要的事情是进行备份。备份允许从各种情况中恢复,并在磁盘用完时提供安全网。我们是pgBackRest 的忠实粉丝,它提供了一种为 PostgreSQL 创建托管备份存储库的方法,其中包含许多效率和安全设置。
在将 Postgres 集群部署到生产中时,应该始终有一个备份策略。这包括评估以下内容:
- 备份所在的位置。确保它们位于与 Postgres 数据库不同的磁盘上,或者位于像 S3 这样的 blob 对象存储系统中。
- 备份保留策略是什么。通常将备份保留 7 到 21 天。分阶段备份方法也是明智的。例如,有 2 周的每日备份,然后每 2 周有 1 次备份,最多 6 个月,然后是过去 3 年的每月 1 次备份。
- 多久进行一次备份。这将与 RPO / RTO 一致。
- 如何监控备份。确保备份存储库是健康的,以防需要在灾难场景中使用它!
- 定期测试备份并进行测试还原。
拥有一个健康的备份系统为从错误(例如磁盘不足)中恢复提供了更多帮助。我们还应该注意到,所有 Crunchy 产品都带有内置的备份服务。
完整磁盘
如果 Postgres 的磁盘空间不足,则需要查找一些内容。根据服务器配置,可能有多个设备在运行,因此数据目录、表空间、日志或 WAL 目录都可能受到影响。
磁盘已满的原因有很多,以下是一些常见的原因:
- archive_command 失败,WAL 正在填满磁盘空间。
- 断开连接的备用数据库有复制槽,这会导致 WAL 填满磁盘。
- 大型数据库更改会生成如此多的 WAL,以至于它消耗了所有可用的磁盘空间。
- 实际上只是用完了存储数据的磁盘空间,而正常监视器和警报并没有让你知道。
对整个数据库系统最不利的情况是一个完整的 WAL 目录。这导致数据库无法对数据库系统进行更多更改,因为它无法记录 WAL 更改。然后Postgres别无选择,只能发出PANIC并关闭。
其中许多情况与 WAL 填充磁盘有关。关闭数据库会让你陷入危险境地,所以这就是我们将在这篇文章中深入探讨的地方。
Postgres WAL 归档工作原理的简要概述:
- 当 Postgres 需要对数据进行更改时,它首先在预写日志 (WAL) 中记录此更改,并将 WAL 同步到磁盘。Fsync 是一个系统调用,它保证写入的数据实际上已经完全写入持久存储,而不仅仅是存储在文件系统缓存中。一旦这个 WAL 成功写入磁盘,即使数据库在这次写入和对数据目录的实际更改之间崩溃,数据库也能够通过重放 WAL 中存储的更改来恢复到一致状态。
- 在未来的某个时刻,数据库将发出 CHECKPOINT,它将所有修改过的缓冲区刷新到磁盘并将更改永久存储在数据目录中。当 CHECKPOINT 完成后,数据库会记录发生这种情况的 WAL 位置,并知道到目前为止所有这些数据都已成功写入磁盘。(值得注意的是,任何通过 CHECKPOINT 写入磁盘的更改都已经在 WAL 流中记录了其内容,因此这只是确保 WAL 数据记录的更改实际上应用于实际的一种方式磁盘上的关系文件。)
- WAL 以 16MB 的块存储在磁盘文件中;当数据库生成 WAL 文件时,它会在它们上运行 archive_command 来处理将这些存储在数据库之外。archive_command 的退出代码将决定 Postgres 是否认为这已经成功。一旦 archive_command 成功完成,Postgres 就会认为 WAL 段已被成功处理,并且可以将其删除或回收以节省空间。如果 archive_command 失败,Postgres 会保留 WAL 文件并无限期地再次尝试,直到此命令成功。即使特定的段文件失败,Postgres 也会继续为所有其他数据库更改累积 WAL 文件,并继续重试在后台归档前一个文件。
如您所见,如果 archive_command 存在问题,这可能会导致 WAL 中的堆积,因为 Postgres 将不会删除任何段,直到它们被 archive_command 确认接收。
当 Postgres 服务器启动时,它会查看 pg_control 文件以确定最后一个检查点是什么时候,并将重放自上次检查点以来在数据库中生成的所有 WAL。这确保数据库将达到一致的状态,该状态将反映 WAL 中所做和记录的所有数据更改。
WAL 文件按顺序命名,文件名的多个部分根据数据库中所做更改的顺序定义。您可以将整个数据库更改历史视为从一开始就播放所有 WAL 文件。然而,由于随着时间的推移会有太多的变化需要重放,Postgres 使用 CHECKPOINTs 作为一个基本点来确定删除/回收 WAL 文件并从这一点提取是安全的。
就像失败的 archive_command 可以无限期地保留 WAL 文件一样,由于复制槽使用者离线而未更新的复制槽将阻止 WAL 被删除并成为填充磁盘的向量。PostgreSQL 13 以后可以通过使用 max_slot_wal_keep_size 设置来防止这种特定情况。这样做的好处是限制了 WAL 将占用的磁盘数量(从而防止主服务器因存储不足而关闭),同时权衡如果您做达到此限制,副本将需要被重建,因为主要已经删除了它前进所需的 WAL。
破碎的档案
一个常见的情况是 archive_command 返回错误时出现问题。如果这没有得到充分监控,您可以继续一段时间,pgBackRest 返回错误,最终 pg_wal 目录填满。
archive_command 可能因不同原因而失败;它可能是 pgBackRest 存储库上的磁盘空间不足,ssh 密钥可能已更改,pgBackRest 版本可能已在存储库服务器上更新而主数据库服务器上没有相应的更新,或许多其他可能性。这里唯一要做的就是确保您正在监视日志,特别是针对 archive_command 失败的日志,因为这通常表示必须由您的 DBA 团队或 Crunchy 解决的问题。
当 pg_wal 磁盘填满时,这意味着没有更多空间来写入额外的更改,因此 Postgres 做了它唯一能做的事情:恐慌并终止服务器和所有连接;对于无法进行更多更改的数据库没有太大用处。
重要的是要记住,即使 Postgres 由于 pg_wal 磁盘已满而关闭并且不会再次启动,此时还没有损坏的数据库,仍然可以采取一些措施来启动数据库,而不会丢失或损坏数据。
什么不该做:
- 永远不要删除 WAL。
看到 WAL 填满磁盘空间的常见下意识反应是删除这些日志文件。这是系统管理员非常常用的方法 - 大日志文件填满磁盘,摆脱它们,对吗?可是等等 …。WAL 不仅仅是普通的系统日志。它们是启动和运行 Postgres 不可或缺的一部分,还记得上面的检查点吗?删除 WAL 将损坏数据库。
请记住,一旦 Postgres 运行正常,并且它已经验证不再需要这些文件(即,它确认它已成功归档文件),它本身将删除额外的 WAL 文件。
如果您删除 WAL 文件,您可以保证您的数据库将处于不一致状态并且会被损坏。永远不要删除 WAL 文件!
删除 WAL 的唯一时间是在专家的特定指导和指导下。我们是否曾经删除过 WAL 文件并将它们复制到其他地方?当然,是的,我们有。但这是在专家情况下,我们已经对系统进行了备份,并且可以根据需要回滚到已知状态。
- 不要立即使用备份还原覆盖现有数据目录。
在从备份恢复的最佳情况下,已经确定可以接受数据丢失,因为实际上选择放弃自上次备份以来的任何数据库更改。从备份恢复是一种灾难恢复方法,通常在整个系统无法运行或数据文件已损坏的情况下很有帮助;即,真正的灾难。
特别是,如果有一个失败的 archive_command,这意味着你将放弃自上次成功备份以来数据库中发生的所有事务。这是关键:如果 archive_command 失败,并且这也是您用于基本备份的内容,则无法保证上次备份的时间。(您正在监控您的备份和存档命令,对吗?)
如果需要,可以进行备份恢复,但这不应该是您发现问题时首先去的地方。
- 不要只是就地调整大小。
随着生产的下降,你显然会渴望尽快修复此问题。虽然你对磁盘用完的下意识反应是添加更多存储空间,但通常最好配置一个更大的新实例并在那里进行恢复,而不是尝试就地调整大小。首先也是最重要的是,我们希望将损坏的实例保留在原位,以便我们以后可以参考它;在这一点上,可能不清楚问题的真正原因是什么。保留任何已停止工作的生产实例是执行事后分析并对任何新工作系统的完整性充满信心的最佳方式。
注意:这是一个比其他的更温和的“不要”;如果这是启动和运行数据库的唯一选择,那么这种方法应该可以正常工作。
你应该做什么:
- 立即进行文件系统级备份。
在执行任何操作之前,请确保 Postgres 已停止并备份 PostgreSQL 数据目录(包括 pg_wal 目录和任何非默认表空间),以便在需要时可以恢复到此状态。修复 Postgres 和您的 WAL 存档时可能会出现很多不同的问题,因此能够保留尽可能多的原始证据/状态既可以在重建过程中保护您,也可以为确定根目录提供有价值的取证数据原因。
任何处理这种情况的方法都是可能的;例如 pgBackRest 支持离线备份,您可以使用文件系统级快照、rsync 到远程服务器、tarball 等。
- 创建一个具有足够空间的新实例(或至少一个新卷)。
我们建议尽可能还原到新实例。如果能够使用刚刚创建的备份,则可以在确保新实例上的所有配置、路径等正确无误后测试启动它。如果您在新实例上安装,还要确保使用相同的 Postgres 版本(包括其他包和扩展),并且区域设置具有相同的设置。
现在您已经消除了空间问题,您应该能够恢复数据库操作。
- 解决根本问题。
现在数据库已备份并运行,请查看日志以了解失败的原因,并修复潜在问题。现在添加/调整您的监控,以便您将来能够检测和防止此问题。例如,如果这是由失败的 archive_command 引起的,您可以使用一些日志分析工具来通知您是否再次发生此类事情。Crunchy 推荐 pgMonitor,它可以检测失败的 archive_command,并在磁盘百分比达到某个阈值时发出警报。
在这里总结一下,请不要删除您的 WAL。如果您的磁盘已满,Postgres 有工具可以帮助您快速有效地恢复。




