ベアメタルでZFS上にPostgreSQLを動かす
postgresサーバーを新しく立てて、Atuin用にします! 今回はホットレプリカも用意して、信頼性を大幅に高めます。Atuinではここ数年、障害もデータベースの問題も起きていませんが、運を試し続けたくはありません。
Atuin APIイメージ向けに構築したhetzner k3sのセットアップも、興味があるかもしれません。
まずはかなり最小限の構成にして、チューニングは後で行います。どのオプションが自分のワークロードに最適かはまだ分からないので、シンプルにしておきます。
Atuinのクエリは複雑ではありません。主にかなり大量のデータを保存し、おおむねシーケンシャルに読み出します。JOINも複雑なクエリもほとんどありません。
数十万行をできるだけ速く書き込んだり読み出したりするバーストが発生することはよくあります。ただし、これもかなりシーケンシャルで、まったく「複雑」ではありません。
理想としては、データを可能な限り圧縮したいです。Atuinが保存するデータの大半は圧縮しにくい暗号化済みデータですが、JSONのパディングやタイムスタンプなど、暗号化されていないデータもかなりあります。圧縮しましょう! CPUを多少使う代わりに、ディスク使用量だけでなくI/Oもきれいに削減できます。
クエリはシンプルなので、CPUコストは問題ありません。
セットアップ
これはオークションで購入したHetznerのマシン2台で動かします。Ryzen CPUと1TBのNVMe SSDを4本搭載しています。
SSDのうち2本はシンプルなRAIDミラーで運用し、OSとログを置きます。残る2本には、ZFSファイルシステムとpostgresを置きます。
ミラーリングのためストレージ容量の50%を使うことになります。ただし、これはベアメタルのハードウェアなので、ディスクが故障する可能性があります。レプリカへフェイルオーバーし、技術者にディスクを交換してもらうまで、データベースが動き続けるようにしたいです。
# Install zfs
apt install zfsutils-linux
# check version
zfs versionlsblkでディスクレイアウトを確認します。
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はどちらもOS用に使用中ですが、/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 partzpool 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をセットアップする必要があります。ZFSへ移す前に、Postgresの初期化を実行する必要があります。面倒ですが、仕方ありません。
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_walZFSの設定
以下を含め、いろいろ読みました。
- https://vadosware.io/post/everything-ive-seen-on-optimizing-postgres-on-zfs-on-linux/
- https://bun.uptrace.dev/postgres/tuning-zfs-aws-ebs.html
上記の記事の一つでは、fdatasyncを使用した際、成功した書き込みを返すものの、実際には正しく書き込めていないNVMeハードウェアの問題がありました。多くのドライブは、データが揮発性の書き込みキャッシュに保存された時点で、実際には保存されていなくても書き込み成功を返します。以下でこれを無効化します。
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のブロックサイズ = 8KBならより高いTPSを得られますが、Atuinは主にシーケンシャルで、大量のデータを読み書きします。Postgresのブロックサイズは変更できます(コンパイル時のオプションです)が、それは今後試すことにします。
まずはデフォルトの128KBを試し、様子を見ます。小さい値は速くなる可能性がありますが、大きい値のほうが圧縮率は高くなる傾向があります。ただし多くの要因に左右されるため、計測してみます。
# 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は部分ページを書き込めないため、これはかなり冗長です。
次に、ARC(ZFSのページキャッシュ)へシステムメモリの75%を割り当てます。残りは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でバックアップを設定し、何か問題が起きた場合にフェイルオーバーできるホットスタンバイをセットアップします!
その後、データセットをコピーして、この新しいデータベースを本番環境に投入します 🚀
記事をランダムに読む