]> 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>
Mon, 7 Sep 2026 11:33:59 +0000 (07:33 -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 e74cbab0f5715bc0ecf1582b4464dd07dd965844..6c8e6cf02bdc0e88ee4b6da7686d19bf8f0ce373 100644 (file)
@@ -432,6 +432,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) {
@@ -608,14 +614,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;
 }
 
@@ -643,7 +657,7 @@ again:
                if (((used + ticket->bytes <= space_info->total_bytes) ||
                     btrfs_can_overcommit(fs_info, space_info, 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);
                        ticket->bytes = 0;
@@ -1889,7 +1903,8 @@ static int __reserve_bytes(struct btrfs_fs_info *fs_info,
        if (!pending_tickets &&
            ((used + orig_bytes <= space_info->total_bytes) ||
             btrfs_can_overcommit(fs_info, space_info, 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;
        }