PostgreSQL 以其稳定性而闻名,这毋庸置疑。但这并不意味着它永远不会崩溃。虽然 PostgreSQL 通常会自行清理,但在崩溃后它并没有很好的清理方法(毕竟它丢失了内存)。因此,你的数据目录中可能会出现一些孤立的文件。如果这些文件很小,你可能不会担心。但有时你的数据目录中可能会出现很多垃圾文件。众所周知,解决这个问题非常困难,所以我决定写一篇文章来解决这个问题。
PostgreSQL 为何会崩溃?
如果本文标题让您对 PostgreSQL 产生不好的印象,我会尽力消除您的这种感觉。PostgreSQL 很少崩溃。但有时崩溃并非 PostgreSQL 的错。让我列举一些可能导致崩溃的原因:
- 你没有禁用内存过量提交,导致内存不足,并且 Linux 内核的内存不足终止程序向其中一个 PostgreSQL 进程发送了 SIGKILL
- 包含的文件系统pg_wal已满, PostgreSQL 无法创建新的WAL段
- PostgreSQL 进程在“关键部分”(修改数据但尚未达到一致性)遇到无法处理的错误 — PostgreSQL 将此类错误升级为PANIC
- 内存、磁盘或其他硬件组件故障导致数据损坏
- 管理员无意中损坏了数据库
- PostgreSQL 软件错误导致崩溃或数据损坏(您是否正在运行不受支持的版本或未更新最新的次要版本?)
事实上,导致崩溃并留下大量孤立文件的最常见原因是用户运行VACUUM (FULL)。此命令会创建表及其所有索引的副本。如果磁盘空间不足,而 PostgreSQL 无法创建新的WAL段,数据库服务器就会崩溃。这样的崩溃会留下未完全创建的表副本。为了避免此类问题,我建议您将pg_wal和数据目录放在不同的文件系统上(同时,也请将日志文件放在单独的文件系统上)。
为什么崩溃会留下孤立文件?
您可能知道 PostgreSQL在崩溃时无法清理,但为什么它在重启时无法清理呢?对此我有两个答案:
- 遍历所有数据库目录中的所有文件并将其与目录数据进行比较,这pg_filenode.map可能需要很长时间,在此期间服务器尚不可用
- 目录表中的数据损坏可能导致 PostgreSQL 删除合法数据文件,这将加剧数据损坏
我认为第二点是放弃任何自动清理活动的一个很好的理由。
崩溃后如何识别并删除孤立文件?
这是关键问题。我提供两种方法:
安全的方法:转储并恢复
处理该问题的简单、安全且受支持的方法是:
- 停止应用程序
- pg_dump数据库到磁盘
- DROP DATABASE— 这将删除所有文件,包括孤立文件
- 创建一个新的空数据库
- 将转储恢复到新数据库
- 启动应用程序
顺便说一句,这也是清理经历过任何形式数据损坏的数据库的正确方法。
虽然这种方式安全又简单,但清理大型数据库可能会导致严重的宕机。如果您觉得无法承受,并且不想采取下文所述的风险路径,那么您应该接受这些孤立文件。它们不会造成任何损害,只会占用部分磁盘空间,而且如今磁盘空间通常比长时间宕机要便宜得多。
危险的方法:识别并删除孤立文件
有几个PostgreSQL 函数允许超级用户检查文件系统:
精选 PostgreSQL 文件信息函数
| 功能 | 描述 |
|---|---|
| pg_ls_dir(dirname) | 列出数据库服务器上的目录,返回一个表text |
| pg_stat_file(filename) | 返回包含数据库服务器文件各种信息的复合记录,包括大小和修改时间戳 |
| pg_filenode_relation(tablespace, filenode) | 返回属于数据库目录中某个文件的关系(表、索引、序列等)的对象 ID(如果不存在这样的关系,则返回 NULL) |
通过了解 PostgreSQL 的内部结构,我们可以编写一个针对数据库目录中所有孤立文件的查询:
WITH d AS (
/* default tablespace directory for the database */
SELECT current_setting('data_directory') ||
'/base/' ||
(SELECT oid FROM pg_database
WHERE datname = current_database()) AS path
)
SELECT split_part(dir.f, '.', 1) AS filenode,
/* relation size is the sum of all segments */
pg_size_pretty(sum(f.size)) AS size,
max(f.modification) AS last_modified
FROM d
/* all the files in the database directory */
CROSS JOIN LATERAL pg_ls_dir(d.path) AS dir(f)
/* file data like size and modification timestamp */
CROSS JOIN LATERAL pg_stat_file(d.path || '/' || dir.f) AS f
WHERE /* file name is digits only, optionally followed by a dot and more digits */
/* that means: a segment of the "main fork" of a relation */
f ~ '^\d+(\.\d+)*$'
/* PostgreSQL doesn't know a relation that belongs to the file */
AND pg_filenode_relation(0::oid, split_part(dir.f, '.', 1)::bigint::oid) IS NULL
GROUP BY filenode;
要删除查询返回的文件节点(假设我们得到 12345),请转到数据库目录:
SELECT current_setting('data_directory') ||
'/base/' ||
(SELECT oid FROM pg_database
WHERE datname = current_database());
并删除属于它的文件:
rm 12345*
关于删除孤立文件的警告
删除文件时要小心:
- 上述查询仅会识别默认表空间中的文件。如果您创建了其他表空间,则查询将无法在这些表空间中找到孤立文件。
- 不要删除最近的文件。并发事务可能已经创建了这些文件,而目录条目尚未对您的查询可见。
- 最好在崩溃发生后等待一两周。然后,仅删除查询返回的、在崩溃时最后修改的文件。
- 如果查询返回一些小文件,请不要删除它们。它们不会给您带来麻烦,也不值得冒险。
在删除数据目录中的任何文件之前,请确保您已做好备份。
结论
崩溃后清除孤立文件的安全方法是转储并恢复数据库。对于那些胆子更大的朋友,我提供了一个查询,可以帮助你识别这些孤立文件。
原文地址:https://www.cybertec-postgresql.com/en/orphaned-files-after-postgresql-crash/
原文作者:Laurenz Albe




