src/backend/storage/page/README
Checksums
数据页校验和用于检测 I/O 系统引入的损坏。我们不为缓冲对抗不可纠正的内存错误:据大型机房研究,此类错误实测发生率很低(http://www.cs.toronto.edu/~bianca/papers/sigmetrics09.pdf;2010/12/22 在 -hackers 上讨论过)。
当前实现要求在 initdb 时整库启用,或对离线集群使用 pg_checksums。
校验和并非在数据页上始终有效。
页离开共享缓冲池时校验和有效;之后因 I/O 再次进入共享缓冲池时会校验。我们在即将 flush 共享池中的缓冲之前设置校验和。因此,一旦因数据修改甚至 hint 而改动页面,该页的校验和即被隐式失效。共享缓冲中大量(甚至多数)页面的 pd_checksum 无效,解读该字段时需注意。
因此,经 WAL 记录的页修改不会更新页校验和,全页镜像(FPI)上的校验和也可能无效。这些页镜像由 WAL 的 CRC 覆盖,与本机制分开校验。WAL 重放时不应检查全页镜像的页校验和。
可这样理解:WAL CRC 保护进入 WAL 流的记录;数据页校验保护进入共享缓冲池的块。目的相近,机制完全独立;二者合起来用于检测数据重新进入 PostgreSQL 可控内存时的错误。另:WAL 校验为 32-bit CRC,页校验和仅为 16-bit。
对数据块的任何写出都可能在写失败时造成半写(torn page)。全页写入(full page writes)写入 WAL 以防御该问题。页已脏时设置 hint bit 是安全的,因为自上次 checkpoint 以来必然已为该页写过 FPI。在原本干净的页上设置 hint bit 则可能引入半写;通常无关紧要(hint 本身可丢),但若启用了页校验和,丢失若干 bit 会使校验和失效。因此在 full_page_writes = on 且启用校验和时,必须专门写一条 WAL,以便在 WAL 中记录全页镜像。Hint 更新应通过 MarkBufferDirtyHint() 保护,由该函数在必要时写出 FPI。
计算页校验和时会纳入标准页中部「空洞」里那些本应为零的字节。从存储读回块时,也就隐式检查空洞是否仍全为零,以便发现虽未必已毁掉用户数据、却可能毁掉数据的错误。WAL 中的全页镜像不检查空洞是否为零:空洞中的数据被跳过,重放 backup block 时再填零。原因是:WAL 失败是致命错误,会阻断后续恢复;而普通数据块校验失败对服务器是严重错误,但通常不构成 critical failure(对用户仍非常糟糕)。
恢复期间不能写新的 WAL 记录。因此在启用校验和时,恢复过程中设置 hint bit 时,若缓冲尚未脏,则不得将页标脏。Hot Standby 可能从设置 hint 中受益,但启用校验和时,设置 hint 后不能把页弄脏(半写风险)。须等待主库传来的、已包含这些 hint 更新的全页镜像。