[PERFORMANCE]INTERMEDIATE

L2ARCを足したら逆に遅くなった

`homenas`にRAM32GBのまま1TBのL2ARCを足したら、体感速度が足す前より悪化したことがあります。原因は、L2ARCの各エントリのヘッダ情報がARC(RAM)側に間借りしているという構造でした。ARCとL2ARCの関係、この見落としがちな制約、実務での判断フローを整理します。

homenasのRAMが32GBだった頃、「余っているSSDがあるから」という理由だけで1TBのL2ARCを足したことがあります。結果は体感速度の悪化でした。L2ARCのヒット率自体は上がっていたのに、全体としては足す前より遅くなっていたのです。

原因は、L2ARCの各エントリが持つヘッダ情報が、L2ARCデバイス側ではなくARC(つまりRAM)側に間借りしているという、あまり知られていない構造にあります。この記事では、ARCとL2ARCがそれぞれ何をキャッシュしているのか、なぜ「メモリを増やす」と「L2ARCを足す」は代替手段ではなく別の解決策なのかを整理し、実務でどちらを選ぶべきかの判断フローをまとめます。

ARCとは何か

ARC(Adaptive Replacement Cache)は、ZFSがディスクの代わりにRAM上に保持する読み込みキャッシュです。単純なLRU(最近使ったものを優先)ではなく、MRU(最近参照)とMFU(頻繁に参照)の2つのリストを動的なバランスで使い分ける適応型のアルゴリズムであることが名前の由来です。一度しか読まれないシーケンシャルスキャン(バックアップジョブなど)によってMFUの中身が押し出されてしまう、という単純なLRUの弱点をある程度緩和できます。

デフォルトではARCの上限(zfs_arc_max)は物理RAMのおおむね半分程度に設定されますが、ディストリビューションやバージョンによって初期値は異なるため、専用ファイルサーバーであれば明示的に引き上げるのが基本です。ARCが大きいほど、ディスクまで行かずにRAMから返せる読み込みが増えます。

L2ARCはARCの「二次キャッシュ」であって代替ではない

L2ARC(Level 2 ARC)は、ARCから溢れて追い出される(evictされる)ブロックを、フラッシュデバイスなどの低レイテンシメディアに退避させておく仕組みです。ここで重要なのは、L2ARCはARCが足りているときには使われないという点です。ARCに収まっているブロックへの読み込みはL2ARCまで降りてきません。L2ARCが効くのは、ワーキングセットがRAM(ARC)には収まらないが、ディスクの物理限界よりは十分速いフラッシュには収まる、という中間的なケースに限られます。

zpool add tank cache /dev/nvme0n1

L2ARCデバイスの追加はこの1行で完結しますが、追加した瞬間に速くなるわけではありません。L2ARCは読み込まれた実績のあるブロックから徐々に埋まっていく仕組み(warm-up)のため、追加直後はヒット率がほぼゼロの状態から始まります。

読み込み要求が実際にどの経路をたどるかを整理すると、次のようになります。

読み込み要求の経路: ARC → L2ARC → ディスク

読み込み要求ARC (RAM)確認ARCヒットRAMから返却(µs級)hitmissL2ARC (フラッシュ)確認L2ARCヒットフラッシュから返却(数百µs級)hitmissディスク (HDD/SSD)両方missディスクから読み込み(ms級)読み込んだブロックは次回のためにARCへキャッシュされる

→ 3段のレイテンシ階層(RAM > フラッシュ > ディスク)のうち、上位ほど速いが容量は小さい。L2ARCが効くのは、ARCではヒットしないがディスクまでは行きたくない中間層の読み込みに限られる。

L2ARCのヘッダはRAMを消費するという構造

homenasで起きていたのはまさにこれでした。L2ARCに格納された各ブロックには、「どこに何が入っているか」を管理するためのヘッダ情報が必要で、このヘッダ自体はL2ARCデバイス側ではなくARC(つまりRAM)側に常駐します。

つまりL2ARCを大きくするほど、本来データキャッシュに使えるはずのARC容量の一部がヘッダ管理に食われていきます。当時の自分の環境で起きていたのはこの流れです。

  • L2ARCデバイス自体の読み込みヒット率は上がっていた
  • しかしヘッダ管理コストの増加によってARC本体の実効容量が圧迫され、ARC側のヒット率が下がっていた
  • トータルで見ると、L2ARCを足す前より体感性能が悪化していた

32GBのRAMに1TBのL2ARCは、今思えば比率として無茶でした。L2ARCのサイズはRAM容量とのバランスで決めるべきで、単に「余っているSSDの容量」を基準に決めるものではありません。

RAM(ARC)がL2ARCヘッダ管理に圧迫される様子(概念図)

L2ARCなしL2ARC未使用データ用 100%適正サイズのL2ARCRAM容量に対して妥当なサイズデータ用 85%ヘッダ管理用 15%RAMに対して過大なL2ARC「余っているSSDだから」で決めたサイズデータ用 55%ヘッダ管理用 45%
ARCデータ用(RAM) L2ARCヘッダ管理用(RAM消費)

※ 比率は傾向を示す概念図であり実測値ではない。実際の割合はブロックサイズ・RAM容量・L2ARCデバイスサイズに依存するため、実運用の判断は`arc_summary`の実測値に基づくこと。

メモリ増設かL2ARCか

実務でよく聞かれる「メモリを足すべきか、L2ARCを足すべきか」への回答は、ワーキングセットサイズと読み書き比率で概ね決まります。

ワークロードの状態推奨対応
ワーキングセットがRAM(ARC)にほぼ収まる、または収まりそうまずメモリ増設(zfs_arc_max引き上げ)を検討。L2ARCは不要
ワーキングセットがRAMを大きく超えるが、readヘビーL2ARC追加を検討。ただしRAM容量に対するヘッダオーバーヘッドを見込んだ上でサイズを決める
ワーキングセットがRAMを大きく超え、writeヘビーL2ARCはほぼ無力(読み込み専用キャッシュのため)。SLOGやvdev本数の見直しを検討
小ファイルが大量にあり、メタデータ参照が律速データ本体ではなくメタデータ専用のL2ARC(後述)を検討

原則として、コスト効率で見ればメモリ増設のほうが単純で効果も読みやすい選択肢です。L2ARCは「これ以上メモリを積めない、あるいは積むコストがSSDより高い」という制約がある場合の次善策と捉えるのが実務的です。

メタデータ専用のL2ARC

データ本体は大きすぎてL2ARCに乗り切らないが、ディレクトリ構造やinode相当の情報(メタデータ)へのアクセスが性能を左右するワークロード(大量の小ファイルを扱うファイルサーバーなど)では、データセット単位でキャッシュ対象をメタデータのみに絞る選択肢があります。

zfs set secondarycache=metadata tank/fileshare

secondarycacheall(デフォルト)・metadatanoneの3値を取ります。metadataにすると、L2ARC(および同じ設定が効く場合のARC)にはメタデータのみが乗り、データ本体のキャッシュ枠を消費しません。データ本体のキャッシュヒット率をほぼ捨ててでもメタデータ探索を高速化したい、という割り切った構成に向きます。

ヒット率を数字で確認する

判断を勘に頼らないために、実際のARC/L2ARCヒット率を確認する手段を押さえておきます。

arc_summary

arc_summary(ZFSonLinux/OpenZFSに付属)は、ARCサイズ・ヒット率・MRU/MFUの内訳・L2ARCのヒット率まで、現在のキャッシュ状態をまとめて表示してくれるツールです。L2ARCを追加した後の効果測定は、体感ではなくこの出力のL2 HitsL2 Missesの推移で判断します。

より細かくリアルタイムで追いたい場合はarcstat(多くのディストリビューションでパッケージ化されている)を使うと、hit%列とl2hit%列が1秒間隔などで流れて確認できます。生の統計値は/proc/spl/kstat/zfs/arcstats(Linux)からも取得可能です。

いずれの場合も、L2ARCを追加した直後はwarm-upが済んでいないため、判断は最低でも通常の業務サイクル(日次バッチや月次処理を含む)を1周させてからにするべきです。

再起動のたびに温め直す必要はない:persistent L2ARC

OpenZFS 2.0以降では、L2ARCの内容は再起動やプールのexport/importをまたいでも保持される(persistent L2ARC)ようになっています。古いバージョンの情報として「L2ARCは再起動のたびにゼロから温め直す必要がある」という説明を見かけることがありますが、現行のOpenZFSではこの制約は解消済みです。ただし、ディストリビューションが同梱するZFSのバージョンによっては挙動が異なるため、実際の挙動はzfs versionで確認したバージョンに基づいて判断してください。

結局あのL2ARCはどうしたか

homenasはその後RAMを128GBまで増設し、L2ARCは256GBまで縮小しました。今はarc_summaryのL2 Hitsを月イチくらいで眺めるだけの、大人しいキャッシュ層に落ち着いています。導入前に確認すべきだったのは、結局この5点でした。

  • ワーキングセットサイズ(現在のRAM/ARCに対してどの程度超過しているか)
  • 読み書き比率(L2ARCは読み込みにしか効かない)
  • 追加予定デバイスのRAM消費見込み(ヘッダオーバーヘッドとのバランス)
  • メタデータ律速か、データ本体律速か(secondarycacheの選択)
  • arc_summaryによるヒット率の現状把握(施策の前後で必ず比較する)

「読み込みが遅いのでL2ARCを足す」は、ワーキングセットとRAMの関係を確認しないまま行うと逆効果になり得ます。まずメモリ増設で足りるかを検討し、それでも足りない場合にRAMとのバランスを考えたL2ARCサイジングに進む、という順序を今は徹底しています。