FreeBSD 中文社区 2025 第二季度问卷调查
FreeBSD 中文社区(CFC)
VitePress 镜像站QQ 群 787969044视频教程Ⅰ视频教程Ⅱ
  • FreeBSD 从入门到追忆
  • 中文期刊
  • 状态报告
  • 发行说明
  • 手册
  • 网络文章集锦
  • 笔记本支持报告
  • Port 开发者手册
  • 架构手册
  • 开发者手册
  • 中文 man 手册
  • 文章
  • 书籍
  • FreeBSD 中文期刊
  • 编辑日志
  • 2025-123 下游项目
    • FreeBSD 发布工程:新主管上任
    • GhostBSD:从易用到挣扎与重生
    • BSD Now 与将来
    • 字符设备驱动教程(第三部分)
    • 学会走路——连接 GPIO 系统
    • FreeBSD 中对 SYN 段的处理
    • FreeBSD 2024 年秋季峰会
  • 2024-1112 虚拟化
    • 字符设备驱动程序教程(第二部分)
    • 面向 Linux 和 Windows 用户的 bhyve
    • Xen 与 FreeBSD
    • Wifibox:一种嵌入式虚拟化无线路由器
    • 嵌入式 FreeBSD:Fabric——起步阶段
    • DGP:一种新的数据包控制方法
    • 会议报告:我在都柏林的 EuroBSDCon 体验
  • 2024-0910 内核开发
    • 字符设备驱动程序教程
    • VPP 移植到了 FreeBSD:基础用法
    • 利用 Kyua 的 Jail 功能提升 FreeBSD 测试套件的并行效率
    • FreeBSD 上的 Valgrind
    • 嵌入式 FreeBSD:探索 bhyve
    • TCP/IP 历险记:FreeBSD TCP 协议栈中的 Pacing
    • 实用软件:实现无纸化(Paperless)
  • 2024-0708 存储与文件系统
    • FreeBSD 中的 NVMe-oF
    • FreeBSD iSCSI 入门
    • 使用 ZFS 原生加密保护数据
    • 嵌入式 FreeBSD:打造自己的镜像
    • TCP LRO 简介
    • 基于 Samba 的时间机器备份
  • 2024-0506 配置管理对决
    • 基本系统中的 mfsBSD
    • rdist
    • Hashicorp Vault
    • 在 GitHub 上向 FreeBSD 提交 PR
    • 悼念 Mike Karels
    • 2024 年 5-6 月来信
    • 嵌入式 FreeBSD 面包板
    • TCP/IP 历险记:TCP BBLog
    • 实用软件:开发定制 Ansible 模块
  • 2024-0304 开发工作流与集成
    • FreeBSD 内核开发工作流程
    • FreeBSD 与 KDE 持续集成(CI)
    • 更现代的内核调试工具
    • 从零开始的 ZFS 镜像及 makefs -t zfs
    • 提升 Git 使用体验
  • 2024-0102 网络(十周年)
    • FreeBSD 中的 RACK 栈和替代 TCP 栈
    • FreeBSD 14 中有关 TCP 的更新
    • if_ovpn 还是 OpenVPN
    • SR-IOV 已成为 FreeBSD 的重要功能
    • FreeBSD 接口 API(IfAPI)
    • BATMAN:更优的可移动热点网络方式
    • 配置自己的 VPN——基于 FreeBSD、Wireguard、IPv6 和广告拦截
    • 实用软件:使用 Zabbix 监控主机
  • 2023-1112 FreeBSD 14.0
    • LinuxBoot:从 Linux 启动 FreeBSD
    • FreeBSD 容器镜像
    • 现在用 Webhook 触发我
    • 新的 Ports 提交者:oel Bodenmann (jbo@freebsd.org)
  • 2023-0910 Port 与软件包
    • 回忆录:与 Warner Losh(@imp)的访谈
    • 在你自己的仓库中定制 Poudriere 源
    • Wazuh 和 MITRE Caldera 在 FreeBSD Jail 中的使用
    • PEP 517
    • CCCamp 2023 旅行报告
  • 2023-0708 容器与云
    • 在 Firecracker 上的 FreeBSD
    • 使用 pot 和 nomad 管理 Jail
    • 会议报告:C 与 BSD 正如拉丁语与我们——一位神学家的旅程
    • 抒怀之旅:与 Doug Rabson 的访谈
    • 基于 Jail 的广告拦截教程
    • 我们收到的来信
  • 2023-0506 FreeBSD 三十周年纪念特刊
    • CheriBSD 近十多年的历程
    • AArch64:成为 FreeBSD 新的一级架构
    • 岁月如梭:我个人的时间线
    • 安装 FreeBSD 1.0:回顾 30 年前
    • ZFS 是如何进入 FreeBSD 的呢?
    • 我不是来自约克郡的,我保证!
    • 回忆录:采访 David Greenman Lawrence
    • FreeBSD 和早期的 Unix 社区
    • 早期的 FreeBSD 移植
    • FreeBSD 30 周年:成功的秘诀
    • FreeBSD 在日本:回忆之旅与今日之实
  • 2023-0304 嵌入式
    • CheriBSD port 和软件包
    • 让我们来试试 ChatGPT
    • GPU 直通
  • 2023-0102 构建 FreEBSD Web 服务器
    • ZFS 的原子 I/O 与 PostgreSQL
    • 虚拟实验室——BSD 编程研讨会
    • ZFS 简介
    • 会议报告:落基山庆祝女性计算机科学家
    • 进行中的工作/征求反馈:数据包批处理
    • 基金会与 FreeBSD 桌面
  • 2022-1112 可观测性和衡量标准
    • 在 FreeBSD 的 DDB 内核调试器中编写自定义命令
    • DTrace:老式跟踪系统的新扩展
    • 基于证书的 Icinga 监控
    • 活动监控脚本(activitymonitor.sh)
    • 实用 IPv6(第四部分)
    • EuroBSDCon 会议报道
    • 实用 Port:Prometheus 的安装与配置
    • 书评:《用火解决问题:管理老化的计算机系统(并为现代系统保驾护航)》Kill It with Fire: Manage Aging Computer Systems (and Future Proof Modern Ones)
  • 2022-0910 安全性
    • CARP 简介
    • 重构内核加密服务框架
    • PAM 小窍门
    • SSH 小窍门
    • 实用 IPv6(第三部分)
    • 书评:Understanding Software Dynamics(深入理解软件性能——一种动态视角)—— Richard L. Sites 著
    • 访谈:保障 FreeBSD 安全性
    • MCH 2022 会议报告
  • 2022-0708 科研、系统与 FreeBSD
    • 在 FreeBSD 上构建 Loom 框架
    • 教授本科生 Unix 课程
    • FreeBSD 入门研讨会
    • 实用 IPv6(第二部分)
    • 在 2022 年及以后推广 FreeBSD
    • 进行中的工作/征求反馈:Socket 缓冲区
    • FreeBSD 开发者峰会报告
    • 支持 Electromagnetic Field 2022
  • 2022-0506 灾难恢复
    • 使用 FreeBSD 构建高弹性的私有云
    • LLDB 14 —— FreeBSD 新调试器
    • 实用 IPv6(第一部分)
    • 利用 netdump(4) 进行事后内核调试
    • 进行中的工作/征求反馈:FreeBSD 启动性能
    • 实用 Port:在 OpenZFS 上设置 NFSv4 文件服务器
  • 2022-0304 ARM64 是一级架构
    • FreeBSD/ARM64 上的数据科学
    • Pinebook Pro 上的 FreeBSD
    • 嵌入式控制器的 ACPI 支持
    • 进行中的工作/征求反馈:Lumina 桌面征集开发人员
    • 实用 Port:如何设置 Apple 时间机器
  • 2022-0102 软件与系统管理
    • 为 FreeBSD Ports 做贡献
    • 使用 Git 贡献到 FreeBSD Ports
    • CBSD:第一部分——生产环境
    • 将 OpenBSD 的 pf syncookie 代码移植到 FreeBSD 的 pf
    • 进行中的工作/征求反馈:mkjail
    • 《编程智慧:编程鬼才的经验和思考》(The Kollected Kode Vicious)书评
    • 会议报告:EuroBSDCon 2021 我的第一次 EuroBSDCon:一位新组织者的视角
  • 2021-1112 存储
    • 开放通道 SSD
    • 构建 FreeBSD 社区
    • 与完美操作系统同行 27 年
    • 进行中的工作/征求反馈:OccamBSD
    • 通过 iSCSI 导入 ZFS ZIL——不要在工作中这样做——就像我做的那样
  • 2021-0910 FreeBSD 开发
    • FreeBSD 代码审查与 git-arc
    • 如何为 FreeBSD 实现简单的 USB 驱动程序
    • 内核开发技巧
    • 程序员编程杂谈
  • 2021-0708 桌面/无线网
    • 通往 FreeBSD 桌面的直线路径
    • FreeBSD 13 中的人机接口设备 (HID) 支持
    • Panfrost 驱动程序
    • 用 Git 更新 FreeBSD
    • FreeBSD 的新面孔
    • 想给你的桌面加点佐料?
  • 2021-0506 安全
    • 七种提升新安装 FreeBSD 安全性的方法
    • copyinout 框架
    • 使用 TLS 改善 NFS 安全性
    • Capsicum 案例研究:Got
    • 对 Jail 进行安全扫描
  • 2021-0304 FreeBSD 13.0
    • 展望未来
    • FreeBSD 13.0 工具链
    • FreeBSD 13.0 中有新加载器吗?
    • TCP Cubic 准备起飞
    • OpenZFS 中的 Zstandard 压缩
    • 会议报告:FreeBSD 供应商峰会
    • Git 不够吗?
  • 2021-0102 案例研究
    • Tarsnap 的 FreeBSD 集群
    • BALLY WULFF
    • Netflix Open Connect
    • FreeBSD 的新面孔
    • 写作学者的 FreeBSD
    • 在世界之巅
  • 2020-1112 工作流/持续集成(CI)
    • FreeBSD Git 快速入门
    • 使用 syzkaller 进行内核 Fuzzing
    • Mastering Vim Quickly 书评
    • 线上会议实用技巧
    • 在控制台上进行网络监控
  • 2020-0910 贡献与入门
    • 采访:Warner Losh,第 2 部分
    • 代码审查
    • 撰写良好的提交消息
    • 如何在不是程序员的情况下做出贡献——成为 FreeBSD 译者
    • 如何成为文档提交者
    • 谷歌编程之夏
    • 为 FreeBSD 期刊撰写文章
    • 你为什么使用 FreeBSD
    • FreeBSD 的新面孔
  • 2020-0708 基准测试/调优
    • FreeBSD Friday
    • 采访:Warner Losh,第 1 部分
    • 构建和运行开源社区
    • 在 FreeBSD 上轻松搭建我的世界(Minecraft)服务器
    • FreeBSD 的新面孔
  • 2020-0506 网络性能
    • 内核中的 TLS 卸载
    • 访谈:Michael W Lucas
    • FreeBSD 桌面发行版
    • 使用 Poudriere 进行 Port 批量管理
    • FreeBSD 的新面孔
由 GitBook 提供支持
LogoLogo

FreeBSD 中文社区(CFC) 2025

在本页
  • 块
  • 撕裂写入
  • 恢复
  • 展望未来
在GitHub上编辑
导出为 PDF
  1. 2023-0102 构建 FreEBSD Web 服务器

ZFS 的原子 I/O 与 PostgreSQL

上一页GPU 直通下一页虚拟实验室——BSD 编程研讨会

最后更新于1个月前

  • 原文链接:

  • 作者:THOMAS MUNRO

PostgreSQL 是一种关系型数据库管理系统,符合 SQL 标准,使用类 BSD 许可证。其前身 POSTGRES 始于 1980 年代中期的伯克利大学。它在 FreeBSD 上很受欢迎,通常部署在 ZFS 存储上。许多有关 PostgreSQL 在 ZFS 上的文章都建议调整 ZFS 的 recordsize 设置和 PostgreSQL 的 full_page_writes 设置。然而,设置后者对性能和崩溃安全性的真正影响往往没有得到充分解释,可能是因为在大多数流行的文件系统上调整这个设置通常不安全。在本文中,我将简要总结这个神秘机制背后的逻辑和权衡——但在此之前,我们先简单讨论一下块大小。

块

几乎所有 PostgreSQL 的磁盘 I/O 都是基于 8KB 的块(或页面)对齐的。虽然可以重新编译 PostgreSQL,使用不同的大小,但这种做法不常见。这个大小最初可能是为了匹配 UFS 的历史默认块大小(但需要注意的是,FreeBSD 的 UFS 现在默认为 32KB)。ZFS 使用术语“记录大小(recordsize)”,其默认为 128KB。与其他文件系统不同,ZFS 可在任何时候轻松更改记录大小,并可以为每个数据集单独配置。

若数据将被随机访问,那么理论上记录大小应与 PostgreSQL 的 8KB 块匹配。否则,随机 I/O 可能会受到两个因素的影响:

  • I/O 放大:因为每次读取/写入 8KB 块时,还会传输额外的相邻数据。

  • 读 - 写顺序:当存储块当前不在操作系统缓存中,且必须写入 8KB 块时,相邻数据必须先被读取。

如果数据主要是顺序访问的,或者访问频率较低,特别是如果使用较大记录的 ZFS 压缩所带来的好处大于对 I/O 带宽和延迟的顾虑,那么调整记录大小可能是个好主意。

某些出处推荐使用 16KB、32KB 或 128KB 的记录大小,认为这是一个在不产生过多写放大和延迟的情况下,能够实现更好压缩的最佳选择。我的目的并不是做出这样的推荐——我怀疑并不存在一个标准答案——而是要解释发生了什么。有些应用程序对不同类型的数据有混合的需求。表空间可以用来将不同的表存储在不同的 ZFS 数据集里,这些数据集可以具有不同的记录大小、压缩方式或物理介质。表也可以进行分区,例如将旧数据存储在一个表空间中,而当前的活动数据存储在另一个表空间中。

CREATE TABLESPACE compressed_tablespace
LOCATION '/tank/pgdata/compressed_tablespace';

ALTER TABLE t
SET TABLESPACE compressed_tablespace;

与小 ZFS 记录大小相关的问题是碎片化。频繁更新的表可能会导致块散布到各个地方,我们希望它们能够在物理上聚集,以便获得良好的顺序读性能。简单的方法是要求 PostgreSQL 重写包含表和索引的文件,从而在 ZFS 层级上进行碎片整理。这可以通过执行 VACUUM FULL table_name 或 CLUSTER table_name 来实现,前提是你愿意在重写过程中锁定该表,使查询无法访问该表。重写表也可以使新的记录大小生效,如果它已在数据集级别进行了更改。

撕裂写入

PostgreSQL 的默认启用设置 full_page_writes ,而 ZFS 用户通常会将其关闭。关闭后,写密集型工作负载的性能变得更快且更一致。例如,在低端云虚拟机上的简单 pgbench 测试中,我通过关闭该设置实现了 32% 的每秒事务增加。那么,它到底做了什么呢?这需要一些出乎意料的背景说明。简而言之,PostgreSQL 使用物理日志记录来保证崩溃安全,这意味着写入到单个数据库页面时必须是原子性的,以防断电,否则它可能无法在崩溃后恢复。除非你能保证存储栈具有这种属性,否则 PostgreSQL 必须做一些额外的工作来保护数据。

断电时的原子性是指,如果在断电时某个物理写入正在进行,我们可以期望之后读取到该块的旧版本或新版本,但不是部分修改或撕裂的版本。这与并发读写的原子性不同(见下文)。物理日志记录(简称“物理到页面,逻辑在页面内”)是教科书中对日志策略的分类术语,意思是日志记录通过文件和块编号标识要修改的块,但随后描述如何在该页面内“逻辑”地修改它,而不是仅仅更新物理地址的位。崩溃后,恢复算法可以处理“旧”页面内容或“新”页面内容,应用任何日志记录的更改以使其恢复正常。如果遇到旧数据和新数据的非原子性混合,则无法重放逻辑更改,恢复失败!

一个表面上的问题是,如果启用了 data_checksums,PostgreSQL 的页面级校验和检查将无法读取该页面。如果禁用校验和,恢复过程会继续,但类似“在槽位 3 插入元组 (42, Fred)”这样的逻辑更改将无法可靠地重放。为了应用这个示例中的更改,我们需要理解页面上使用的预先存在的元数据表,但它可能已经被损坏。

物理日志记录是数据库行业中广泛使用的一种技术,不同的关系型数据库管理系统(RDBMS)为解决撕裂页面问题提出了不同的解决方案。由于开源系统通常在各种低端系统上开发和使用,这些系统往往没有防止断电的硬件保护,因此故障很常见,必须开发软件解决方案。PostgreSQL 当前的解决方案是切换到页面级物理日志记录或全页面写入,即在每个数据页面的首次修改后,将整个数据页面写入日志,这发生在每个检查点之后。检查点是一个周期性的后台活动,理想情况下,它对前台事务性能的影响应最小。然而,由于“首次接触”规则,待检查点开始,写密集型工作负载可能会突然生成更多的日志数据,因为小的更新突然需要记录多个 8KB 的页面。这个效应通常会逐渐减弱,因为对每个页面的后续修改会恢复为物理日志,直到下一个检查点,有时会导致 I/O 带宽和事务延迟呈锯齿形波动。

另一种流行的开源数据库有不同的解决方案,也涉及将所有数据写入两次,并在两次写入之间使用同步屏障,因为这两份副本不能被撕裂。而 ZFS 不需要任何这些!得益于其自身的写时复制(COW)设计,ZFS 提供了记录级的原子性。ZFS 记录无法看到旧内容和新内容的混合,因为它不会物理地覆盖它们,其 TXG 系统和 ZIL(ZFS 日志)使得写入具有事务性。因此,只要 recordsize 至少为 8KB,就可以安全地将 full_page_writes=off 设置为关闭。

需要注意的是,在某些情况下 ZFS 也会物理地将数据写入两次。常见的建议是考虑为包含主数据文件的数据集设置 logbias=throughput(但可能不适用于包含 PostgreSQL 日志目录 pg_wal 的数据集——这个话题在本文中没有探讨)。该选项尝试将块直接写入其最终位置,而不是首先在 ZIL 中记录它们。如果使用 ZFS 默认的 logbias=latency 设置并且 PostgreSQL 默认的 full_page_writes=on,那么实际上数据可能会被写入四次,因为 PostgreSQL 和 ZFS 都执行额外的工作来创建记录级原子性,而这两个设置最终都只保留一个副本。

不幸的是,在某些特殊场景下,full_page_writes=on 仍然是确保行为正确所必需的:在运行 pg_basebackup 和 pg_rewind 时。这些工具用于备份,或从另一台服务器创建或重新同步流式复制;在 pg_basebackup 的情况下,运行命令时会自动启用全页面写入,而在 pg_rewind 的情况下,如果没有手动启用该选项,命令将拒绝运行(这是当前版本中的一个令人烦恼的不一致性)。这些工具会制作数据文件的原始文件系统级复制,以及用于崩溃恢复的日志,以解决并发更改引起的一致性问题。在这里,我们遇到了 I/O 原子性的不同含义:读取可能被并发写入的文件。第一个问题是,Linux 和 Windows 上的文件系统(但不是 ZFS,或 FreeBSD 上的任何文件系统,因为使用了范围锁)在存在重叠并发写入时,可能会向读者展示一个随机的旧数据和新数据混合。这会导致数据不一致。此外,目前的 I/O 操作方式未能适当对齐,因此即使在 ZFS 上,撕裂的页面也可能被复制。为了防范这种情况,需要启用 full_page_writes 行为。这个问题最终应该在 PostgreSQL 中修复,通过以适当的对齐和锁定方式复制原始数据文件。需要注意的是,如果采取适当的预防措施(主要是确保快照能够原子性地捕获日志和所有数据文件),ZFS 快照可以替代 pg_basebackup,从而减少在克隆或备份繁忙系统时的影响。

恢复

我们已经看到 full_page_writes=off 如何提高写事务的性能,并且 ZFS 使其变得安全。不幸的是,对于复制和崩溃恢复,也可能会有负面的性能影响。这两项活动都涉及恢复,意味着它们需要重放日志。虽然全页面图像在写入时会导致性能下降,但在恢复时重放时它们却起到了优化作用。我们不需要执行一个可能会阻塞恢复串行处理循环的随机同步读取,因为页面的内容已经在我们精心排列的顺序日志中,并且在此之后它已被缓存。

一组使用 PostgreSQL 在 illumos 操作系统上的 ZFS 上进行大规模部署的研究人员,研究了 full_page_writes=off 对恢复 I/O 停顿的影响。他们开发了一种名为 pg_prefaulter 的工具作为解决方法。研究人员发现,由于可预测的 I/O 停顿,他们的流式复制无法跟上主服务器的速度。由于大多数大规模使用 PostgreSQL 的用户甚至无法设置 full_page_writes=off,他们可能处于独特的位置来观察到这种效应。如果遇到此问题,pg_prefaulter 可能是一个解决方案,直到内置预取功能可用为止。

展望未来


THOMAS MUNRO 是一位开源数据库开发者,现任微软 Azure 工作,通常使用 FreeBSD 进行开发。

PostgreSQL 15 包含了部分解决方案:它会提前查看日志,找到即将被读取的页面,并发出 POSIX_FADV_WILLNEED 提示,以生成可配置程度的 I/O 并发性(这是一种简易的异步 I/O)。截至本文撰写时,FreeBSD 会忽略这一提示,但 OpenZFS 的未来版本希望能将其连接到 FreeBSD 的 VFS(OpenZFS 拉取请求 )(译者注:截止 2025/1/31 仍未合并)。最终,这应该会被一个真正的异步 I/O 子系统所替代,该子系统正在开发中,并计划用于 PostgreSQL 的未来版本。

块大小对齐可能在未来的 PostgreSQL 版本中成为一个更重要的话题,这些版本可能会包括提议的直接 I/O 支持,目前该支持仅以原型形式存在。这与 OpenZFS 对直接 I/O 支持的开发恰好吻合(拉取请求 #10018),而且可能需要块大小一致性才能有效工作(当前的原型会回退到 ARC;其他一些文件系统则会拒绝不对齐的直接 I/O)。另一个正在开发的 OpenZFS 特性是块克隆(拉取请求 )(译者注:已合并),以及新的 FreeBSD 系统接口,PostgreSQL 希望能够利用这些接口,快速克隆数据库和数据库对象,且克隆粒度比整个数据集更细。

ZFS’s Atomic I/O and PostgreSQL
#13958
#13392