]> git.hungrycats.org Git - linux/commitdiff
btrfs: raid56: pad sub-stripe writes to full stripes in open runs
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Thu, 30 Jul 2026 19:49:41 +0000 (15:49 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:01 +0000 (17:40 -0400)
A sub-stripe write does a full RMW: read every untouched data sector of
the stripe, recompute parity, write.  Under stripe-exclusive allocation
the read phase is usually pointless: a stripe covered by a live stripe
run has never been written at or past the run's allocation frontier, so
the sectors being read contain nothing.

When every data sector the rbio does not cover lies at or past the
frontier, zero-fill those sectors instead of reading them and write
them out with the stripe -- the zeros must reach the disk, or the
parity (computed over them) would not match what scrub reads back.  The
partial write becomes one full-stripe write: no read phase, one parity
pass.  A frontier that grows during the attempt is safe: the newer
allocation's write serializes behind this rbio's stripe lock and lands
over the zeros.  If any uncovered sector is below the frontier (already
allocated to someone else), fall back to the normal RMW.

Together with kicking parked rbios before the fast fsync's writeback
wait, this removes both stalls the raid56 layer added to fsync under
stripe_alloc: the park deadline and the RMW read round trip.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/raid56.c

index b558778fc3e84237dd60376452202ae1b56e87e3..d1662895810fc60dd37e58d063b764bee2ed9371 100644 (file)
@@ -891,6 +891,49 @@ bool btrfs_stripe_run_range_usable(struct btrfs_block_group *bg,
        return true;
 }
 
+/*
+ * For the raid56 pad-to-full optimization: a sub-stripe write into a
+ * stripe covered by a live stripe run may zero-fill (instead of read) any
+ * sector at or past the run's allocation frontier, because such sectors
+ * have never been written in the run's lifetime and, once the run closes,
+ * its stripe's tail cannot be reallocated while the stripe holds live
+ * data.  Returns true and sets *pad_from to the frontier when the stripe
+ * at [stripe_start, stripe_start + stripe_len) has a paddable tail.  The
+ * frontier can grow right after this returns; that is safe, because the
+ * later allocation's write serializes behind the caller's stripe lock
+ * and overwrites the zeros.
+ */
+bool btrfs_stripe_run_pad_start(struct btrfs_fs_info *fs_info,
+                               u64 stripe_start, u64 stripe_len,
+                               u64 *pad_from)
+{
+       struct btrfs_block_group *bg;
+       struct btrfs_open_stripe_run *run;
+       unsigned long flags;
+       bool ret = false;
+
+       if (list_empty_careful(&fs_info->open_stripe_bgs))
+               return false;
+       bg = btrfs_lookup_block_group(fs_info, stripe_start);
+       if (!bg)
+               return false;
+
+       spin_lock_irqsave(&bg->stripe_run_lock, flags);
+       list_for_each_entry(run, &bg->open_stripe_runs, list) {
+               if (stripe_start < run->start || stripe_start >= run->end)
+                       continue;
+               if (run->offset > stripe_start &&
+                   run->offset < stripe_start + stripe_len) {
+                       *pad_from = run->offset;
+                       ret = true;
+               }
+               break;
+       }
+       spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+       btrfs_put_block_group(bg);
+       return ret;
+}
+
 /* No open or draining run covers @bytenr any more. */
 static bool stripe_log_range_settled(struct btrfs_block_group *bg, u64 bytenr)
 {
index 6343635470425256b09113b5b008dcd56e7a2866..27d4bf7e33751d92525cb0a30d13540c0a97b628 100644 (file)
@@ -405,6 +405,9 @@ void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg);
 bool btrfs_stripe_in_open_run(struct btrfs_fs_info *fs_info, u64 logical);
 bool btrfs_stripe_run_range_usable(struct btrfs_block_group *bg,
                                   u64 run_start, u64 *run_len);
+bool btrfs_stripe_run_pad_start(struct btrfs_fs_info *fs_info,
+                               u64 stripe_start, u64 stripe_len,
+                               u64 *pad_from);
 void btrfs_log_settle_stripes(struct btrfs_inode *inode, u64 file_start,
                              u64 file_len, u64 bytenr, u64 num_bytes);
 int btrfs_stripe_alloc_check_support(struct btrfs_fs_info *fs_info);
index 756af2b49d457947df6780902b11a49ddbfca952..6f3a7d2db8c49ef1025224cb0c97906b3ae558d0 100644 (file)
  */
 #define RBIO_PARKED_BIT                4
 
+/*
+ * Set when a partial write rbio's uncovered data sectors were zero-filled
+ * instead of read (they lie at or past the covering stripe run's
+ * allocation frontier and have never been written).  The write path then
+ * writes them out with the stripe -- parity must match the disk, or
+ * scrub would flag the never-written sectors -- turning the partial
+ * write into a full-stripe write with no read phase.
+ */
+#define RBIO_PADDED_BIT                5
+
 #define RBIO_CACHE_SIZE 1024
 
 /*
@@ -1615,7 +1625,12 @@ static int rmw_assemble_write_bios(struct btrfs_raid_bio *rbio,
                        continue;
 
                if (stripe < rbio->nr_data) {
-                       sector = sector_in_rbio(rbio, stripe, sectornr, 1);
+                       /*
+                        * A padded rbio writes its zero-filled stripe sectors
+                        * too, so parity matches the disk (see RBIO_PADDED_BIT).
+                        */
+                       sector = sector_in_rbio(rbio, stripe, sectornr,
+                                       !test_bit(RBIO_PADDED_BIT, &rbio->flags));
                        if (!sector)
                                continue;
                } else {
@@ -2536,6 +2551,68 @@ static void submit_write_bios(struct btrfs_raid_bio *rbio,
        }
 }
 
+/*
+ * Try to avoid the RMW read phase of a sub-stripe write by zero-filling
+ * the uncovered data sectors.  Legal only when every uncovered sector
+ * lies at or past its stripe run's allocation frontier -- never written
+ * in the run's lifetime, and unreachable by other allocations while this
+ * stripe holds live data (see btrfs_stripe_run_pad_start()).  A frontier
+ * that grows under us is safe: the newer allocation's write serializes
+ * behind this rbio's stripe lock and lands over the zeros.
+ */
+static bool rmw_try_pad_full(struct btrfs_raid_bio *rbio)
+{
+       struct btrfs_fs_info *fs_info = rbio->bioc->fs_info;
+       const u64 stripe_start = rbio->bioc->full_stripe_logical;
+       const u32 sectorsize = fs_info->sectorsize;
+       const u64 stripe_len = (u64)rbio->nr_data * BTRFS_STRIPE_LEN;
+       u64 pad_from;
+       int i;
+
+       if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+               return false;
+       if (!btrfs_stripe_run_pad_start(fs_info, stripe_start, stripe_len,
+                                       &pad_from))
+               return false;
+
+       /* Populate bio_sectors so covered sectors are visible below. */
+       index_rbio_pages(rbio);
+
+       /* Every data sector this rbio does not cover must be paddable. */
+       for (i = 0; i < rbio->nr_data * rbio->stripe_nsectors; i++) {
+               const int stripe_nr = i / rbio->stripe_nsectors;
+               const int sectornr = i % rbio->stripe_nsectors;
+               u64 logical = stripe_start + stripe_nr * BTRFS_STRIPE_LEN +
+                             (u64)sectornr * sectorsize;
+
+               if (rbio->bio_sectors[i].has_paddr)
+                       continue;
+               if (logical < pad_from)
+                       return false;
+       }
+
+       if (alloc_rbio_data_pages(rbio) < 0)
+               return false;
+
+       for (i = 0; i < rbio->nr_data * rbio->stripe_nsectors; i++) {
+               struct sector_ptr *sector;
+               void *kaddr;
+
+               if (rbio->bio_sectors[i].has_paddr)
+                       continue;
+               sector = &rbio->stripe_sectors[i];
+               kaddr = kmap_local_sector(sector);
+               memset(kaddr, 0, sectorsize);
+               kunmap_local(kaddr);
+               sector->uptodate = true;
+       }
+
+       set_bit(RBIO_PADDED_BIT, &rbio->flags);
+       /* The whole stripe goes out: every vertical position is written. */
+       bitmap_set(&rbio->dbitmap, 0, rbio->stripe_nsectors);
+       return true;
+}
+
 /*
  * To determine if we need to read any sector from the disk.
  * Should only be utilized in RMW path, to skip cached rbio.
@@ -2585,8 +2662,22 @@ static void rmw_rbio(struct btrfs_raid_bio *rbio)
        /*
         * Either full stripe write, or we have every data sector already
         * cached, can go to write path immediately.
+        *
+        * Padding must win over the cache: a stolen cached rbio can carry
+        * all-uptodate pages from the stripe's PREVIOUS life -- before its
+        * extents were freed and the stripe was re-claimed -- and letting
+        * those pages skip the pad path bakes the cache's memory of dead
+        * data into fresh parity while only the bio sectors reach disk.
+        * If any on-disk column then diverges from that memory (device
+        * corruption, or simply free-space columns nobody rewrote), the
+        * stripe is latently unreconstructible.  A paddable rbio instead
+        * zero-fills and WRITES every uncovered column, making the stripe
+        * self-consistent on disk regardless of what the cache remembered;
+        * the claimed-stripe invariant (a stripe write depends on nothing
+        * on disk) requires this ordering.
         */
-       if (!rbio_is_full(rbio) && need_read_stripe_sectors(rbio)) {
+       if (!rbio_is_full(rbio) && !rmw_try_pad_full(rbio) &&
+           need_read_stripe_sectors(rbio)) {
                /*
                 * Now we're doing sub-stripe write, also need all data stripes
                 * to do the full RMW.