Repository navigation
Conversation
adamguo0
force-pushed
the
l2arc_header_pruning
branch
from
October 14, 2026 14:41
13f0422 to
2ddf4a0
Compare
l2arc_meta_percent limits the memory used by L2ARC-only headers. Once the headers exceed it, the feed thread stops writing, but the headers that already exist remain. Old L2ARC entries are normally evicted as the feed thread advances the write hand, so eviction stops with the writes. The L2ARC header limit is a fraction of arc_c, but the L2ARC headers are not evictable by memory pressure. Before the ARC first comes under memory pressure, the limit is a fraction of arc_c_max instead, so the headers can grow to l2arc_meta_percent of arc_c_max. When memory pressure later lowers the ARC target, the headers exceed the limit and the feed thread stops writing. Importing a pool with an L2ARC can also drive header memory usage above l2arc_meta_percent. Once this happens, ZFS can neither evict the headers nor cache new data in the L2ARC until the ARC target grows enough to fit them, which may never happen. This commit introduces L2ARC header pruning. When the headers exceed the limit, prune the oldest L2ARC-only headers on the device until they fit, so that writes resume. Headers that still have an L1 part do not count toward the limit and are kept. That includes ghost headers, whose L2ARC copy may be the only cached copy of recently evicted data. A per-device marker keeps the scan position across lock drops and feed iterations, so kept headers are not rescanned on every pass. General memory pressure alone does not prune, since dropping headers frees little memory compared to the cache it discards. Pruning only forgets in-memory entries. The device hands and log blocks are untouched, so a later import may restore the entries, subject to the same limit. The new l2arc_hdr_prune_budget tunable bounds the headers each device examines per feed iteration, including those it keeps. Setting it to 0 disables pruning. The new l2arc_hdr_prune_batch_limit tunable sets how many headers pruning examines each time it takes a device's buffer list lock. The new l2_hdr_pruned arcstat counts pruned headers, and zarcsummary reports it as "Pruned headers". Signed-off-by: Adam Guo <adamguo@amazon.com>
adamguo0
force-pushed
the
l2arc_header_pruning
branch
from
October 15, 2026 14:43
2ddf4a0 to
202ce83
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
This change addresses #11702. Two prior attempts are #1612 and #1936, more on that later.
l2arc_meta_percent limits the memory used by L2ARC-only headers. Once the headers exceed it, the feed thread stops writing, but the headers that already exist remain. Old L2ARC entries are normally evicted as the feed thread advances the write hand, so eviction stops with the writes.
The L2ARC header limit is a fraction of arc_c, but the L2ARC headers are not evictable by memory pressure. Before the ARC first comes under memory pressure, the limit is a fraction of arc_c_max instead, so the headers can grow to l2arc_meta_percent of arc_c_max. When memory pressure later lowers the ARC target, the headers exceed the limit and the feed thread stops writing. Importing a pool with an L2ARC can also drive header memory usage above l2arc_meta_percent. Once this happens, ZFS can neither evict the headers nor cache new data in the L2ARC until the ARC target grows enough to fit them, which may never happen.
The fundamental problem is that the L2ARC header footprint can increase with arc_c, but once the headers are there they cannot decrease with arc_c. We get into a situation where system memory is permanently occupied by L2ARC headers. There is no memory headroom for (1) the regular ARC, or (2) feeding the L2ARC. Not only is there less/no room for ARC data, the L2ARC itself gradually becomes stale over time. The only mitigation is to delete L2ARC data, make more memory available to ARC, or remove/resize the L2ARC altogether. It would be much more preferable for ZFS to under-utilize the available L2ARC rather than get into this pathological low-performance state.
Description
This commit introduces L2ARC header pruning. When the headers exceed the limit, prune the oldest L2ARC-only headers on the device until they fit, so that writes resume. Headers that still have an L1 part do not count toward the limit and are kept. That includes ghost headers, whose L2ARC copy may be the only cached copy of recently evicted data. A per-device marker keeps the scan position across lock drops and feed iterations, so kept headers are not rescanned on every pass. General memory pressure alone does not prune, since dropping headers frees little memory compared to the cache it discards.
Pruning only forgets in-memory entries. The device hands and log blocks are untouched, so a later import may restore the entries, subject to the same limit.
In 2013, #1612 and #1936 proposed reclaiming header memory by advancing the write hand with l2arc_evict() until enough headers were freed. Both were carried into #2110, and the L2ARC patches were removed before it merged because they needed more testing. This change differs in three ways.
The new l2arc_hdr_prune_budget tunable bounds the headers each device examines per feed iteration, including those it keeps. Setting it to 0 disables pruning. The new l2arc_hdr_prune_batch_limit tunable sets how many headers pruning examines each time it takes a device's buffer list lock. The new l2_hdr_pruned arcstat counts pruned headers, and zarcsummary reports it as "Pruned headers".
How Has This Been Tested?
I added ZTS tests that exercise the new functionality to the l2arc suite, and ran the l2arc suite on an Amazon Linux 2023, kernel 6.12, aarch64 (EC2 m7g.4xlarge) machine.
I also built ZFS, created a pool, and messed around manually with l2arc_meta_percent and the new parameters to verify that (1) pruning takes place, (2) the new arcstats work, and (3) after lifting l2arc_meta_percent, l2arc feed resumes.
I also ran the QEMU CI test suite on Ubuntu 24.04 and FreeBSD 15.1 x86_64 virtual machines.
Types of Changes
Checklist
Signed-off-by.