[PATCH] bcache: improve writeback throughput when frontend I/O is idle
[email protected] Tue, 7 Oct 2025 17:27:28 +0800
| Newsgroups | org.kernel.vger.linux-bcache |
|---|---|
| Message-ID | <[email protected]> |
From: Coly Li <[email protected]> Currently in order to write dirty blocks to backend device in LBA order for better performance, inside write_dirty() the I/O is issued only when its sequence matches current expected sequence. Otherwise the kworker will repeat check-wait-woken loop until the sequence number matches. When frontend I/O is idle, the writeback rate is set to INT_MAX, but the writeback thoughput doesn't increase much. There are two reasons, - The check-wait-woken loop is inefficient. - I/O depth on backing device is low. To improve the writeback throughput, this patch does two things, - Remove the check-wait-woken cycle from write_dirty() In read_dirty(), the read dirty bios are issued in LBA order and they are not completed in issue orders. The check-wait-woken loop makes sure these bios are ordered in LBA order again and issued to backing device, but indeed it is unncessary now. When all the bios are issued in a reasonable time windows, they can be properly merged or sorted by LBA address. mq-deadline does such stuff perfectly in benchmark, and bfq is just a bit less than mq-deadline, but all are much better than current check-wait-woken loops. - Read more dirty keys when frontend I/O is idle Define WRITEBACKS_IN_PASS (5), MAX_WRITEBACKS_IN_PASS (30) for write- back dirty keys in each pass, and define WRITESIZE_IN_PASS (5000) and MAX_WRITESIZE_IN_PASS (30000) for total writeback data size in each pass. When frontend I/O is idle, new values MAX_WRITEBACKS_IN_PASS and MAX_WRITESIZE_IN_PASS are used to issue more read-dirty bios on cache device and in true issue more writeback bios on backing device. On an 8 component disks md raid5 array, after applying this patch and when there is no frontend I/O for a while, the writeback throughput on backing device increases from 4MiB/s to 8MiB/s (because all cached dirty blocks are 4KiB), queue depth on the md raid5 array roughly increases from 21 to 55, and on each component disk the queue depth roughly increases from 1.5 to 4.5. Thanks to the developers of mq-deadline and bfq schedulers in these years. Signed-off-by: Coly Li <[email protected]> --- drivers/md/bcache/writeback.c | 57 ++++++++--------------------------- drivers/md/bcache/writeback.h | 6 ++-- 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/drivers/md/bcache/writeback.c b/drivers/md/bcache/writeback.c index e6c548e83ff1..20edde7152bf 100644 --- a/drivers/md/bcache/writeback.c +++ b/drivers/md/bcache/writeback.c @@ -322,7 +322,6 @@ static unsigned int writeback_delay(struct cached_dev *dc, struct dirty_io { struct closure cl; struct cached_dev *dc; - uint16_t sequence; struct bio bio; }; @@ -405,27 +404,6 @@ static CLOSURE_CALLBACK(write_dirty) { closure_type(io, struct dirty_io, cl); struct keybuf_key *w = io->bio.bi_private; - struct cached_dev *dc = io->dc; - - uint16_t next_sequence; - - if (atomic_read(&dc->writeback_sequence_next) != io->sequence) { - /* Not our turn to write; wait for a write to complete */ - closure_wait(&dc->writeback_ordering_wait, cl); - - if (atomic_read(&dc->writeback_sequence_next) == io->sequence) { - /* - * Edge case-- it happened in indeterminate order - * relative to when we were added to wait list.. - */ - closure_wake_up(&dc->writeback_ordering_wait); - } - - continue_at(cl, write_dirty, io->dc->writeback_write_wq); - return; - } - - next_sequence = io->sequence + 1; /* * IO errors are signalled using the dirty bit on the key. @@ -444,9 +422,6 @@ static CLOSURE_CALLBACK(write_dirty) closure_bio_submit(io->dc->disk.c, &io->bio, cl); } - atomic_set(&dc->writeback_sequence_next, next_sequence); - closure_wake_up(&dc->writeback_ordering_wait); - continue_at(cl, write_dirty_finish, io->dc->writeback_write_wq); } @@ -482,10 +457,7 @@ static void read_dirty(struct cached_dev *dc) int nk, i; struct dirty_io *io; struct closure cl; - uint16_t sequence = 0; - BUG_ON(!llist_empty(&dc->writeback_ordering_wait.list)); - atomic_set(&dc->writeback_sequence_next, sequence); closure_init_stack(&cl); /* @@ -502,6 +474,9 @@ static void read_dirty(struct cached_dev *dc) while (!kthread_should_stop() && !test_bit(CACHE_SET_IO_DISABLE, &dc->disk.c->flags) && (checked < dump_nr)) { + size_t max_size_in_pass; + int max_writebacks_in_pass; + size = 0; nk = 0; @@ -509,31 +484,26 @@ static void read_dirty(struct cached_dev *dc) w = dump_keys[checked]; BUG_ON(ptr_stale(dc->disk.c, &w->key, 0)); + if (!atomic_read(&dc->disk.c->at_max_writeback_rate)) { + max_writebacks_in_pass = WRITEBACKS_IN_PASS; + max_size_in_pass = WRITESIZE_IN_PASS; + } else { + max_writebacks_in_pass = MAX_WRITEBACKS_IN_PASS; + max_size_in_pass = MAX_WRITESIZE_IN_PASS; + } + /* * Don't combine too many operations, even if they * are all small. */ - if (nk >= MAX_WRITEBACKS_IN_PASS) + if (nk >= max_writebacks_in_pass) break; /* * If the current operation is very large, don't * further combine operations. */ - if (size >= MAX_WRITESIZE_IN_PASS) - break; - - /* - * Operations are only eligible to be combined - * if they are contiguous. - * - * TODO: add a heuristic willing to fire a - * certain amount of non-contiguous IO per pass, - * so that we can benefit from backing device - * command queueing. - */ - if ((nk != 0) && bkey_cmp(&keys[nk-1]->key, - &START_KEY(&w->key))) + if (size >= max_size_in_pass) break; size += KEY_SIZE(&w->key); @@ -552,7 +522,6 @@ static void read_dirty(struct cached_dev *dc) w->private = io; io->dc = dc; - io->sequence = sequence++; dirty_init(w); io->bio.bi_opf = REQ_OP_READ; diff --git a/drivers/md/bcache/writeback.h b/drivers/md/bcache/writeback.h index 31df716951f6..fa7582df1ac2 100644 --- a/drivers/md/bcache/writeback.h +++ b/drivers/md/bcache/writeback.h @@ -8,8 +8,10 @@ #define CUTOFF_WRITEBACK_MAX 70 #define CUTOFF_WRITEBACK_SYNC_MAX 90 -#define MAX_WRITEBACKS_IN_PASS 5 -#define MAX_WRITESIZE_IN_PASS 5000 /* *512b */ +#define WRITEBACKS_IN_PASS 5 +#define MAX_WRITEBACKS_IN_PASS 30 +#define WRITESIZE_IN_PASS 5000 /* *512b */ +#define MAX_WRITESIZE_IN_PASS 30000 /* *512b */ #define WRITEBACK_RATE_UPDATE_SECS_MAX 60 #define WRITEBACK_RATE_UPDATE_SECS_DEFAULT 5 -- 2.39.5