Commit 03fe78cc authored by Josef Bacik's avatar Josef Bacik Committed by David Sterba

btrfs: use delalloc_bytes to determine flush amount for shrink_delalloc

We have been hitting some early ENOSPC issues in production with more
recent kernels, and I tracked it down to us simply not flushing delalloc
as aggressively as we should be.  With tracing I was seeing us failing
all tickets with all of the block rsvs at or around 0, with very little
pinned space, but still around 120MiB of outstanding bytes_may_used.
Upon further investigation I saw that we were flushing around 14 pages
per shrink call for delalloc, despite having around 2GiB of delalloc
outstanding.

Consider the example of a 8 way machine, all CPUs trying to create a
file in parallel, which at the time of this commit requires 5 items to
do.  Assuming a 16k leaf size, we have 10MiB of total metadata reclaim
size waiting on reservations.  Now assume we have 128MiB of delalloc
outstanding.  With our current math we would set items to 20, and then
set to_reclaim to 20 * 256k, or 5MiB.

Assuming that we went through this loop all 3 times, for both
FLUSH_DELALLOC and FLUSH_DELALLOC_WAIT, and then did the full loop
twice, we'd only flush 60MiB of the 128MiB delalloc space.  This could
leave a fair bit of delalloc reservations still hanging around by the
time we go to ENOSPC out all the remaining tickets.

Fix this two ways.  First, change the calculations to be a fraction of
the total delalloc bytes on the system.  Prior to this change we were
calculating based on dirty inodes so our math made more sense, now it's
just completely unrelated to what we're actually doing.

Second add a FLUSH_DELALLOC_FULL state, that we hold off until we've
gone through the flush states at least once.  This will empty the system
of all delalloc so we're sure to be truly out of space when we start
failing tickets.

I'm tagging stable 5.10 and forward, because this is where we started
using the page stuff heavily again.  This affects earlier kernel
versions as well, but would be a pain to backport to them as the
flushing mechanisms aren't the same.

CC: stable@vger.kernel.org # 5.10+
Reviewed-by: default avatarNikolay Borisov <nborisov@suse.com>
Signed-off-by: default avatarJosef Bacik <josef@toxicpanda.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent fcdef39c
...@@ -2779,10 +2779,11 @@ enum btrfs_flush_state { ...@@ -2779,10 +2779,11 @@ enum btrfs_flush_state {
FLUSH_DELAYED_REFS = 4, FLUSH_DELAYED_REFS = 4,
FLUSH_DELALLOC = 5, FLUSH_DELALLOC = 5,
FLUSH_DELALLOC_WAIT = 6, FLUSH_DELALLOC_WAIT = 6,
ALLOC_CHUNK = 7, FLUSH_DELALLOC_FULL = 7,
ALLOC_CHUNK_FORCE = 8, ALLOC_CHUNK = 8,
RUN_DELAYED_IPUTS = 9, ALLOC_CHUNK_FORCE = 9,
COMMIT_TRANS = 10, RUN_DELAYED_IPUTS = 10,
COMMIT_TRANS = 11,
}; };
int btrfs_subvolume_reserve_metadata(struct btrfs_root *root, int btrfs_subvolume_reserve_metadata(struct btrfs_root *root,
......
...@@ -493,6 +493,11 @@ static void shrink_delalloc(struct btrfs_fs_info *fs_info, ...@@ -493,6 +493,11 @@ static void shrink_delalloc(struct btrfs_fs_info *fs_info,
long time_left; long time_left;
int loops; int loops;
delalloc_bytes = percpu_counter_sum_positive(&fs_info->delalloc_bytes);
ordered_bytes = percpu_counter_sum_positive(&fs_info->ordered_bytes);
if (delalloc_bytes == 0 && ordered_bytes == 0)
return;
/* Calc the number of the pages we need flush for space reservation */ /* Calc the number of the pages we need flush for space reservation */
if (to_reclaim == U64_MAX) { if (to_reclaim == U64_MAX) {
items = U64_MAX; items = U64_MAX;
...@@ -500,22 +505,21 @@ static void shrink_delalloc(struct btrfs_fs_info *fs_info, ...@@ -500,22 +505,21 @@ static void shrink_delalloc(struct btrfs_fs_info *fs_info,
/* /*
* to_reclaim is set to however much metadata we need to * to_reclaim is set to however much metadata we need to
* reclaim, but reclaiming that much data doesn't really track * reclaim, but reclaiming that much data doesn't really track
* exactly, so increase the amount to reclaim by 2x in order to * exactly. What we really want to do is reclaim full inode's
* make sure we're flushing enough delalloc to hopefully reclaim * worth of reservations, however that's not available to us
* some metadata reservations. * here. We will take a fraction of the delalloc bytes for our
* flushing loops and hope for the best. Delalloc will expand
* the amount we write to cover an entire dirty extent, which
* will reclaim the metadata reservation for that range. If
* it's not enough subsequent flush stages will be more
* aggressive.
*/ */
to_reclaim = max(to_reclaim, delalloc_bytes >> 3);
items = calc_reclaim_items_nr(fs_info, to_reclaim) * 2; items = calc_reclaim_items_nr(fs_info, to_reclaim) * 2;
to_reclaim = items * EXTENT_SIZE_PER_ITEM;
} }
trans = (struct btrfs_trans_handle *)current->journal_info; trans = (struct btrfs_trans_handle *)current->journal_info;
delalloc_bytes = percpu_counter_sum_positive(
&fs_info->delalloc_bytes);
ordered_bytes = percpu_counter_sum_positive(&fs_info->ordered_bytes);
if (delalloc_bytes == 0 && ordered_bytes == 0)
return;
/* /*
* If we are doing more ordered than delalloc we need to just wait on * If we are doing more ordered than delalloc we need to just wait on
* ordered extents, otherwise we'll waste time trying to flush delalloc * ordered extents, otherwise we'll waste time trying to flush delalloc
...@@ -595,8 +599,11 @@ static void flush_space(struct btrfs_fs_info *fs_info, ...@@ -595,8 +599,11 @@ static void flush_space(struct btrfs_fs_info *fs_info,
break; break;
case FLUSH_DELALLOC: case FLUSH_DELALLOC:
case FLUSH_DELALLOC_WAIT: case FLUSH_DELALLOC_WAIT:
case FLUSH_DELALLOC_FULL:
if (state == FLUSH_DELALLOC_FULL)
num_bytes = U64_MAX;
shrink_delalloc(fs_info, space_info, num_bytes, shrink_delalloc(fs_info, space_info, num_bytes,
state == FLUSH_DELALLOC_WAIT, for_preempt); state != FLUSH_DELALLOC, for_preempt);
break; break;
case FLUSH_DELAYED_REFS_NR: case FLUSH_DELAYED_REFS_NR:
case FLUSH_DELAYED_REFS: case FLUSH_DELAYED_REFS:
...@@ -906,6 +913,14 @@ static void btrfs_async_reclaim_metadata_space(struct work_struct *work) ...@@ -906,6 +913,14 @@ static void btrfs_async_reclaim_metadata_space(struct work_struct *work)
commit_cycles--; commit_cycles--;
} }
/*
* We do not want to empty the system of delalloc unless we're
* under heavy pressure, so allow one trip through the flushing
* logic before we start doing a FLUSH_DELALLOC_FULL.
*/
if (flush_state == FLUSH_DELALLOC_FULL && !commit_cycles)
flush_state++;
/* /*
* We don't want to force a chunk allocation until we've tried * We don't want to force a chunk allocation until we've tried
* pretty hard to reclaim space. Think of the case where we * pretty hard to reclaim space. Think of the case where we
...@@ -1069,7 +1084,7 @@ static void btrfs_preempt_reclaim_metadata_space(struct work_struct *work) ...@@ -1069,7 +1084,7 @@ static void btrfs_preempt_reclaim_metadata_space(struct work_struct *work)
* so if we now have space to allocate do the force chunk allocation. * so if we now have space to allocate do the force chunk allocation.
*/ */
static const enum btrfs_flush_state data_flush_states[] = { static const enum btrfs_flush_state data_flush_states[] = {
FLUSH_DELALLOC_WAIT, FLUSH_DELALLOC_FULL,
RUN_DELAYED_IPUTS, RUN_DELAYED_IPUTS,
COMMIT_TRANS, COMMIT_TRANS,
ALLOC_CHUNK_FORCE, ALLOC_CHUNK_FORCE,
...@@ -1158,6 +1173,7 @@ static const enum btrfs_flush_state evict_flush_states[] = { ...@@ -1158,6 +1173,7 @@ static const enum btrfs_flush_state evict_flush_states[] = {
FLUSH_DELAYED_REFS, FLUSH_DELAYED_REFS,
FLUSH_DELALLOC, FLUSH_DELALLOC,
FLUSH_DELALLOC_WAIT, FLUSH_DELALLOC_WAIT,
FLUSH_DELALLOC_FULL,
ALLOC_CHUNK, ALLOC_CHUNK,
COMMIT_TRANS, COMMIT_TRANS,
}; };
......
...@@ -94,6 +94,7 @@ struct btrfs_space_info; ...@@ -94,6 +94,7 @@ struct btrfs_space_info;
EM( FLUSH_DELAYED_ITEMS, "FLUSH_DELAYED_ITEMS") \ EM( FLUSH_DELAYED_ITEMS, "FLUSH_DELAYED_ITEMS") \
EM( FLUSH_DELALLOC, "FLUSH_DELALLOC") \ EM( FLUSH_DELALLOC, "FLUSH_DELALLOC") \
EM( FLUSH_DELALLOC_WAIT, "FLUSH_DELALLOC_WAIT") \ EM( FLUSH_DELALLOC_WAIT, "FLUSH_DELALLOC_WAIT") \
EM( FLUSH_DELALLOC_FULL, "FLUSH_DELALLOC_FULL") \
EM( FLUSH_DELAYED_REFS_NR, "FLUSH_DELAYED_REFS_NR") \ EM( FLUSH_DELAYED_REFS_NR, "FLUSH_DELAYED_REFS_NR") \
EM( FLUSH_DELAYED_REFS, "FLUSH_ELAYED_REFS") \ EM( FLUSH_DELAYED_REFS, "FLUSH_ELAYED_REFS") \
EM( ALLOC_CHUNK, "ALLOC_CHUNK") \ EM( ALLOC_CHUNK, "ALLOC_CHUNK") \
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment