在裸金属服务器上用 ZFS 运行 PostgreSQL
原文由 Ellie Huxtable 于 发布,订阅该博客
我正在为 Atuin 搭建新的 postgres 服务器!这次我们会加上热备,让整体变得可靠得多。Atuin 已经有好几年没有出现过宕机或数据库问题了,但我可不想去赌运气。
你可能也会对我为 Atuin API 镜像做的 hetzner k3s 部署感兴趣
一开始我会先做一个相当精简的配置,之后再慢慢调优。我还不确定哪些选项最适合我的工作负载,所以先保持简单。
Atuin 的查询并不复杂。我们主要就是存储海量数据,并且(大致上)按顺序读取。几乎没有关联查询,也很少有复杂查询。
我们经常会遇到突发情况,需要尽快写入或读取几万、甚至几十万行数据——不过这些操作同样是比较顺序化的,谈不上“复杂”。
理想情况下,我们会尽可能地压缩数据。虽然 Atuin 存的主要是加密数据(不太好压缩),但仍有相当一部分未加密数据,比如 JSON 填充、时间戳等等。把它们压缩起来!这样不仅能有效减少磁盘占用,还能降低 IO,代价是多消耗一些 CPU。
由于我们的查询很简单,这点 CPU 开销完全可以接受。
搭建环境
我用的是在拍卖中买来的几台 Hetzner 机器。它们配备了 Ryzen CPU 和 4 块 1TB 的 NVMe SSD。
其中两块 SSD 会组成简单的 RAID 镜像,用来存放操作系统和日志。另外两块则用来搭建 ZFS 文件系统并存放 postgres。
这样做会牺牲 50% 的存储空间用于镜像,但由于这是裸金属硬件,硬盘随时可能故障。我希望能确保即使有硬盘损坏,数据库也能继续运行,直到我切换到备库、并让技术人员换上新硬盘。
# Install zfs
apt install zfsutils-linux
# check version
zfs version用 lsblk 查看磁盘布局
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme1n1 259:0 0 953.9G 0 disk
├─nvme1n1p1 259:1 0 32G 0 part
│ └─md0 9:0 0 32G 0 raid1 [SWAP]
├─nvme1n1p2 259:2 0 1G 0 part
│ └─md1 9:1 0 1022M 0 raid1 /boot
├─nvme1n1p3 259:3 0 128G 0 part
│ └─md2 9:2 0 127.9G 0 raid1 /var
├─nvme1n1p4 259:4 0 1K 0 part
└─nvme1n1p5 259:5 0 792.9G 0 part
└─md3 9:3 0 792.7G 0 raid1 /
nvme0n1 259:6 0 953.9G 0 disk
nvme3n1 259:7 0 953.9G 0 disk
nvme2n1 259:8 0 953.9G 0 disk
├─nvme2n1p1 259:9 0 32G 0 part
│ └─md0 9:0 0 32G 0 raid1 [SWAP]
├─nvme2n1p2 259:10 0 1G 0 part
│ └─md1 9:1 0 1022M 0 raid1 /boot
├─nvme2n1p3 259:11 0 128G 0 part
│ └─md2 9:2 0 127.9G 0 raid1 /var
├─nvme2n1p4 259:12 0 1K 0 part
└─nvme2n1p5 259:13 0 792.9G 0 part
└─md3 9:3 0 792.7G 0 raid1 //dev/nvme1n1 和 /dev/nvme2n1 已经被系统占用,而 /dev/nvme0n1 和 /dev/nvme3n1 还空着,正好用来做 ZFS 镜像。
创建镜像
zpool create postgres mirror -o ashift=12 /dev/nvme0n1 /dev/nvme3n1很快就返回了!
再用 lsblk 查看一下,已经能看到占用情况了
nvme0n1 259:6 0 953.9G 0 disk
├─nvme0n1p1 259:14 0 953.9G 0 part
└─nvme0n1p9 259:15 0 8M 0 part
nvme3n1 259:7 0 953.9G 0 disk
├─nvme3n1p1 259:18 0 953.9G 0 part
└─nvme3n1p9 259:19 0 8M 0 part用 zpool status 确认一下
pool: postgres
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
postgres ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme3n1 ONLINE 0 0 0
errors: No known data errors存储池默认挂载在 /postgres。不错!
Postgres
在创建数据集之前,我们得先把 postgres 装好。Postgres 需要在迁移到 ZFS 之前先完成初始化。有点麻烦,但也没办法。
Ubuntu 22.04 自带的版本比较旧,所以要用 postgres 官方源来安装
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
apt updateapt install postgresql-16 postgresql-contrib-16
systemctl enable postgresql
systemctl start postgresql然后,停掉 postgres,把它的数据先移到临时位置,创建好数据集,再把数据搬回来。用 mv/cp 应该也行,或许用 rsync 来保留权限会更好。
systemctl stop postgresql
# move postgres data to temp
mv /var/lib/postgresql/16/main/pg_wal /tmp/pg_wal
mv /var/lib/postgresql /tmp/postgresql
# create the datasets
zfs create postgres/data -o mountpoint=/var/lib/postgresql
zfs create postgres/wal -o mountpoint=/var/lib/postgresql/16/main/pg_wal
# switcharoo the data back
cp -r /tmp/postgresql/* /var/lib/postgresql
cp -r /tmp/pg_wal/* /var/lib/postgresql/16/main/pg_wal
# sort perms
chmod -R 0700 /var/lib/postgresql
chmod -R 0700 /var/lib/postgresql/16/main/pg_wal
chown -R postgres: /var/lib/postgresql
# start postgres once more
systemctl start postgresql检查一下是否正常:
zfs list
NAME USED AVAIL REFER MOUNTPOINT
postgres 600K 922G 24K /postgres
postgres/db 72K 922G 24K /postgres/db
postgres/db/base 24K 922G 24K /postgres/db/base
postgres/db/pg_wal 24K 922G 24K /postgres/db/pg_wal配置 ZFS
我参考了不少资料,包括:
- https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/
- https://bun.uptrace.dev/postgres/tuning-zfs-aws-ebs.html
上面其中一篇文章提到,有些 NVMe 硬件在使用 fdatasync 时会报告写入成功,但实际上并没有真正落盘。很多硬盘会在数据还缓存在易失性写缓存中时就报告写入成功,而不是真正持久化。用下面的命令禁用这个行为:
apt install nvme-cli
nvme set-feature -f 6 -v 0 /dev/nvme0n1
nvme set-feature -f 6 -v 0 /dev/nvme3n1我花了不少时间考虑最优的 recordsize。虽然把 recordsize 设成和 postgres 块大小一致(8k)能获得更高的 tps,但 Atuin 的读写大多是顺序的、而且一次会处理大量数据。我可以修改 postgres 的块大小(这是个编译时选项),但打算以后再考虑尝试。
一开始,我会先试试默认的 128k,看看效果如何。较小的数值可能会更快,但较大的数值通常能获得更好的压缩率。具体还要看很多因素,所以我会先实际测一测再说。
# enable compression
zfs set compression=zstd-3 postgres
# disable access time (so, so many writes...)
zfs set atime=off postgres
# enable improved extended attributes
zfs set xattr=sa postgres
# zfs set recordsize=16k postgres然后,我在 postgres 一侧把 full_page_writes = off 关掉——ZFS 不会写入不完整的页,所以这个选项基本是多余的。
接下来,我们会把系统内存的 75% 分给 ARC(ZFS 的页缓存)。剩下的部分留给 postgres 的 shared_buffers。
echo 51539607552 >> /sys/module/zfs/parameters/zfs_arc_max要让这个设置在重启后依然生效,需要在 /etc/modprobe.d/zfs.conf 中加入
options zfs zfs_arc_max=51539607552Postgres 配置
下面这些不是 ZFS 专属的,而是一些通用的 postgres 调优
# 25% of 64GB
shared_buffers = 16GB
work_mem = 8MB
# make vaccuums/etc faster
maintenance_work_mem = 1GB
# tell the planner how much the ZFS ARC will likely cache
effective_cache_size = 48GB还有很多可以调的地方,但实际上这些配置带来的提升最大。Postgres 默认配置的内存给得非常小!
到这里,我重启了一次,以确保一切正常、且配置都已正确持久化。
压测一下
我想确保系统性能至少还过得去,于是跑了一下 pgbench,结果如下
scaling factor: 50
query mode: simple number of clients: 20
number of threads: 4
maximum number of tries: 1
number of transactions per client: 100000
number of transactions actually processed: 2000000/2000000
number of failed transactions: 0 (0.000%)
latency average = 2.863 ms
initial connection time = 10.287 ms
tps = 6984.804317 (without initial connection time)还不错!虽然这并不能完全代表我的实际工作负载,但至少说明系统没有跑偏。以后我还会进一步调优。
接下来
接下来我会用 pgbackrest 配置备份,并搭建一个可以随时切换的热备,以防万一!
然后把数据集迁移过去,让这个新数据库正式上线 🚀
随机一篇博客
评论
登录后参与讨论