linux-raid.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Miroslav Cimerman <mc@raidlab.org>
To: Song Liu <song@kernel.org>, Yu Kuai <yukuai@fygo.io>
Cc: Li Nan <magiclinan@didiglobal.com>, Xiao Ni <xiao@kernel.org>,
	Tejun Heo <tj@kernel.org>, NeilBrown <neil@brown.name>,
	linux-raid@vger.kernel.org, linux-kernel@vger.kernel.org,
	Anton Gavriliuk <antosha20xx@gmail.com>
Subject: [PATCH] md/raid5: fix write stalls when a write is queued during a drain
Date: Sat, 14 Nov 2026 22:46:36 +0000	[thread overview]
Message-ID: <20261114224636.34362-1-mc@raidlab.org> (raw)

With continuous O_DIRECT writes to a RAID-5/6 array that uses
read-modify-writes, a few 4k writes can take seconds to complete, up
to the length of the run.

The pre-read permission (STRIPE_PREREAD_ACTIVE) belongs to a stripe.
A REQ_SYNC write queued while an earlier write on the same stripe is
being drained finds the bit set and gets no count of its own in
preread_active_stripes. handle_stripe() clears the bit when the drain
result is handled, so the queued write gets STRIPE_DELAYED, and the
stripe goes to delayed_list together with the completion of the
drained write. raid5_activate_delayed() only moves it on once
preread_active_stripes is below IO_THRESHOLD, which with other
REQ_SYNC writes in flight may happen until the load stops.

Check for writes queued on the stripe, under stripe_lock, after
clearing the bit, and keep the bit and its count if there are any.
A write queued later tests the bit after the clear and either takes
the permission itself or finds it kept. Do this for any queued write:
a queued buffered write would otherwise hold back the completion of
the drained write the same way.

md-7.4, RAM disks, 4k random writes, io_uring, QD64, 20 s, slowest
write without -> with this patch, md defaults (1 job) and
group_thread_cnt=8 (16 jobs):

  RAID-6 4+2 (rmw_level=2) 18.0 s -> 2.0 ms  19.3 s -> 5.8 ms
  RAID-6 8+2               13.4 s -> 2.2 ms  13.0 s -> 7.0 ms
  RAID-5 5+1               16.9 s -> 1.8 ms  18.3 s -> 4.9 ms
  RAID-5 9+1               17.4 s -> 2.2 ms  12.0 s -> 5.8 ms

IOPS are unchanged with md defaults and within a few percent with 8
workers. Buffered writes are not affected.

Fixes: e9c7469bb4f5 ("md: implment REQ_FLUSH/FUA support")
Cc: stable@vger.kernel.org
Signed-off-by: Miroslav Cimerman <mc@raidlab.org>
---
 drivers/md/raid5.c | 41 +++++++++++++++++++++++++++++++++++++++--
 drivers/md/raid5.h |  4 +++-
 2 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index efb4b0d37cb0..b103cc8d142a 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -5011,6 +5011,27 @@ static void break_stripe_batch_list(struct stripe_head *head_sh,
 		set_bit(STRIPE_HANDLE, &head_sh->state);
 }
 
+/*
+ * Check if a stripe head has any writes queued. Takes sh->stripe_lock under
+ * which writes are queued.
+ */
+static bool stripe_has_queued_writes(struct stripe_head *sh)
+{
+	bool queued = false;
+	int i;
+
+	spin_lock(&sh->stripe_lock);
+	for (i = 0; i < sh->disks; i++) {
+		if (sh->dev[i].towrite) {
+			queued = true;
+			break;
+		}
+	}
+	spin_unlock(&sh->stripe_lock);
+
+	return queued;
+}
+
 /*
  * handle_stripe - do things to a stripe.
  *
@@ -5165,8 +5186,24 @@ static void handle_stripe(struct stripe_head *sh)
 					set_bit(STRIPE_INSYNC, &sh->state);
 			}
 		}
-		if (test_and_clear_bit(STRIPE_PREREAD_ACTIVE, &sh->state))
-			s.dec_preread_active = 1;
+		if (test_and_clear_bit(STRIPE_PREREAD_ACTIVE, &sh->state)) {
+			/*
+			 * A write queued while the previous one was drained
+			 * has no pre-read permission of its own: a REQ_SYNC
+			 * write or discard found the bit set, other writes
+			 * never take it. Keep the permission for it, or it
+			 * gets STRIPE_DELAYED and the stripe, together with
+			 * the completion of the drained write, may wait on
+			 * delayed_list until preread_active_stripes drops
+			 * below IO_THRESHOLD. A write queued after
+			 * stripe_has_queued_writes() tests the bit after our
+			 * clear, so it takes the permission itself or finds
+			 * it kept.
+			 */
+			if (!stripe_has_queued_writes(sh) ||
+			    test_and_set_bit(STRIPE_PREREAD_ACTIVE, &sh->state))
+				s.dec_preread_active = 1;
+		}
 	}
 
 	/*
diff --git a/drivers/md/raid5.h b/drivers/md/raid5.h
index 1a61b474fc22..c3feb7779dbe 100644
--- a/drivers/md/raid5.h
+++ b/drivers/md/raid5.h
@@ -448,7 +448,9 @@ enum {
  * When preread is initiated on a stripe, we set PREREAD_ACTIVE and add
  * it to the count of prereading stripes.
  * When write is initiated, or the stripe refcnt == 0 (just in case) we
- * clear the PREREAD_ACTIVE flag and decrement the count
+ * clear the PREREAD_ACTIVE flag and decrement the count. When write is
+ * initiated and another write is already queued on the stripe, the flag and
+ * the count are kept for that write (see handle_stripe()).
  * Whenever the 'handle' queue is empty and the device is not plugged, we
  * move any strips from delayed to handle and clear the DELAYED flag and set
  * PREREAD_ACTIVE.

base-commit: 47e1b18f38d74015cb9fd30b5ff2a1eb88e77d31
-- 
2.53.0


                 reply	other threads:[~2026-11-14 22:48 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261114224636.34362-1-mc@raidlab.org \
    --to=mc@raidlab.org \
    --cc=antosha20xx@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-raid@vger.kernel.org \
    --cc=magiclinan@didiglobal.com \
    --cc=neil@brown.name \
    --cc=song@kernel.org \
    --cc=tj@kernel.org \
    --cc=xiao@kernel.org \
    --cc=yukuai@fygo.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).