ZFS を運用するにあたってのチップスです。
UNIX の伝統的なファイルシステムには atime というアクセスされた時刻を保持するフィールドがあります。 ZFS もこの機能をサポートしておりデフォルトでは on に設定されています。 しかし、Copy On Write を基本とする ZFS と atime は非常に相性が悪く、性能劣化を引き起こす要因になります。 なぜならば読み込み操作を行うたびに atime の更新という書き込み処理が発生し、全ての書き込みは Copy On Write で一度コピーが作成されるため、読み込み操作の数に比例して作業用にディスク容量を必要とすることになります。 性能面、資源面ともにデメリットとなりますので、off にしましょう。
なお、FreeBSD のインストーラから ZFS root でインストールした場合、インストーラが zroot の atime を off にします。 そのため、後から自分で追加したプールに対して設定が必要となります。
ただし、 zroot/var/mail だけは atime on に設定されます。tcsh などがメールの到着をチェックするためです。
ZFS は Copy On Write が基本です。 ファイルを削除する場合でもメタデータやディレクトリの書き換えが発生する分、コピーが発生します。 このときコピーできるだけの容量がプールになければファイルを削除することができません。 もし、プールを 100% 使い切ってしまうと、それはリードオンリーになることと同じ意味を持ちます。 そうなる前に不要なスナップショットやファイルを削除するようにしましょう。 もし不幸にも使用率が100%になってしまったら、プールにディスクを追加して容量を増やすか、より大きなプールに全データを移すしかありません。
経験的にはプールの使用率が 70〜80% をキープするくらいが安全と思われます。 もし、性能を厳しく追い求めるならば使用率を 50% くらいまでにすると空き領域の検索が速い分、高速に動作する可能性が高いです。
プールに追加するハードディスクはパーティションを切っておいたほうが良いです。
パーティションがないほうが性能面では有利になりますが、その差はわずかです。 長年運用を続けたあとにハードディスクを故障で交換する際にセクタ数まで含めて同じハードディスクを入手できるかどうかは分かりません。 おそらくより大容量のモデルを購入することになるでしょう。 その場合、予めパーティションを作成しておけば、同じサイズにパーティションを切り直すだけで、 交換ディスクとして使用することができます。 余ったパーティションを他の用途に使うこともできます。 ディスク障害から復旧したら後日、プールサイズの拡張などを検討すればよいのです。
ZFSのプールがあれば top(1) が ARC のサマリを表示します。 以下に表示のサンプルを示します。
ARC: 2864M Total, 884M MFU, 1793M MRU, 1696K Anon, 34M Header, 150M Other 2065M Compressed, 4466M Uncompressed, 2.16:1 Ratio
それぞれの項目の意味は次の通りです。
項目 | 内容 |
---|---|
Total | ARC全体のデータ量 |
MFU | 最もよく使われるデータとして保持している量 |
MRU | 最も間近に使われたデータとして保持している量 |
Anon | 一時的なデータとして保持している量 |
Header | ヘッダのデータ量 |
Other | その他のデータとして保持している量 |
Compressed | 圧縮した状態で保持しているデータ量 |
Uncompressed | 圧縮前のデータ量 |
Ratio | 圧縮率 |
デフォルトでは搭載メモリ量 - 1GB が ARC の最大値として割り当てられます。 つまり 8GB のメモリを搭載した環境では 7GB まで ARC に使われる可能性があります。
これを制限するパラメータが sysctl の vfs.zfs.arc.max 1)です。 アプリケーションにどの程度メモリを空けて置きたいのかを考えて、ARC を制限しましょう。
例えば 4GB に制限するには以下のようにします。
sysctl vfs.zfs.arc.max=4294967296
L2ARC は割り当てた容量の約 1.59% のメモリをその管理に使用します。 つまりキャッシュヒット率をあげようとして 1TB の SSD を割り当てたとすると
1 TB * 0.0159 = 15.9 GB
で約16GBのメモリを使用します。
メインメモリがこのサイズ以下の環境では満足な性能を得られないでしょう。
逆に L2ARC の管理に 1GB のメモリを割り当てると考えると、 逆算して 63GB の L2ARC を割り当てるべきということが分かります。
1 GB / 0.0159 = 62.89 GB
FreeBSD 12.0 現在、 L2ARC の内容は電源を落とすと失われます。 (将来的には改善される見込みです)
厳密には L2ARC のメディアに乗っている内容は保持されますが、 メモリ上の管理データを失いますので、その内容を再利用してくれません。
リブート直後は L2ARC は全てミスヒットすることを覚えておいてください。 必要ならばアプリケーションレベルでウォームアップを行ってください。
zfs では特定のユーザに対し、一部のファイルシステム操作を許可する設定ができます。 これを利用すると、例えばユーザのホームディレクトリの下に限って、zfs ファイルシステムの作成、マウント、スナップショットの作成、クローンの作成などを許可することができます。 許可の設定自体は root 権限が必要ですが、一度許可を与えてしまえば限られた範囲の操作はユーザ権限で行うことができます。
例えば、 `zpool/home/alice` にユーザ alice のホームディレクトリがあった場合、 alice に対して以下の操作を許可するようにします。
# zfs allow alice create,destroy,mount,clone,promote,rename,snapshot,rollback zpool/home/alice
権限の意味はそれぞれ zfs のサブコマンドに対応しています。 上記の例では ファイルシステムの作成、破棄、マウント、クローン、クローン元への依存を削除、リネーム、スナップショット作成、ロールバック、を alice に対して許可しています。
これでユーザ alice は自身のホームディレクトリの下であれば、自由に zfs のファイルシステムを作成・破棄することができます。
なお、FreeBSD ではユーザ権限でマウントを許可する際には `sysctl vfs.usermount=1` が必要です。
/etc/sysctl.conf に `vfs.usermount=1` を書くのを忘れないようにしましょう。
`vfs.usermount=1` を設定すると zfs だけでなく、 CD/DVD などのマウントや fuse によるマウントも権限があれば実行できるようになります。
zfs のバックアップにスナップショットを send & recv するのは定番とも言えますが、 ファイルシステムの使用量が大きくなるとバックアップに時間がかかるようになります。 ひょっとするとネットワークの不調などで途中で止まってしまうことがあるかもしれません。 その場合、最初からやり直すのはとても辛いため、途中からやり直すことができます。
zfs recv に -s オプションを渡すと recv が途中で止まった際にどこで止まったのかを示すトークンを保存してくれます。 トークンは recv したファイルシステムの receive_resume_token プロパティに保存されます。 このトークンを zfs send の -t オプションに渡すと、途中から再開してくれます。
以下にユーザ alice のホームディレクトリをバックアップする際の簡単な例を示します。
# zfs send zpool/home/alice | zfs recv -s zpool/backup/alice # ^C (一時停止) # zfs get -H -o value receive_resume_token zpool/backup/alice 1-d2524e736-d0-789c636064000310a500c4ec50360710e72765a526973030f0419460caa7a515a79680647679c0e4d990e4932a4b528b81b44640743536fd25f9e9a599290c0c193176fb8c54d81e3a20c97382e5f31273531918aa0af2f373f433f27353f52b4b339333328bf2f5cbf38bb21d8c0c0c2d740d0c758dcc196000003b291e6b # zfs send -t '1-d2524e736-d0-789c636064000310a500c4ec50360710e72765a526973030f0419460caa7a515a79680647679c0e4d990e4932a4b528b81b44640743536fd25f9e9a599290c0c193176fb8c54d81e3a20c97382e5f31273531918aa0af2f373f433f27353f52b4b339333328bf2f5cbf38bb21d8c0c0c2d740d0c758dcc196000003b291e6b' | zfs recv -s zpool/backup/alice
2回目以降の zfs send にはトークンのみを渡して、ファイルシステム名は渡しません。 zfs recv の受け側は必要に応じて ssh や nc などを利用するとネットワーク経由でバックアップすることができます。
dedup はファイルの中の重複箇所を自動的に検出し、zfsのブロック単位でまとめてくれる便利な機能ですが、反面ハッシュ値の保持や比較に多くのリソースを使用します。 dedup では1つのハッシュを保持するのに320bytesを消費するため、例えば1ブロック128kbで2TBのデータを保持したとすると5GBのメモリを使用します。
2,147,483,648 KB / 128 = 16,777,216 blocks 320 * 16,777,216 = 5,368,709,120 bytes = 5GB
もちろん、この値はARCとは無関係です。 そして全ての書き込みでハッシュ値との比較処理が走ります。 資源面、性能面ともに多くのリソースを必要としますので、通常は使わないほうが良いです。 また、dedup を有効にした後に書き込まれたファイルは常に dedup 用のハッシュ値を保持するようになります。 dedupを無効にしてもその後に書かれたファイルに対してハッシュ値の比較が行われなくなるだけで既に書き込まれたファイルは dedup の対象となります。 完全に無効化するにはファイルを再度コピーする必要があります。
もちろん、上記のリソースを確保した上で dedup を使用することには何の問題もありません。
zfs ではプールからブロックデバイスを生成する機能があるため、これを swap として利用することができます。 しかし、swap はメモリが逼迫したときに使用されるものですので、そもそも大量にメモリを使う zfs で swap を賄うのは矛盾した状態を生み出します。
例えば、zfs の ARC が増大しアプリケーションが使えるメモリが少なくなったために swap を使用するような場合、swap に書き込みが行われることで更に ARC を使用しますます使えるメモリが減ることにつながります。
swap を zfs 上に取るのは便利ではありますが、あまり得策とは言えません。
atime は off にしましょう。理由は前述の通りです。
PostgreSQL のデータ領域に ZFS を使う場合、record size を 8k にしましょう。 デフォルトでは 128k です。
例として zroot/postgres に対して record size を 8k にするには以下を実行します。
zfs set recordsize=8k zroot/postgres
ZFS は record size 毎にまとめてディスクアクセスを行います。 PostgreSQL は 8k 単位でディスクアクセスを行いますので、 PostgreSQL が 8k を書き込もうとしたときには ZFS は 128k サイズを読み込んだ上で 8k を変更し、それを 128k 単位で書き込もうとすることになり、効率が悪くなるのです。
log bias を throughput にすると、ZFS は書き込みデータをログデバイスに書き込むことをやめます。PostgreSQL には WAL がありますので、ZFS がログを書き込む意味はないと言っても良いでしょう。 無駄な書き込みがなくなる分、性能向上が期待できます。
zfs set logbias=throughput zroot/postgres
PostgreSQL はアプリケーション側で必要なデータをメモリ内にキャッシュしますので、 ZFS 側でデータをキャッシュする意味は少なくなります。 もし、ARC のサイズを小さくしたいならば、メタデータのみキャッシュするように変更することができます。
zfs set primarycache=metadata zroot/postgres
これにより性能向上が得られるかどうかはケースバイケースです。 PostgreSQL 側のバッファサイズなどと合わせて全体をチューニングした上で判断してください。
lz4 による圧縮は非常に高速に動作します。 圧縮率はそれほど良くはありませんが、半分程度に圧縮されれば、見た目のスループットは2倍に向上します。圧縮率が悪くても lz4 が高速に動作するためペナルティが少なく、設定するメリットは十分にあります。
zfs set compression=lz4 zroot/postgres
atime は off にしましょう。理由は前述の通りです。
データ用とログ用に2つのファイルシステムを用意します。 データ用には recordsize=16k を、ログ用はデフォルトの recordsize=128k を設定します。
用途 | recordsize |
---|---|
データ用 | 16k |
ログ用 | 128k (default) |
innodb はデータの読み書きを一度に 16k ずつ行うため、それに合わせます。 ログ用は追記のためデフォルトのままでかまいません。
innodb でもログを先行書き込みしますので zfs でログを書き込む必要はないとも言って良いでしょう。 データ用のファイルシステムに対し logbias=throuput を設定し zfs のログの書き込みを OFF にします。 ログ用については失われると困るため、デフォルトのまま logbias=latency が良いでしょう。
用途 | logbias |
---|---|
データ用 | throuput |
ログ用 | latency (default) |
MySQL もアプリケーション側で必要なデータをメモリ内にキャッシュします。 こちらを十分に効かせれば ZFS でキャッシュさせる必要性は低くなりますので、 primarycache=metadata を設定し、メタデータのキャッシュのみを行うようにします。
データ用、ログ用、どちらにも言えます。
用途 | primarycache |
---|---|
データ用 | metadata |
ログ用 | metadata |
lz4 による圧縮は非常に高速に動作します。 圧縮率はそれほど良くはありませんが、半分程度に圧縮されれば、見た目のスループットは2倍に向上します。圧縮率が悪くても lz4 が高速に動作するためペナルティが少なく、設定するメリットは十分にあります。
データ用、ログ用ともに compression=lz4 を設定しておくと良いでしょう。
用途 | compression |
---|---|
データ用 | lz4 |
ログ用 | lz4 |
my.cnf に skip_innodb_doublewrite を設定し MySQL にデータを2回書く処理を止めさせます。
これはもともと 16k のデータの内、半分の 8k だけはディスクに書かれたが残りの 8k は書かれていないようなタイミングで電源喪失などが発生した際にそれを検知するためのものです。 Copy On Write の ZFS ではどのようなタイミングでもこういう事にはならないため、単純に OFF にすることができます。 2回の書き込みが1回に減る分、性能向上が期待できます。
atime は off にしましょう。理由は前述の通りです。
MyISAM は一度に 8k ずつデータを読み書きしますので、recordsize=8k を指定します。 ログ用にディレクトリを分けることができませんので、データ用に合わせます。
MyISAM でもアプリケーション側で必要なデータをメモリ内にキャッシュします。 primarycache=metadata で zfs の arc 使用量を制限し、アプリケーション側で使えるメモリを増やします。
データとログが同じファイルシステムに置かれるため、安全のためデフォルトのまま logbias=latecy にするのが良いでしょう。
lz4 で得られるメリットは innodb と同じです。