いつも個人のプロジェクトではPostgreSQLを使っており、自宅サーバで大きいデータベースを運用しています。最近まではmdadmで組んだRAID-5の上に、ext4でファイルシステムを作って、その中にデータベースのテーブルスペースを置いていました。
しかし、最近ZFSを使い始め、同じようにRAID-Zでファイルシステムを作ってテーブルスペースを置くようになりチューニングを再確認していたのですが、少し気になることが出てきたので調べてみました。
PostgreSQLのshared_buffers設定
PostgreSQLの設定には、shared_buffersというものがあり、搭載されているメモリから共有メモリを確保して、PostgreSQLの各プロセスが利用しています。この値は
1GB以上のRAMを載せた専用データベースサーバを使用している場合、shared_buffersに対する妥当な初期値はシステムメモリの25%です。 shared_buffersをこれよりも大きな値に設定することが有効なワークロードもあります。 しかし、PostgreSQLはオペレーティングシステムキャッシュにも依存するため、shared_buffersにRAMの40%以上を割り当てても、それより小さい値の時より動作が良くなる見込みはありません。(PostgreSQL日本語ドキュメント 19.4. 資源の消費より)
とあり、あまり大きくしても効果が無いとまで書かれています。
実際にext4でテーブルスペースを管理していた時は、この設定でhtopをみると搭載メモリの利用状況を示すバーの約25%が緑色、約75%が黄色となり、キャッシュが有効に働いていることが確認できました。
ZFSのARC設定
ZFSにはARCと呼ばれるメモリ上のキャッシュ機構を持っています。これは空きメモリから使用され、ディスクから読み出した際にARCにも格納され、同じデータの読み出しが発生した場合はディスクを参照せずここから読み出すことによって高速化を行います。ZFSを開発したSunのエンジニアが、MySQLで使う場合のチューニング指標として
ZFS上でMySQL/InnoDBを利用する場合、利用されるキャッシュにはいくつかの階層が存在します。InnoDB自身がバッファプールを持っていますし、ZFSにはARCがあります。そして、それらのキャッシュは個別に何をキャッシュまたはフラッシュすべきかということを判断するようになっています。そして、それらが同じデータをキャッシュするというような状況が生じてしまうこともあるでしょう。InnoDB内でキャッシュすると、データへたどり着くまでにより短い(そして高速な)コードパスで済むでしょう。そして、InnoDB内でキャッシュミスが生じれば、例えそのデータがARCに存在していたとしてもダーティページのフラッシュが生じてしまうことになります。これは不要な書き込みを生じさせる原因となります。ARCは利用可能なメモリの容量によって(自)動的に縮退・拡張しますが、単にそれを制限する方がより効率的です。我々が実施したテストでは、ZFS内でキャッシュするよりInnoDB内でデータをキャッシュしたほうが7〜200%の性能向上が見込めることを確認しています。(翻訳を紹介している漢(オトコ)のコンピュータ道: 違いが分かるエンジニアのためのMySQL/InnoDB/ZFSチューニング!より)
と紹介されています。このため、データベースでZFSを利用する場合ARCのキャッシュ情報はallではなくmetadataのみをキャッシュするように設定するという紹介をあちこちで見かけます。
鵜呑みにして設定
わたしもこれを読んで同じように設定していたのですが、思った以上にパフォーマンスが上がらず、はてどうしたものかと悩んでいました。そしてふと気が付いたのが、htopで表示されるメモリの使用状況バーは搭載メモリの25%だけが緑でキャッシュが全く使われていない状況でした。つまりメモリの25%しか使っていない状態でした。
ということは、ext4ではOSの標準設定によりキャッシュが働いているからshared_buffersを増やさなくても良いということなのですが、ZFSでキャッシュを最低限にしている場合、shared_buffersを確保した方がいいのではないか?ということです。
そこで、PostgreSQLにおいてZFSを使用する場合、shared_buffersを増やしたときと、ARCのすべてキャッシュするときのベンチマークを取ってみました。
以下の環境でテストを行いました。
- Core i7-6700(3.4GHz) Windows 10 Pro上のHyper-Vで実行されるCentOS7
- カーネル 3.10.0-957.1.3.el7.x86_64
- CPU割り当て4
- メモリ 4GB(Hyper-Vの動的メモリは使用しない)
- ZFS 0.7.12(RPMからインストール)
zfs set recordsize=128K tank(初期値)
zfs set compression=off tank(初期値)
zfs set dedup=off tank(初期値)
- PostgreSQL 11.1(ソースからビルド)
- pgbench -i -s 100 bench(1000万件)
- SELECT pg_database_size('bench'); → 1,576,528,895(データベースサイズは約1.5GB)
- 設定変更後に再起動。一度pgbench -l -T 60 -c 2 -j 2 -P 10 benchで60秒間のベンチを回して、その後5回分のトランザクション数を記録
テストパターンは以下の4種類です。
- 両方鵜呑みにする
shared_buffers=1GB
zfs set primarycache=metadata tank
- ZFSの言うとおりにデータベースのキャッシュを増やす
shared_buffers=3GB
zfs set primarycache=metadata tank
- PostgreSQLの言うとおりにファイルシステムのキャッシュを増やす
shared_buffers=1GB
zfs inherit primarycache tank(デフォルトがallなので)
- 一応両方増やす(サイズ的にshared_buffersが優先になるはず)
shared_buffers=3GB
zfs inherit primarycache tank
結果
回数\パターン | 1 | 2 | 3 | 4 |
1 | 656 | 590 | 758 | 773 |
2 | 552 | 670 | 835 | 822 |
3 | 613 | 543 | 663 | 644 |
4 | 586 | 656 | 788 | 794 |
5 | 476 | 674 | 1016 | 987 |
平均値 | 577 | 627 | 812 | 804 |
中央値 | 586 | 656 | 788 | 794 |
ZFSのキャッシュをした方が良いという結果になりました。
パターン2はパターン4と同じになるかと思ったのですが、パターン1よりは性能が良いがパターン3より性能が出ないということで、やはりshared_buffersの設定は増やしすぎても効果が無いようです。
またパターン3とパターン4でも差がないことからshared_buffersを増やすぐらいならARCに回した方が良いという結果になりました。
MySQLでZFSを使うシーンに出会っていないので、今回はPostgreSQLの設定だけですが、やはり実際に測ってみないと分からないものだなと感じました。
ちなみに、ZFSのレコードサイズをデータベースとあわせた方が良いという意見を見かけるので、8Kと16Kを試してみたのですが、
zfs set recordsize=8K tank
回数\パターン | 1 | 2 | 3 | 4 |
1 | 696 | 688 | 681 | 689 |
2 | 701 | 688 | 690 | 704 |
3 | 686 | 674 | 631 | 716 |
4 | 627 | 621 | 639 | 654 |
5 | 683 | 633 | 678 | 657 |
平均値 | 679 | 661 | 664 | 684 |
中央値 | 686 | 674 | 678 | 689 |
zfs set recordsize=16K tank
回数\パターン | 1 | 2 | 3 | 4 |
1 | 678 | 609 | 682 | 683 |
2 | 713 | 543 | 682 | 688 |
3 | 543 | 612 | 604 | 668 |
4 | 592 | 682 | 680 | 663 |
5 | 667 | 690 | 689 | 717 |
平均値 | 639 | 627 | 667 | 684 |
中央値 | 667 | 612 | 682 | 683 |
大した有意差が見られず、また128Kの方が成績が良かったことから、うちでも8Kにしていたのを128Kに戻しました。
ARCが効きやすいレコードサイズの設定があるのかと思って検索してみたりしたのですが、それらしいドキュメントは見つけられませんでした。ソースをあたるしかなさそうです。