聊聊数据库的孤儿文件问题
前段时间在灿灿的微信公众号(PostgreSQL学徒)上看到了PG数据库的孤儿文件的问题,文章名为《空前都去哪里了?》,讨论的是PostgreSQL数据库的孤儿文件的发现与清理的问题。PostgreSQL数据库的孤儿文件问题是指在数据库中存在一些没有被任何表或索引引用的文件,这些文件占用了磁盘空间,但是无法被清理或删除。这种情况可能是由于一些特殊的原因造成的。比如数据库崩溃或异常终止,导致文件系统和数据库之间的数据不一致;数据库升级或迁移时,没有正确地删除旧版本的文件等等。
其实孤儿文件问题不只是PostgreSQL才有的问题,一些开源数据库,比如MongoDB,也会存在类似的问题。当分片集群中的数据块迁移失败或者清理不完全时,就可能导致一些文档在多个分片上重复存在,这些文档被称为孤儿文档。为了解决这个问题,MongoDB提供了一个cleanupOrphaned命令,可以删除指定集合的孤儿文档。
MySQL、Oracle等使用表空间段页模式的数据库虽然不会产生孤儿文件,但是可能会产生表空间碎片或者垃圾数据段。因此这些数据库也需要通过表或者表空间的碎片整理来解决数据空间被垃圾占用的问题。
早年我们运维Oracle数据库的时候就经常会发现某个表空间中没多少业务数据,但是已经无法分配空间了。分析之后发现是因为在很多perment的表空间中有很多大型的临时段。
这是因为数据库在执行一些大规模的操作,如创建索引、CTAS、分区交换等,这些操作会在perment表空间中创建temporary segments,如果操作没有正常结束,这些临时段就变成“孤儿空间”存在于表空间中了。
如果SMON进程没有及时清理,就会导致这些temporary segments残留在perment表空间中,引起用户表无法扩展的问题。一些经验不足的DBA还容易把这个问题误判为临时空间不足。这个问题在Oracle 10.2之前的版本中出现的比较频繁,不巧的是那时候的存储资源也比较金贵,给数据库分配的存储空间十分有限。一旦出现了PERMENT表空间中的临时段没有及时清理,就容易出现存储空间爆掉的问题,引起应用故障。为此Oracle 从8.0开始提供了DROP_SEGMENTS事件,用于强制清理临时段: alter session set events 'immediate trace name DROP_SEGMENTS level TS#+1'。
Oracle数据库的临时段垃圾是很讨厌的,早些年存储系统的性能极差,遇到此类问题的时候,还会引发数据库启动关闭变得很慢,因为在启动关闭数据库的时候,清理临时段都是标准操作。我曾经遇到过一个用户在某次例行重启数据库的时候,因为临时段清理过慢而认为数据库死掉了,反复重启了数个小时才启动成功,差点影响了第二天营业厅开门。最后他们把例行运维时不许重启数据库写入了运维手册。随着Oracle数据库smon能力的提升以及现代硬件性能的提升,现在这种问题已经很少出现了,不过DROP_SEGMENTS这个事件至今还保留着。
PG数据库并没有直接提供工具来检查与清理孤儿文件,不过孤儿文件的问题还是需要引起我们注意的。看过灿灿的文章后,我就觉得应该在D-SMART里写一个工具来自动分析数据库中存在的孤儿文件问题。
根据灿灿文中介绍的方法,我们编写了一个孤儿文件检查工具(这个工具也将会发布在今年年底发布的D-SMART 2023年度版本的社区版中供大家免费使用)。
在我们D-SMART开发环境的PG数据库中,竟然也有1213个孤儿文件,虽然只占据了15GB的存储空间,不过大量的文件也会对文件系统的访问产生负面的性能影响。
检查工具会在D-SMART的/usr/software目录下生成一个orphan_file.sh脚本。用户将该脚本拷贝到目标数据库的服务器上,就可以完成清理工作。脚本在清理之前会将孤儿文件备份到/tmp/orphan_file目录下,然后再做清理工作。定期将因此使用该工具的时候,要定期将/tmp/orphan_file目录清理掉,避免tmp文件系统满了。
我一直不理解PG数据库为什么不像Oracle那样提供一个负责清理系统垃圾的smon进程,自动完成这些垃圾的清理工作。这其实是一个十分合理也不太难做的需求,把这些问题遗留给DBA来做不是一个好的想法。我们在编写这个工具的时候还是遇到了很多问题的,刚开始因为工具存在BUG,生成的清理脚本因为误删文件而引发了数据库丢失数据的问题。而如果数据库内核来提供一个自动清理机制,会完全得多。基于PG开源代码的国产数据库厂商是不是应该在这方面做些工作呢?这个工作做好了,既可以提升自己产品的能力,也可以将这项工作贡献给开源社区,促进PG开源社区的发展。