Introduce limit on size of L2ARC headers

Since L2ARC buffers are not evicted on memory pressure, too large
amount of headers on system with irrationally large L2ARC can render
it slow or even unusable.  This change limits L2ARC writes and
rebuild if unevictable L2ARC-only headers reach dangerous level.

While there, call arc_adapt() on L2ARC rebuild, so that it could
properly grow arc_c, reflecting potentially significant ARC size
increase and avoiding slow growth with hopeless eviction attempts
later when "overflow" is detected.

Reviewed-by: Ryan Moeller <ryan@iXsystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reported-by: Richard Elling <Richard.Elling@RichardElling.com>
Signed-off-by: Alexander Motin <mav@FreeBSD.org>
Closes #10765
This commit is contained in:
Alexander Motin 2020-08-25 17:33:36 -04:00 committed by GitHub
parent 47a3f3fc01
commit 523e1295fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 5 deletions

View File

@ -198,6 +198,20 @@ feature.
Default value: \fB200\fR%. Default value: \fB200\fR%.
.RE .RE
.sp
.ne 2
.na
\fBl2arc_meta_percent\fR (int)
.ad
.RS 12n
Percent of ARC size allowed for L2ARC-only headers.
Since L2ARC buffers are not evicted on memory pressure, too large amount of
headers on system with irrationaly large L2ARC can render it slow or unusable.
This parameter limits L2ARC writes and rebuild to achieve it.
.sp
Default value: \fB33\fR%.
.RE
.sp .sp
.ne 2 .ne 2
.na .na

View File

@ -823,6 +823,7 @@ unsigned long l2arc_feed_min_ms = L2ARC_FEED_MIN_MS; /* min interval msecs */
int l2arc_noprefetch = B_TRUE; /* don't cache prefetch bufs */ int l2arc_noprefetch = B_TRUE; /* don't cache prefetch bufs */
int l2arc_feed_again = B_TRUE; /* turbo warmup */ int l2arc_feed_again = B_TRUE; /* turbo warmup */
int l2arc_norw = B_FALSE; /* no reads during writes */ int l2arc_norw = B_FALSE; /* no reads during writes */
int l2arc_meta_percent = 33; /* limit on headers size */
/* /*
* L2ARC Internals * L2ARC Internals
@ -4988,9 +4989,6 @@ arc_adapt(int bytes, arc_state_t *state)
int64_t mrug_size = zfs_refcount_count(&arc_mru_ghost->arcs_size); int64_t mrug_size = zfs_refcount_count(&arc_mru_ghost->arcs_size);
int64_t mfug_size = zfs_refcount_count(&arc_mfu_ghost->arcs_size); int64_t mfug_size = zfs_refcount_count(&arc_mfu_ghost->arcs_size);
if (state == arc_l2c_only)
return;
ASSERT(bytes > 0); ASSERT(bytes > 0);
/* /*
* Adapt the target size of the MRU list: * Adapt the target size of the MRU list:
@ -9144,6 +9142,15 @@ l2arc_write_buffers(spa_t *spa, l2arc_dev_t *dev, uint64_t target_sz)
return (write_asize); return (write_asize);
} }
static boolean_t
l2arc_hdr_limit_reached(void)
{
int64_t s = aggsum_upper_bound(&astat_l2_hdr_size);
return (arc_reclaim_needed() || (s > arc_meta_limit * 3 / 4) ||
(s > (arc_warm ? arc_c : arc_c_max) * l2arc_meta_percent / 100));
}
/* /*
* This thread feeds the L2ARC at regular intervals. This is the beating * This thread feeds the L2ARC at regular intervals. This is the beating
* heart of the L2ARC. * heart of the L2ARC.
@ -9211,7 +9218,7 @@ l2arc_feed_thread(void *unused)
/* /*
* Avoid contributing to memory pressure. * Avoid contributing to memory pressure.
*/ */
if (arc_reclaim_needed()) { if (l2arc_hdr_limit_reached()) {
ARCSTAT_BUMP(arcstat_l2_abort_lowmem); ARCSTAT_BUMP(arcstat_l2_abort_lowmem);
spa_config_exit(spa, SCL_L2ARC, dev); spa_config_exit(spa, SCL_L2ARC, dev);
continue; continue;
@ -9662,7 +9669,7 @@ l2arc_rebuild(l2arc_dev_t *dev)
* online the L2ARC dev at a later time (or re-import the pool) * online the L2ARC dev at a later time (or re-import the pool)
* to reconstruct it (when there's less memory pressure). * to reconstruct it (when there's less memory pressure).
*/ */
if (arc_reclaim_needed()) { if (l2arc_hdr_limit_reached()) {
ARCSTAT_BUMP(arcstat_l2_rebuild_abort_lowmem); ARCSTAT_BUMP(arcstat_l2_rebuild_abort_lowmem);
cmn_err(CE_NOTE, "System running low on memory, " cmn_err(CE_NOTE, "System running low on memory, "
"aborting L2ARC rebuild."); "aborting L2ARC rebuild.");
@ -10002,6 +10009,13 @@ l2arc_log_blk_restore(l2arc_dev_t *dev, const l2arc_log_blk_phys_t *lb,
uint64_t size = 0, asize = 0; uint64_t size = 0, asize = 0;
uint64_t log_entries = dev->l2ad_log_entries; uint64_t log_entries = dev->l2ad_log_entries;
/*
* Usually arc_adapt() is called only for data, not headers, but
* since we may allocate significant amount of memory here, let ARC
* grow its arc_c.
*/
arc_adapt(log_entries * HDR_L2ONLY_SIZE, arc_l2c_only);
for (int i = log_entries - 1; i >= 0; i--) { for (int i = log_entries - 1; i >= 0; i--) {
/* /*
* Restore goes in the reverse temporal direction to preserve * Restore goes in the reverse temporal direction to preserve
@ -10538,6 +10552,9 @@ ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, feed_again, INT, ZMOD_RW,
ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, norw, INT, ZMOD_RW, ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, norw, INT, ZMOD_RW,
"No reads during writes"); "No reads during writes");
ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, meta_percent, INT, ZMOD_RW,
"Percent of ARC size allowed for L2ARC-only headers");
ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, rebuild_enabled, INT, ZMOD_RW, ZFS_MODULE_PARAM(zfs_l2arc, l2arc_, rebuild_enabled, INT, ZMOD_RW,
"Rebuild the L2ARC when importing a pool"); "Rebuild the L2ARC when importing a pool");