]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
btrfs: introduce the device layout aware per-profile available space
authorQu Wenruo <wqu@suse.com>
Wed, 4 Feb 2026 02:54:06 +0000 (13:24 +1030)
committerDavid Sterba <dsterba@suse.com>
Tue, 7 Apr 2026 16:55:53 +0000 (18:55 +0200)
commit52fead5eb8a743384bab24ca8c3695257c755f0f
tree85a0ca7616892aca0f528b49401858c55472d2d5
parent08ef56661f69d40081ef782cd6a162bb0777af74
btrfs: introduce the device layout aware per-profile available space

[BUG]
There is a long known bug that if metadata is using RAID1 on two disks
with unbalanced sizes, there is a very high chance to hit ENOSPC related
transaction abort.

[CAUSE]
The root cause is in the available space estimation code:

- Factor based calculation
  Just use all unallocated space, divide by the profile factor
  One obvious user is can_overcommit().

This can not handle the following example:

  devid 1 unallocated: 1GiB
  devid 2 unallocated: 50GiB
  metadata type: RAID1

If using factor based estimation, we can use (1GiB + 50GiB) / 2 = 25.5GiB
free space for metadata.
Thus we can continue allocating metadata (over-commit) way beyond the
1GiB limit.

But this estimation is completely wrong, in reality we can only allocate
one single 1GiB RAID1 block group, thus if we continue over-commit, at
one time we will hit ENOSPC at some critical path and flips the fs
read-only.

[SOLUTION]
This patch will introduce per-profile available space estimation,
which can provide chunk-allocator like behavior to give a (mostly)
accurate result, with under-estimate corner cases.

There are some differences between the estimation and real chunk
allocator:

- No consideration on hole size
  It's fine for most cases, as all data/metadata strips are in 1GiB size
  thus there should not be any hole wasting much space.

  And chunk allocator is able to use smaller stripes when there is
  really no other choice.

  Although in theory this means it can lead to some over-estimation, it
  should not cause too much hassle in the real world.

  The other benefit of such behavior is, we avoid dev-extent tree search
  completely, thus the overhead is very small.

- No true balance for certain cases
  If we have 3 disks RAID1, and each device has 2GiB unallocated space,
  we can load balance the chunk allocation so that we can allocate 3GiB
  RAID1 chunks, and that's what chunk allocator will do.

  But this current estimation code is using the largest available space
  to do a single allocation. Meaning the estimation will be 2GiB, thus
  under estimate.

  Such under estimation is fine and after the first chunk allocation, the
  estimation will be updated and still give a correct 2GiB
  estimation.
  So this only means the estimation will be a little conservative, which
  is safer for call sites like metadata over-commit check.

With that facility, for above 1GiB + 50GiB case, it will give a RAID1
estimation of 1GiB, instead of the incorrect 25.5GiB.

Or for a more complex example:
  devid 1 unallocated: 1T
  devid 2 unallocated:  1T
  devid 3 unallocated: 10T

We will get an array of:
  RAID10: 2T
  RAID1: 2T
  RAID1C3: 1T
  RAID1C4: 0  (not enough devices)
  DUP: 6T
  RAID0: 3T
  SINGLE: 12T
  RAID5: 2T
  RAID6: 1T

[IMPLEMENTATION]
And for the each profile , we go chunk allocator level calculation:
The pseudo code looks like:

  clear_virtual_used_space_of_all_rw_devices();
  do {
   /*
    * The same as chunk allocator, despite used space,
    * we also take virtual used space into consideration.
    */
   sort_device_with_virtual_free_space();

   /*
    * Unlike chunk allocator, we don't need to bother hole/stripe
    * size, so we use the smallest device to make sure we can
    * allocated as many stripes as regular chunk allocator
    */
   stripe_size = device_with_smallest_free->avail_space;
stripe_size = min(stripe_size, to_alloc / ndevs);

   /*
    * Allocate a virtual chunk, allocated virtual chunk will
    * increase virtual used space, allow next iteration to
    * properly emulate chunk allocator behavior.
    */
   ret = alloc_virtual_chunk(stripe_size, &allocated_size);
   if (ret == 0)
   avail += allocated_size;
  } while (ret == 0)

This minimal available space based calculation is not perfect, but the
important part is, the estimation is never exceeding the real available
space.

This patch just introduces the infrastructure, no hooks are executed
yet.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/volumes.c
fs/btrfs/volumes.h