Table of Contents
本章解释预写式日志如何实现高效、可靠的运行。
可靠性是任何严肃数据库系统的重要属性,而 PostgreSQL 会尽一切可能保证运行可靠。可靠运行的 一个方面是,已提交事务所记录的所有数据都应存储在不受断电、操作系统故障和 硬件故障影响的非易失性区域中(当然不包括该非易失性区域本身的故障)。通常,只要 能成功将数据写入计算机的永久存储设备(磁盘驱动器或等效设备),就满足这一 要求。实际上,即使一台计算机遭受致命损坏,只要磁盘驱动器得以幸存,就可以将 它们移到另一台硬件相近的计算机上,所有已提交事务仍将完好无损。
虽然周期性地强制将数据写到磁盘盘片似乎是个简单操作,但事实并非如此。由于 磁盘驱动器远慢于主存和 CPU,在计算机主存与磁盘盘片之间存在多层缓存。首先是 操作系统的缓冲区缓存,它会缓存常用磁盘块并合并磁盘写入。幸运的是,所有 操作系统都为应用提供了强制将数据从缓冲区缓存写入磁盘的方法,而 PostgreSQL 会使用这些机制。(关于如何调整这一 过程,见参数 wal_sync_method。)
其次,磁盘驱动器控制器中可能还有缓存;这在 RAID 控制卡上 尤为常见。其中一些缓存是 直写式,也就是说写请求一到达 就立刻发送到驱动器。另一些是 回写式,也就是说数据会在 稍后的某个时间发送到驱动器。这类缓存可能构成可靠性隐患,因为磁盘控制器缓存 中的内存是易失性的,断电时会丢失内容。较好的控制卡配有 后备电池单元(BBU),也就是卡上 带有电池,可在系统断电时维持缓存供电。电力恢复后,数据便会写入磁盘驱动器。
最后,大多数磁盘驱动器自身也带有缓存。有些是直写式,有些是回写式;对于 回写式驱动器缓存,同样存在与磁盘控制器缓存相同的数据丢失隐患。消费级 IDE 和 SATA 驱动器尤其可能带有在断电后无法保留内容的回写式缓存。很多固态驱动器 (SSD)也带有易失性的回写式缓存。
这些缓存通常都可以禁用;不过,具体方法会因操作系统和驱动器类型而异:
在 Linux 上,可以使用 hdparm -I 查询 IDE 和 SATA 驱动器;如果 Write cache 旁边有一个 *, 就表示启用了写缓存。可以使用 hdparm -W 0 关闭 写缓存。SCSI 驱动器可以使用 sdparm 查询。使用 sdparm --get=WCE 检查是否启用了写缓存, 使用 sdparm --clear=WCE 将其禁用。
在 FreeBSD 上,可以使用 camcontrol identify 查询 IDE 驱动器,并通过在 /boot/loader.conf 中设置 hw.ata.wc=0 来关闭写缓存;SCSI 驱动器也可以使用 camcontrol identify 查询,并在可用时借助 sdparm 查询和修改写缓存。
在 Solaris 上,磁盘写缓存由 format -e 控制。 (Solaris 的 ZFS 文件系统在启用磁盘写缓存时仍然是 安全的,因为它会发出自己的磁盘缓存刷新命令。)
在 Windows 上,如果 wal_sync_method 是 open_datasync(默认值),可以通过取消勾选 My Computer\Open\ 来禁用写缓存。另一种办法是将 disk drive\Properties\Hardware\Properties\Policies\Enable write caching on the diskwal_sync_method 设为 fsync 或 fsync_writethrough, 这两者都会阻止写缓存。
在 macOS 上,可以通过将 wal_sync_method 设为 fsync_writethrough 来防止写缓存。
较新的 SATA 驱动器(遵循 ATAPI-6 或更高版本标准的那些) 提供了驱动器缓存刷新命令(FLUSH CACHE EXT),而 SCSI 驱动器则早已支持类似的 SYNCHRONIZE CACHE 命令。这些 命令对 PostgreSQL 并不直接可见,但某些文件系统 (如 ZFS、ext4)可以利用它们将 启用了回写缓存的驱动器上的数据刷新到盘片。遗憾的是,这类文件系统与带后备电池 单元(BBU)的磁盘控制器配合时表现并不理想。在这种配置中, 同步命令会强制把控制器缓存中的全部数据写到磁盘,削弱 BBU 的大部分好处。 可以运行 pg_test_fsync 程序查看自己是否受此影响。如果确实 受影响,而又能这样做,则可通过关闭文件系统中的写屏障或重新配置磁盘控制器, 恢复 BBU 的性能优势。如果关闭了写屏障,一定要确保电池保持正常工作;电池故障 有可能导致数据丢失。希望文件系统和磁盘控制器的设计者最终能解决这种次优行为。
当操作系统向存储硬件发出写请求后,它几乎做不了什么来确保数据已经到达真正的 非易失性存储区域。相反,管理员有责任确保所有存储组件都能同时保证数据和 文件系统元数据的完整性。应避免使用写缓存没有电池后备的磁盘控制器。在 驱动器层面,如果驱动器无法保证在关闭前把数据写出,就应禁用回写缓存。如果 使用 SSD,要注意其中很多默认并不遵守缓存刷新命令。可以借助 diskchecker.pl 测试 I/O 子系统行为是否可靠。
数据丢失的另一种风险来自磁盘盘片写操作本身。磁盘盘片被分为若干扇区,通常每个 512 字节。每一次物理读写都处理整个扇区。当写请求到达驱动器时,它可能是若干个 512 字节的倍数(PostgreSQL 通常一次写入 8192 字节,也就是 16 个扇区),而写入过程可能在任何时刻因断电失败,这就意味着 某些 512 字节扇区写成功了,另一些却没有。为防范这类故障, PostgreSQL 会在修改磁盘上的实际页面 之前,定期将整页镜像写入持久 WAL 存储。这样一来,在 崩溃恢复时 PostgreSQL 就能从 WAL 恢复部分写入的 页面。如果你使用了能阻止部分页面写入的文件系统软件(如 ZFS),可以通过关闭 参数 full_page_writes 来禁用这种页面镜像。带后备电池 单元(BBU)的磁盘控制器并不能阻止部分页面写入,除非它们保证写入 BBU 的数据 总是完整的整页(8kB)。
PostgreSQL 还能够防范存储设备上因硬件错误或介质 随时间失效而发生的某些数据损坏,例如读出或写入垃圾数据。
WAL 文件中的每条单独记录都受 CRC-32C(32 位)校验保护,因此我们可以判断 记录内容是否正确。CRC 值会在写入每条 WAL 记录时设置,并在崩溃恢复、归档 恢复和复制过程中进行检查。
数据页默认带有校验和,而记录在 WAL 记录中的整页镜像始终受校验和保护。
pg_xact、pg_subtrans、 pg_multixact、pg_serial、 pg_notify、pg_stat、 pg_snapshots 等内部数据结构并不直接带校验和,其页面 也不受整页写保护。不过,在这些数据结构是持久的场合,会写入 WAL 记录,使 最近的更改能够在崩溃恢复时被准确重建,而这些 WAL 记录本身会按前述方式受 到保护。
pg_twophase 中各个单独的状态文件受 CRC-32C 保护。
较大 SQL 查询用于排序、物化和存放中间结果的临时数据文件当前不带校验和, 对这些文件的更改也不会写入 WAL 记录。
PostgreSQL 不防范可纠正的内存错误,因此这里假定 你使用的是带有业界标准纠错码(ECC)或更强保护机制的 RAM。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。