「OrioleDB を触ってみる」「OrioleDB は小規模DBでも OLTP に強い」に続く、OrioleDB の記事です。
PostgreSQLストリーミングレプリケーションの悩み
PostgreSQLデータベースのDR(ディザスタリカバリ)やHA(高可用性)クラスタを構成する目的でストリーミングレプリケーションを使っている方は多いと思います。ストリーミングレプリケーションは、PostgreSQLがクラッシュリカバリ用に出力する WAL(write ahead log、更新差分の記録) のレコードをスタンバイサーバに送ることでレプリケーションを実現します。レプリケーションを構成するための手順が簡単であり、また、ほぼ全ての変更を伝搬できることが魅力です。
この時の悩みは通信帯域です。
DR目的で遠隔地にレプリケーションを行う場合、十分な通信帯域を用意できないため、主系で大きな更新が発生すると大きな同期遅延が発生します。「遅延を何分/何秒以内とする」みたいな要件を取り決めてしまうと守るのは大変です。
HAクラスタの場合には、大きな更新の反映に際して、並行して実行しているオンライントランザクションが同期レプリケーションのレスポンスを待つために遅延するという問題が生じます。ここで「大きな変更」については「SET synchronous_commit TO off;」として非同期で実行しても助けにはなりません。発生した WALレコードは必ず順次に転送、適用されていくからです。
なお、レプリケーションしていなくとも、データ更新に対して WALを書き出すということは必ず発生して、かつ、インスタンス全体で直列的に処理されるため、この部分はしばしば 1つの PostgreSQLインスタンスの性能ボトルネックになります。同じサーバ内で 2つの PostgreSQLに分けたら性能アップした、ということが起きます。
WAL量を減らせ
この問題を解決するため、最初に思いつくことは時間あたりの WAL量を減らすことです。これはDRの場合にも、HAの場合にも有効です。
他には、優先順位を付けて一部の変更について WAL転送や適用を後回しにする、ということも思いつきますが、データの一貫性という観点で本質的な難しさがありそうです。
PostgreSQLでWAL量を減らす設定としては、wal_compression = on
があります。WAL出力のうち特にサイズが大きいフルページ書き出し(更新のあったページ全体を書き出す、1ページはデフォルトビルドなら 8KB)について圧縮をかけてくれます。圧縮・展開の処理コストと引き換えに転送帯域は節約できます。
さらに、full_page_writes = off
とすると、フルページ書き出し自体を行わなくなります。しかし、これはPostgreSQLが非正常終了したときにリカバリ起動できない可能性を伴う設定であるため、通常は採用されません。
OrioleDB で解決できる?
「OrioleDB を触ってみる」記事で見た通り、OrioleDB でもレプリケーションが可能です。OrioleDB のレプリケーションではデータ転送量は増えているのでしょうか、減っているのでしょうか。
OrioleDB のドキュメントから推測すると、以下のような理由でデータ転送量は減りそうに思えます。
- row level WAL であると言っている、よってフルページ書き込みに相当する出力は無いはず。
- undo log 型のトランザクション書き込みであるから、VACUUM や HOT Prune といったゴミ掃除の処理が無い。ということはそれに伴う WAL 出力が無い。
- 64bit XID である (PostgreSQL は 32bit)。よって XID周回対策のフリーズ処理は無い。それに伴う WAL も無い。
検証: OrioleDB と PostgreSQL の転送量を比較
OrioleDB と PostgreSQL でそれぞれレプリケーションを構築して、プライマリ側に同じワークロードを与えて、そのときのネットワーク送出量を測って比較してみました。本記事執筆時点で OrioleDB は beta9 がリリースされているのですが、手抜きで、前に構築した PsotgreSQL 17.0 + beta6 の環境をそのまま使います。
以下のような具合で、pgbench を時間指定ではなく、-c 10 並列で 各 -t 1000 トランザクション実行、というように総実行トランザクションが一定になるように実行して、その前後の TX: bytes
の増分を調べます。
$ pgbench -s 10 -i db1
《出力省略》
$ ip -s link show enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:19:e1:c0 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
52274885 166119 0 0 0 0
TX: bytes packets errors dropped carrier collsns
6907798175 4622596 0 0 0 0
《実際には何回か実行して値が落ち着くのを待つ》
$ pgbench -c 10 -t 1000 -n db1
《出力省略》
$ ip -s link show enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 08:00:27:19:e1:c0 brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
53382456 176950 0 0 0 0
TX: bytes packets errors dropped carrier collsns
6911827786 4630308 0 0 0 0
《この結果から 6911827786 - 6907798175 = 4029611 bytes と算出》
これは短時間で終わってしまうので、チェックポイント処理後のフルページ書き込みや、バックグラウンドの autovacuum処理の効果が出てこないかもしれません。続いて、もっと時間を要するように pgbench -c 10 -t 100000 -n db1
と各10万トランザクションでも実行してみました。
結果は以下の通りです。
-c 10 -t 1000 (1分間くらい) | -c 10 -t 100000 (50分間くらい) | |
---|---|---|
PostgreSQL | 6025809 bytes | 718227554 bytes |
OrioleDB | 4029611 bytes (66.9%) | 399675708 bytes (55.6%) |
劇的というほどではありませんが、50分間の試験では転送データ量を 55%にまで減らせています。この結果には、XID周回対策の VACUUM は含まれていません(テスト中には実行されませんでした)。また、本試験で PostgreSQL には wal_compresion = on
の設定を与えてあります。
なお、pgbench の初期データ投入時にも比較を行いましたが、こちらは転送データ量に大きな差はありませんでした。
もう少し調べたいこと
このテストは pgbench ですので、データ更新の内容が単調です。そのせいで OrioleDB が楽をしていて、転送データ量が少なく済んでいたかもしれません。他のワークロードでも調査が必要と思われます。また、OrioleDB でデータ圧縮をしたときの影響なども気になります。
データ圧縮と転送データ量
データ圧縮については稿を改めて書くつもりですが、データ圧縮を有効にした場合のレプリケーション転送データ量については、本稿に追記しておきます。結論としては影響無しです。データ圧縮しても転送データ量は一緒でした。row level WAL は圧縮対象にはなりません。