非易失内存退潮之后:Postgres 持久性为何仍离不开 WAL

2026-09-22 11 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

数据库处理数据时依赖 CPU 可直接访问的内存,但普通 DRAM 一旦断电,内容就会消失。Postgres 因此不能只在内存里完成事务:它必须先把关键变更写入操作系统可访问的持久化存储,并通过预写日志(Write-Ahead Logging,WAL)兑现 ACID 中的持久性承诺。

过去二十多年,PCM、3D XPoint、Optane,以及面向 CXL 的非易失内存,都试图缩短“易失内存”和“持久存储”之间的距离。理想状态下,数据库可以像访问内存一样访问断电不丢失的数据。然而,Micron 和 Intel 相继放弃相关产品路线,说明技术上可行并不等于能以合适的成本、容量和生态规模持续交付。

一次提交为什么要等待存储

Postgres 修改一行数据时,不必马上把对应的数据页写回磁盘。它采用 WAL,将描述变更所需的信息先追加到日志中。一次典型提交可以简化为:

  1. 后端进程修改共享缓冲区中的数据页;
  2. 生成对应的 WAL 记录;
  3. 提交时确保 WAL 到达满足持久性要求的存储层;
  4. 数据页可以稍后由后台写入机制落盘;
  5. 崩溃重启后,Postgres 使用 WAL 重放尚未进入数据文件的变更。

这里的关键不是“调用了 write()”,而是提交返回前,日志是否真正跨过了易失层。CPU 缓存、DRAM、操作系统页缓存、控制器缓存和实际介质之间可能存在多层缓冲,因此 Postgres 还需要 fsync 等机制与操作系统、存储设备协作。

这套路径带来了明显复杂度,但也实现了一个重要解耦:事务不必等待随机分布的数据页全部写回,只需要优先保证顺序追加的 WAL 已经持久化。

非易失内存原本想改变什么

如果 CPU 可寻址内存既接近 DRAM 的访问方式,又能在断电后保留内容,数据库架构就有机会减少传统 I/O 路径中的复制、系统调用和上下文切换。WAL、缓冲区管理乃至恢复算法也可能被重新设计。

但“内存不会丢数据”并不能自动消除数据库持久性的全部问题:

  • CPU 缓存仍可能是易失的。 数据写入某个地址,不一定意味着它已经到达持久域。
  • 写入顺序仍然重要。 数据结构更新一半时断电,恢复程序必须区分完整状态和半完成状态。
  • 原子写入粒度有限。 一个跨越多个缓存行或多个对象的事务,仍需要日志、影子副本或其他提交协议。
  • 介质故障不等于进程崩溃。 非易失内存解决断电后的数据保留,不必然解决设备损坏、主机丢失和跨机容灾。
  • 软件生态需要配合。 文件系统、内核、驱动、数据库和运维工具都必须理解新的持久性语义。

因此,非易失内存更可能改变 WAL 的实现成本和数据路径,而不是让事务日志、恢复点和备份凭空消失。

动手观察同步提交的代价

下面的实验可以在安装了 Docker 的机器上运行。它不模拟 Optane 或 CXL 非易失内存,只用于观察 Postgres 在“等待 WAL 持久化”与“不等待”两种策略下的行为差异。结果会受到 Docker、宿主机文件系统和存储缓存影响,不应直接作为生产容量规划依据。

# 启动一个临时 PostgreSQL 16 实例
docker run --rm --name pg-wal-lab \
  -e POSTGRES_PASSWORD=lab \
  -p 55432:5432 \
  -d postgres:16

# 等待数据库就绪
until docker exec pg-wal-lab pg_isready -U postgres >/dev/null 2>&1; do
  sleep 1
done

# 查看与持久性相关的关键配置
docker exec pg-wal-lab psql -U postgres -c \
  'SHOW fsync; SHOW synchronous_commit; SHOW full_page_writes;'

# 初始化 pgbench 数据集
docker exec pg-wal-lab pgbench -U postgres -i -s 10 postgres

# 基线:每次提交等待本地 WAL 刷新
docker exec \
  -e PGOPTIONS='-c synchronous_commit=on' \
  pg-wal-lab pgbench -U postgres -c 8 -j 2 -T 30 postgres

# 对照:提交不等待 WAL 刷新完成
docker exec \
  -e PGOPTIONS='-c synchronous_commit=off' \
  pg-wal-lab pgbench -U postgres -c 8 -j 2 -T 30 postgres

# 清理实验环境
docker stop pg-wal-lab

重点比较两次输出中的 latency averagetps。在存储提交延迟明显的环境中,关闭同步提交通常可能提高吞吐量、降低前台等待时间;但这不是免费的性能开关。synchronous_commit=off 允许客户端收到成功响应时,最近的 WAL 仍未完成持久化。数据库崩溃后结构仍可保持一致,但一部分已经向客户端确认的近期事务可能丢失。

不要为了跑分关闭 fsyncfsync=off 会削弱崩溃恢复所依赖的基本保证,风险远高于针对特定会话或业务选择异步提交。

即使新介质出现,也要先问四个问题

评估任何“持久内存”或新型存储方案时,可以使用以下检查表:

  • 持久域在哪里? 提交返回时,数据是在 CPU 缓存、内存控制器、设备缓存,还是已经到达掉电保护范围?
  • 故障模型是什么? 方案覆盖进程崩溃、操作系统崩溃、整机断电,还是设备永久损坏?
  • Postgres 如何使用它? 只是把 WAL 放在更快的介质上,还是需要修改存储引擎与恢复协议?
  • 成本是否覆盖全生命周期? 除硬件价格外,还要计算容量、备件、驱动支持、监控、升级和迁移风险。

更现实的采用策略,是先优化现有 WAL 路径:确认存储确实兑现 flush 语义,测量提交延迟,按业务边界决定同步提交策略,并使用复制、归档和备份覆盖单机介质无法解决的故障。

非易失 CPU 可寻址内存的商业化受挫,并没有让它提出的问题失效。它反而提醒数据库工程师:持久性从来不是某一种介质的单项能力,而是一条从事务提交、缓存刷新、日志恢复一直延伸到备份与容灾的完整链路。


相关推荐