* [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups
@ 2026-09-29 0:20 Boris Burkov
2026-09-29 0:20 ` [PATCH v2 1/2] btrfs: allocate additional SYSTEM space earlier Boris Burkov
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Boris Burkov @ 2026-09-29 0:20 UTC (permalink / raw)
To: linux-btrfs, kernel-team
A few fixes for system chunk allocation ENOSPCs caused by a sudden influx of a
large number of unused block groups in a filesystem with no free dev extents.
In practice, I saw this on a fully allocated fs with 30TB of space, a 4MiB
system chunk (single metadata mode) and ~10k unused data BGs.
- The first patch tries to avoid the condition by allocating earlier for the
system chunk.
- The second patch eagerly commits a single deletion when the system chunk is
at risk.
More details in the patches themselves.
Changelog:
v2:
- refactored some names and the flow in patch 1 to make it more clear.
Boris Burkov (2):
btrfs: allocate additional SYSTEM space earlier
btrfs: commit after deleting an unused block group if system space is
low
fs/btrfs/block-group.c | 59 +++++++++++++++++++++++++++++++++++-------
1 file changed, 49 insertions(+), 10 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] btrfs: allocate additional SYSTEM space earlier
2026-09-29 0:20 [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Boris Burkov
@ 2026-09-29 0:20 ` Boris Burkov
2026-09-29 0:20 ` [PATCH v2 2/2] btrfs: commit after deleting an unused block group if system space is low Boris Burkov
2026-09-29 14:54 ` [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Filipe Manana
2 siblings, 0 replies; 4+ messages in thread
From: Boris Burkov @ 2026-09-29 0:20 UTC (permalink / raw)
To: linux-btrfs, kernel-team
Currently, reserve_chunk_space() allocates a new system chunk when the
space left is smaller than the reservation for a single chunk tree
update.
Therefore, a filesystem in single metadata mode on a very large block
device can accumulate tens of thousands (TB) of chunks while never
allocating more than the original 4MiB SYSTEM block group created by
mkfs. If it then fills up (with large fallocates for example) and then
that data is freed, en-masse, we end up trying to delete thousands of
block groups in a single transaction in btrfs_delete_unused_bgs().
This process touches most of the nodes and leaves of the chunk tree,
essentially trying to allocate roughly double the current usage of the
chunk tree for cow. When this runs into needing a fresh system chunk,
the fs is full of the empty bgs and we abort with ENOSPC in
btrfs_remove_chunk() like:
BTRFS error (device loop0 state A): Transaction 30733 aborted (-ENOSPC)
space_info SYSTEM (sub-group id 0) has 0 free, is not full
space_info total=4194304, used=3276800, pinned=0, reserved=917504, may_use=0, readonly=0 zone_unusable=0
BTRFS: error (device loop0 state A) in btrfs_remove_chunk:3643: errno=-28 No space left
This was seen on a 30TiB production filesystem with 10TiB of unused
block groups and reproduces on a 30TiB sparse loop device: mkfs with
-m single, fallocate 1GiB files until ENOSPC, delete two of every three
adjacent files, sync.
To fix this, we should allocate the (relatively tiny) system chunks a
little bit more eagerly. If we do it when it is half full, we ensure we
can delete all of the block groups in one go. To fill up half of a 4MiB
system bg requires thousands of block_groups so this should only affect
very large fileystems.
Assisted-by: LLM
Signed-off-by: Boris Burkov <boris@bur.io>
---
fs/btrfs/block-group.c | 42 +++++++++++++++++++++++++++++++++---------
1 file changed, 33 insertions(+), 9 deletions(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 2eb09c9901c9..0a41b16ed270 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -1574,6 +1574,19 @@ static bool btrfs_link_bg_list(struct btrfs_block_group *bg, struct list_head *l
return added;
}
+/*
+ * Compute a rough bound on the bytes needed to modify every leaf in the chunk
+ * tree. Normally, we could just allocate a new system chunk then, but that can
+ * fail if there is no free space for a new dev extent. This can happen when
+ * we fill up a large filesystem then rapidly delete a large portion of it.
+ */
+static u64 system_space_target(struct btrfs_space_info *sinfo)
+{
+ lockdep_assert_held(&sinfo->lock);
+
+ return btrfs_space_info_used(sinfo, true) + sinfo->bytes_used;
+}
+
/*
* Process the unused_bgs list and remove any that don't have any allocated
* space inside of them.
@@ -1661,6 +1674,9 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
if (btrfs_is_block_group_used(block_group) ||
(block_group->ro && !(block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED)) ||
list_is_singular(&block_group->list) ||
+ ((block_group->flags & BTRFS_BLOCK_GROUP_SYSTEM) &&
+ space_info->total_bytes - block_group->length <
+ system_space_target(space_info)) ||
test_bit(BLOCK_GROUP_FLAG_FULLY_REMAPPED, &block_group->runtime_flags)) {
/*
* We want to bail if we made new allocations or have
@@ -1674,6 +1690,10 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
* next block group of this type would be created with a
* "single" profile (even if we're in a raid fs) because
* fs_info->avail_*_alloc_bits would be 0.
+ *
+ * Also bail out if this is a system block group that
+ * system_space_target() relies on to ensure head room for
+ * mass deletion.
*/
trace_btrfs_skip_unused_block_group(block_group);
spin_unlock(&block_group->lock);
@@ -4509,6 +4529,7 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans,
struct btrfs_fs_info *fs_info = trans->fs_info;
struct btrfs_space_info *info;
u64 left;
+ bool want_system_chunk, need_system_chunk;
int ret = 0;
/*
@@ -4520,15 +4541,17 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans,
info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_SYSTEM);
spin_lock(&info->lock);
left = info->total_bytes - btrfs_space_info_used(info, true);
+ want_system_chunk = info->total_bytes < system_space_target(info);
+ need_system_chunk = left < bytes;
spin_unlock(&info->lock);
- if (left < bytes && btrfs_test_opt(fs_info, ENOSPC_DEBUG)) {
+ if (need_system_chunk && btrfs_test_opt(fs_info, ENOSPC_DEBUG)) {
btrfs_info(fs_info, "left=%llu, need=%llu, flags=%llu",
left, bytes, type);
btrfs_dump_space_info(info, 0, false);
}
- if (left < bytes) {
+ if (want_system_chunk || need_system_chunk) {
u64 flags = btrfs_system_alloc_profile(fs_info);
struct btrfs_block_group *bg;
struct btrfs_space_info *space_info;
@@ -4572,13 +4595,14 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans,
}
}
- if (!ret) {
- ret = btrfs_block_rsv_add(fs_info,
- &fs_info->chunk_block_rsv,
- bytes, BTRFS_RESERVE_NO_FLUSH);
- if (!ret)
- trans->chunk_bytes_reserved += bytes;
- }
+ if (ret && need_system_chunk)
+ return;
+
+ ret = btrfs_block_rsv_add(fs_info,
+ &fs_info->chunk_block_rsv,
+ bytes, BTRFS_RESERVE_NO_FLUSH);
+ if (!ret)
+ trans->chunk_bytes_reserved += bytes;
}
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] btrfs: commit after deleting an unused block group if system space is low
2026-09-29 0:20 [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Boris Burkov
2026-09-29 0:20 ` [PATCH v2 1/2] btrfs: allocate additional SYSTEM space earlier Boris Burkov
@ 2026-09-29 0:20 ` Boris Burkov
2026-09-29 14:54 ` [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Filipe Manana
2 siblings, 0 replies; 4+ messages in thread
From: Boris Burkov @ 2026-09-29 0:20 UTC (permalink / raw)
To: linux-btrfs, kernel-team
If the device is fully allocated and the system space_info has no room
for a second copy of the chunk tree, deleting many unused block groups
in one transaction aborts it with -ENOSPC in btrfs_remove_chunk(). While
we can get ahead of this problem in practice by allocating the system
space more aggressively, that doesn't help a system that already got
into this state anyway. In particular, this would be a problem on
systems that got into this state on old code from before that
improvement, but it is also a backstop in case we do mess up and still
end up with too little system space.
The way we end up needing a lot of system chunk space in practice is if
we need to delete many bgs at once, resulting in touching many
nodes/leaves in the chunk tree. Therefore, if we detect we are in a
condition where we are low on system space, commit a single transaction
deleting a single bg, which should not need much system chunk space.
After this we have the freed bg and can proceed as usual. Any additional
chunk allocations (for any reason) will also check the system space and
are very likely to allocate more system space, so this should be a
relatively robust mechanism.
Assisted-by: LLM
Signed-off-by: Boris Burkov <boris@bur.io>
---
fs/btrfs/block-group.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 0a41b16ed270..c148b9d6e597 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -1596,6 +1596,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
LIST_HEAD(retry_list);
struct btrfs_block_group *block_group;
struct btrfs_space_info *space_info;
+ struct btrfs_space_info *system_info;
struct btrfs_trans_handle *trans;
const bool async_trim_enabled = btrfs_test_opt(fs_info, DISCARD_ASYNC);
int ret = 0;
@@ -1613,10 +1614,13 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
if (!mutex_trylock(&fs_info->reclaim_bgs_lock))
return;
+ system_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_SYSTEM);
+
spin_lock(&fs_info->unused_bgs_lock);
while (!list_empty(&fs_info->unused_bgs)) {
u64 used;
int trimming;
+ bool low;
block_group = list_first_entry(&fs_info->unused_bgs,
struct btrfs_block_group,
@@ -1864,7 +1868,18 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
btrfs_get_block_group(block_group);
}
end_trans:
- btrfs_end_transaction(trans);
+ /*
+ * If we are low on system space, commit immediately to get back
+ * unallocated space to greatly improve the chances of
+ * successfully allocating a system chunk.
+ */
+ spin_lock(&system_info->lock);
+ low = system_info->total_bytes < system_space_target(system_info);
+ spin_unlock(&system_info->lock);
+ if (unlikely(!ret && low))
+ ret = btrfs_commit_transaction(trans);
+ else
+ btrfs_end_transaction(trans);
next:
btrfs_put_block_group(block_group);
spin_lock(&fs_info->unused_bgs_lock);
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups
2026-09-29 0:20 [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Boris Burkov
2026-09-29 0:20 ` [PATCH v2 1/2] btrfs: allocate additional SYSTEM space earlier Boris Burkov
2026-09-29 0:20 ` [PATCH v2 2/2] btrfs: commit after deleting an unused block group if system space is low Boris Burkov
@ 2026-09-29 14:54 ` Filipe Manana
2 siblings, 0 replies; 4+ messages in thread
From: Filipe Manana @ 2026-09-29 14:54 UTC (permalink / raw)
To: Boris Burkov; +Cc: linux-btrfs, kernel-team
On Tue, Sep 29, 2026 at 1:21 AM Boris Burkov <boris@bur.io> wrote:
>
> A few fixes for system chunk allocation ENOSPCs caused by a sudden influx of a
> large number of unused block groups in a filesystem with no free dev extents.
>
> In practice, I saw this on a fully allocated fs with 30TB of space, a 4MiB
> system chunk (single metadata mode) and ~10k unused data BGs.
>
> - The first patch tries to avoid the condition by allocating earlier for the
> system chunk.
>
> - The second patch eagerly commits a single deletion when the system chunk is
> at risk.
>
> More details in the patches themselves.
>
> Changelog:
> v2:
> - refactored some names and the flow in patch 1 to make it more clear.
>
>
> Boris Burkov (2):
> btrfs: allocate additional SYSTEM space earlier
> btrfs: commit after deleting an unused block group if system space is
> low
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Thanks.
>
> fs/btrfs/block-group.c | 59 +++++++++++++++++++++++++++++++++++-------
> 1 file changed, 49 insertions(+), 10 deletions(-)
>
> --
> 2.55.0
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-29 14:54 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-29 0:20 [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Boris Burkov
2026-09-29 0:20 ` [PATCH v2 1/2] btrfs: allocate additional SYSTEM space earlier Boris Burkov
2026-09-29 0:20 ` [PATCH v2 2/2] btrfs: commit after deleting an unused block group if system space is low Boris Burkov
2026-09-29 14:54 ` [PATCH v2 0/2] btrfs: ENOSPC fixes for unused block groups Filipe Manana
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.