更新 WAL 基础设施
本页面列出了一些关于改进我们使用 WAL 的想法,包括更新磁盘格式,该格式自 2014 年底(9.5 版本发布)的 WAL 格式大一统以来一直没有修改,更新记录什么内容以及何时记录的规则,以及如何运行恢复。
本页面提到的领域截至 2023 年 5 月,处于 PostgreSQL 16 的 Beta 窗口期。
从格式中移除未使用的(或无用的)字节
主要的 WAL 结构包含可能经常未使用或无用的字节。
XLogRecord 中的对齐损失
XLogRecord 结构目前包含 2 字节的对齐损失。我们已经抱怨在某些记录中添加了 4 字节,那么为什么我们不解决这个问题呢?
许多记录中 XLogRecord 的事务 ID 未使用
所有核心索引都不使用 XLogRecord 中的事务 ID,许多 RMGR 也是如此。如果它没有被使用,我们不应该一直记录这 4 个字节。一旦 64 位 XID 落地,这些字节将在每个记录中变成 8 字节 - 我宁愿阻止这种情况发生。
XLogRecord 的平均长度 <<< 2^16
任何 WAL 记录的完整大小很少超过 2^16,并且通常小于 255B;但是存储记录长度的字段 xl_tot_len
目前是 uint32
,通常会留下 2 或 3 个字节的字段未使用。如果我们使用可变长度编码(或类似的东西)来表示该字段,我们可以节省一些字节。
块数据可能经常为 0,或至少 < 2^8
注册到 xlog 记录的块不需要包含太多数据,或者根本不需要包含数据,但我们仍然存储一个 uint16 来表示记录包含多少数据,无论数据是否存在。我们可以经常节省一个字节,有时可以节省 2 个字节,通过使用指标位在例如块 ID 的上部分对字段进行 var 编码。
- 参见 Commitfest补丁
- 参见 -hackers 邮件线程
提高压缩效率
在YouTube 上的这个讨论中,Andrey M. Borodin 提到了 WAL 中多个全页图像的压缩可能可以通过使用单个压缩流来获益,因为大多数记录的 FPI 共享(一些)结构和数据。目前,压缩是在每个页面基础上进行的,但一次压缩更多页面可能会在整体上节省一些字节。
减少脏页面的影响,同时防止撕裂写入
一些 WAL 记录,例如堆表访问方法使用的 VACUUM 记录和来自 Generic RMGR 的“通用 WAL 记录”,不会修改页面布局,而只会修改页面中预定位置的字节。我们把这些修改称为“精确”。
FPI 主要用于对抗撕裂写入,即由于意外的快速关闭,页面中只有一部分更改被持久写入磁盘。但是,如果你有一个页面的“精确”修改,你就不需要在 WAL 中记录一个全页图像,因为如果你 _总是_ 重放“精确”修改,任何撕裂写入(即应该更新的旧页面的字节)都会被新的字节覆盖,并且无论原始修改是什么,页面的最终状态都是相同的。
这一发现可以帮助显着减少真空和冻结的 WAL 压力;这些工作负载通常会产生大量的 WAL 开销,同时也能产生对页面上修改内容的精确指令。
请注意,为了使修改成为“精确”的,必须定义系统规则,以防止意外地以不可恢复的方式覆盖新数据等。要发现并记录使用这种方案可以在页面上修改哪些内容以及哪些内容不能修改的精确规则,但首先,这是一个很好的可能途径,可以减少 PostgreSQL 的 WAL 体积。
WAL 的并行重放
- 主页面:并行恢复
并非所有 WAL 都可以并行重放,但我们可能能够并行重放一些 WAL。并行重放 WAL 可以加快恢复速度并减少读副本延迟峰值:主实例可以使用许多并行进程生成 WAL,但副本目前仅限于一个进程进行恢复。通常,一个线程就足够了,但在某些情况下,副本无法赶上主实例,导致性能永久下降。