]> git.hungrycats.org Git - linux/commitdiff
btrfs: raid56: adaptive park deadline from the parked-rbio backlog
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Tue, 11 Aug 2026 06:53:38 +0000 (02:53 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:03 +0000 (17:40 -0400)
Full-stripe batching parks a sub-stripe write's rbio for a deadline so the
writes that fill the rest of its stripe can merge into it, turning a
read-modify-write into one full-stripe write.  The deadline is a fixed
per-filesystem value.  It is long enough to catch those merges, but at a
small-file fill to ENOSPC -- where a run's stripes never fill because each
file's neighbours land in the next file's stripe rather than this one --
every partial write waits the deadline out, and then its 10x stuck cap, on
an arrival that never comes before padding to a full stripe and going down.
Lowering the deadline globally fixes that fill but throws away the merges
the deadline exists to catch on ordinary moderate writes.

Feed the deadline back from the parking machinery's own backlog instead of
guessing at the workload.  Track stripe_parked_now, the number of
currently-parked rbios, incremented as a partial write parks and
decremented as it unparks.  When it runs ahead of the stripe_park_congestion
knob (0 disables it, the default), a new async partial write takes the short
sync deadline through the existing sync-park path rather than the full one.
This is negative feedback: the shorter deadline drains the backlog, so once
it falls back below the knob later writes regain the full deadline and full-
stripe batching.  No space-state estimate is needed -- a small-file flood
builds the backlog directly, while a large sequential fill (already full
stripes, never parked) and a moderate steady stream do not.

Measured on an eight-device raid5.  A small-file balance-reclaim soak that
times out at the default deadline completes at a congestion of 4; on a
throttled moderate stream, where the backlog stays low, a congestion of 32
never trips, keeps the full deadline, and merges three times as much with
38% fewer RMW reads as the same filesystem under a flat 3ms deadline.  The
gauge and a congestion_short counter join the stripe_park_stats sysfs file
so the knob can be set from measurement.  Off by default, and no data-path
change: a shortened park still pads or reads exactly as it would have, only
sooner.

Assisted-by: Claude:claude-opus-4-8
fs/btrfs/fs.h
fs/btrfs/raid56.c
fs/btrfs/sysfs.c

index 9216300e2f4fc189f8a5a3d679d9eadd83aa9d59..985dc2c6b3e2ad3e300ff892121ea2aab3e7b846 100644 (file)
@@ -827,6 +827,7 @@ struct btrfs_fs_info {
                atomic64_t meta_rmw_cur;
                atomic64_t meta_rmw_old;
                atomic64_t meta_rmw_free;
+               atomic64_t congestion_short;
        } stripe_park_stats;
        /* BTRFS_STRIPE_RMW_* masks; see btrfs_stripe_allow_rmw(). */
        u32 stripe_rmw_opt;
@@ -839,6 +840,10 @@ struct btrfs_fs_info {
         */
        u32 stripe_park_timeout_ms;
        u32 stripe_park_sync_timeout_ms;
+       /* Parked-rbio backlog above which parks go short; 0 = off. */
+       u32 stripe_park_congestion;
+       /* Live count of currently-parked rbios (the backlog gauge). */
+       atomic_t stripe_parked_now;
 
        /* Private scrub information */
        struct mutex scrub_lock;
index 60d741bdf01af5bcecbd17e429f9e9a590912922..59d5d04c02637b994d8f0e2b50c35d2d67dd3a3e 100644 (file)
@@ -765,6 +765,7 @@ static void rbio_unpark_locked(struct btrfs_raid_bio *rbio)
 {
        list_del_init(&rbio->parked_node);
        clear_bit(RBIO_PARKED_BIT, &rbio->flags);
+       atomic_dec(&rbio->bioc->fs_info->stripe_parked_now);
 }
 
 /* Does any bio gathered in @rbio carry a sync hint (a blocked waiter)? */
@@ -853,6 +854,29 @@ static bool rbio_try_park(struct btrfs_raid_bio *rbio)
            timeout_ms == sync_timeout_ms)
                return false;
 
+       /*
+        * Park-congestion feedback.  stripe_parked_now is the live count of
+        * parked rbios.  When it runs ahead of the stripe_park_congestion knob
+        * (0 = off) the park machinery is not clearing its backlog -- merges
+        * are not arriving fast enough, the regime a small-file / partial-write
+        * fill produces -- so collapse this write's deadline to the short sync
+        * value (the sync-park path below flushes at it, skipping the stuck
+        * cap).  Negative feedback: the shorter deadline drains the backlog, so
+        * once it falls back under the knob new writes regain the full deadline
+        * and full-stripe batching.  No space-state assumption.
+        */
+       {
+               unsigned int congestion =
+                       READ_ONCE(fs_info->stripe_park_congestion);
+
+               if (timeout_ms != sync_timeout_ms && sync_timeout_ms != 0 &&
+                   congestion &&
+                   atomic_read(&fs_info->stripe_parked_now) > (int)congestion) {
+                       timeout_ms = sync_timeout_ms;
+                       atomic64_inc(&fs_info->stripe_park_stats.congestion_short);
+               }
+       }
+
        spin_lock(&table->parked_lock);
        set_bit(RBIO_PARKED_BIT, &rbio->flags);
        set_bit(RBIO_WAS_PARKED_BIT, &rbio->flags);
@@ -862,6 +886,7 @@ static bool rbio_try_park(struct btrfs_raid_bio *rbio)
        rbio->park_stuck_deadline = jiffies +
                msecs_to_jiffies(timeout_ms * 10);
        list_add_tail(&rbio->parked_node, &table->parked);
+       atomic_inc(&fs_info->stripe_parked_now);
        spin_unlock(&table->parked_lock);
        atomic64_inc(&fs_info->stripe_park_stats.parked);
        queue_delayed_work(system_percpu_wq, &table->parked_work,
index bf38732b4c650c9dff7af61d1af65a6419440bb6..bd96e81fa5eddcbc8a3e0de8419f5c6c3b22f073 100644 (file)
@@ -1248,7 +1248,9 @@ static ssize_t btrfs_stripe_park_stats_show(struct kobject *kobj,
                "meta_rmw %lld\n"
                "meta_rmw_cur %lld\n"
                "meta_rmw_old %lld\n"
-               "meta_rmw_free %lld\n",
+               "meta_rmw_free %lld\n"
+               "congestion_short %lld\n"
+               "parked_now %d\n",
                atomic64_read(&fs_info->stripe_park_stats.parked),
                atomic64_read(&fs_info->stripe_park_stats.merged_bytes),
                atomic64_read(&fs_info->stripe_park_stats.filled),
@@ -1266,7 +1268,9 @@ static ssize_t btrfs_stripe_park_stats_show(struct kobject *kobj,
                atomic64_read(&fs_info->stripe_park_stats.meta_rmw),
                atomic64_read(&fs_info->stripe_park_stats.meta_rmw_cur),
                atomic64_read(&fs_info->stripe_park_stats.meta_rmw_old),
-               atomic64_read(&fs_info->stripe_park_stats.meta_rmw_free));
+               atomic64_read(&fs_info->stripe_park_stats.meta_rmw_free),
+               atomic64_read(&fs_info->stripe_park_stats.congestion_short),
+               atomic_read(&fs_info->stripe_parked_now));
 }
 BTRFS_ATTR(, stripe_park_stats, btrfs_stripe_park_stats_show);
 
@@ -1303,6 +1307,7 @@ BTRFS_ATTR_RW(, _name, btrfs_##_name##_show, btrfs_##_name##_store)
 
 BTRFS_PARK_TIMEOUT_ATTR(stripe_park_timeout_ms, stripe_park_timeout_ms);
 BTRFS_PARK_TIMEOUT_ATTR(stripe_park_sync_timeout_ms, stripe_park_sync_timeout_ms);
+BTRFS_PARK_TIMEOUT_ATTR(stripe_park_congestion, stripe_park_congestion);
 
 
 
@@ -1720,6 +1725,7 @@ static const struct attribute *btrfs_attrs[] = {
        BTRFS_ATTR_PTR(, stripe_park_stats),
        BTRFS_ATTR_PTR(, stripe_park_timeout_ms),
        BTRFS_ATTR_PTR(, stripe_park_sync_timeout_ms),
+       BTRFS_ATTR_PTR(, stripe_park_congestion),
        BTRFS_ATTR_PTR(, temp_fsid),
 #ifdef CONFIG_BTRFS_EXPERIMENTAL
        BTRFS_ATTR_PTR(, offload_csum),