]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: charge relocation a margin per reservation, not per extent
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 5 Sep 2026 18:50:03 +0000 (14:50 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:26 +0000 (17:36 -0400)
The stripe margin is one full stripe per outstanding extent, collateral
for the tails a commit traps under delalloc that has not been placed
yet.  The data relocation inode charged it too, for extents that are
already allocated: relocating a group of 16-48K files inflated
bytes_stripe_margin to 150 MB, more than the claimable supply, so the
next cluster reservation failed at once and the reclaim trigger retried
the same six groups 130 times in 15 minutes while the margin swung
between zero and 150 MB with nothing dirty.

Do not charge the per-extent margin for the data relocation root.
Instead, when a relocation reservation is checked against the
whole-stripe supply, round it up to whole stripes and add one stripe of
collateral, which is what relocation's own open runs can lose at the
next commit.

Assisted-by: Claude:claude-fable-5
fs/btrfs/space-info.c

index e772fe1f5bc4aa619d314327f389b56f7e2e1bd0..a6c4fd1d598cccf27ab5239f00bec1954ee9824e 100644 (file)
@@ -455,6 +455,12 @@ void btrfs_stripe_margin_mod(struct btrfs_inode *inode, int mod)
 
        if (!unit || !sinfo || mod == 0)
                return;
+       /*
+        * Relocation's extents are already placed; its reservations are
+        * charged a margin of their own in stripe_claimable_admit().
+        */
+       if (btrfs_is_data_reloc_root(inode->root))
+               return;
        if (btrfs_test_opt(fs_info, STRIPE_ALLOC)) {
                /* charges and releases both live */
        } else if (mod > 0 || READ_ONCE(sinfo->bytes_stripe_margin) == 0) {
@@ -667,14 +673,22 @@ static void remove_ticket(struct btrfs_space_info *space_info,
  * FLUSH_DATA ticket retry).  A later phase adds bytes_stripe_open to the bound to
  * match statfs exactly and recover that space.
  */
-static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 bytes)
+static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 bytes,
+                                  bool reloc)
 {
        struct btrfs_fs_info *fs_info = space_info->fs_info;
+       const u64 unit = READ_ONCE(fs_info->stripe_margin_unit);
 
        if (!btrfs_test_opt(fs_info, STRIPE_ALLOC) ||
-           !(space_info->flags & BTRFS_BLOCK_GROUP_DATA) ||
-           !READ_ONCE(fs_info->stripe_margin_unit))
+           !(space_info->flags & BTRFS_BLOCK_GROUP_DATA) || !unit)
                return true;
+       /*
+        * Relocation carries no per-extent margin (its extents are already
+        * placed): charge it whole stripes plus one, the tail its own open
+        * runs can lose at the next commit.
+        */
+       if (reloc)
+               bytes = round_up(bytes, unit) + unit;
        return space_info->bytes_may_use + bytes <= space_info->bytes_stripe_claimable;
 }
 
@@ -702,7 +716,7 @@ again:
                /* Check and see if our ticket can be satisfied now. */
                if ((used_after <= space_info->total_bytes ||
                     can_overcommit(space_info, used, ticket->bytes, flush)) &&
-                   stripe_claimable_admit(space_info, ticket->bytes)) {
+                   stripe_claimable_admit(space_info, ticket->bytes, false)) {
                        btrfs_space_info_update_bytes_may_use(space_info, ticket->bytes);
                        remove_ticket(space_info, ticket, 0);
                        space_info->tickets_id++;
@@ -1942,7 +1956,8 @@ static int reserve_bytes(struct btrfs_space_info *space_info, u64 orig_bytes,
        if (!pending_tickets &&
            ((used + orig_bytes <= space_info->total_bytes) ||
             can_overcommit(space_info, used, orig_bytes, flush)) &&
-           stripe_claimable_admit(space_info, orig_bytes)) {
+           stripe_claimable_admit(space_info, orig_bytes,
+                                  flush == BTRFS_RESERVE_FLUSH_DATA_RELOC)) {
                btrfs_space_info_update_bytes_may_use(space_info, orig_bytes);
                ret = 0;
        }