forked from Minki/linux
71dda2a562
Current way to calculate the writeback rate only considered the dirty sectors, this usually works fine when the fragmentation is not high, but it will give us unreasonable small rate when we are under a situation that very few dirty sectors consumed a lot dirty buckets. In some case, the dirty bucekts can reached to CUTOFF_WRITEBACK_SYNC while the dirty data(sectors) not even reached the writeback_percent, the writeback rate will still be the minimum value (4k), thus it will cause all the writes to be stucked in a non-writeback mode because of the slow writeback. We accelerate the rate in 3 stages with different aggressiveness, the first stage starts when dirty buckets percent reach above BCH_WRITEBACK_FRAGMENT_THRESHOLD_LOW (50), the second is BCH_WRITEBACK_FRAGMENT_THRESHOLD_MID (57), the third is BCH_WRITEBACK_FRAGMENT_THRESHOLD_HIGH (64). By default the first stage tries to writeback the amount of dirty data in one bucket (on average) in (1 / (dirty_buckets_percent - 50)) second, the second stage tries to writeback the amount of dirty data in one bucket in (1 / (dirty_buckets_percent - 57)) * 100 millisecond, the third stage tries to writeback the amount of dirty data in one bucket in (1 / (dirty_buckets_percent - 64)) millisecond. the initial rate at each stage can be controlled by 3 configurable parameters writeback_rate_fp_term_{low|mid|high}, they are by default 1, 10, 1000, the hint of IO throughput that these values are trying to achieve is described by above paragraph, the reason that I choose those value as default is based on the testing and the production data, below is some details: A. When it comes to the low stage, there is still a bit far from the 70 threshold, so we only want to give it a little bit push by setting the term to 1, it means the initial rate will be 170 if the fragment is 6, it is calculated by bucket_size/fragment, this rate is very small, but still much reasonable than the minimum 8. For a production bcache with unheavy workload, if the cache device is bigger than 1 TB, it may take hours to consume 1% buckets, so it is very possible to reclaim enough dirty buckets in this stage, thus to avoid entering the next stage. B. If the dirty buckets ratio didn't turn around during the first stage, it comes to the mid stage, then it is necessary for mid stage to be more aggressive than low stage, so i choose the initial rate to be 10 times more than low stage, that means 1700 as the initial rate if the fragment is 6. This is some normal rate we usually see for a normal workload when writeback happens because of writeback_percent. C. If the dirty buckets ratio didn't turn around during the low and mid stages, it comes to the third stage, and it is the last chance that we can turn around to avoid the horrible cutoff writeback sync issue, then we choose 100 times more aggressive than the mid stage, that means 170000 as the initial rate if the fragment is 6. This is also inferred from a production bcache, I've got one week's writeback rate data from a production bcache which has quite heavy workloads, again, the writeback is triggered by the writeback percent, the highest rate area is around 100000 to 240000, so I believe this kind aggressiveness at this stage is reasonable for production. And it should be mostly enough because the hint is trying to reclaim 1000 bucket per second, and from that heavy production env, it is consuming 50 bucket per second on average in one week's data. Option writeback_consider_fragment is to control whether we want this feature to be on or off, it's on by default. Lastly, below is the performance data for all the testing result, including the data from production env: https://docs.google.com/document/d/1AmbIEa_2MhB9bqhC3rfga9tp7n9YX9PLn0jSUxscVW0/edit?usp=sharing Signed-off-by: dongdong tao <dongdong.tao@canonical.com> Signed-off-by: Coly Li <colyli@suse.de> Signed-off-by: Jens Axboe <axboe@kernel.dk>
156 lines
3.8 KiB
C
156 lines
3.8 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _BCACHE_WRITEBACK_H
|
|
#define _BCACHE_WRITEBACK_H
|
|
|
|
#define CUTOFF_WRITEBACK 40
|
|
#define CUTOFF_WRITEBACK_SYNC 70
|
|
|
|
#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 WRITEBACK_RATE_UPDATE_SECS_MAX 60
|
|
#define WRITEBACK_RATE_UPDATE_SECS_DEFAULT 5
|
|
|
|
#define BCH_AUTO_GC_DIRTY_THRESHOLD 50
|
|
|
|
#define BCH_WRITEBACK_FRAGMENT_THRESHOLD_LOW 50
|
|
#define BCH_WRITEBACK_FRAGMENT_THRESHOLD_MID 57
|
|
#define BCH_WRITEBACK_FRAGMENT_THRESHOLD_HIGH 64
|
|
|
|
#define BCH_DIRTY_INIT_THRD_MAX 64
|
|
/*
|
|
* 14 (16384ths) is chosen here as something that each backing device
|
|
* should be a reasonable fraction of the share, and not to blow up
|
|
* until individual backing devices are a petabyte.
|
|
*/
|
|
#define WRITEBACK_SHARE_SHIFT 14
|
|
|
|
struct bch_dirty_init_state;
|
|
struct dirty_init_thrd_info {
|
|
struct bch_dirty_init_state *state;
|
|
struct task_struct *thread;
|
|
};
|
|
|
|
struct bch_dirty_init_state {
|
|
struct cache_set *c;
|
|
struct bcache_device *d;
|
|
int total_threads;
|
|
int key_idx;
|
|
spinlock_t idx_lock;
|
|
atomic_t started;
|
|
atomic_t enough;
|
|
wait_queue_head_t wait;
|
|
struct dirty_init_thrd_info infos[BCH_DIRTY_INIT_THRD_MAX];
|
|
};
|
|
|
|
static inline uint64_t bcache_dev_sectors_dirty(struct bcache_device *d)
|
|
{
|
|
uint64_t i, ret = 0;
|
|
|
|
for (i = 0; i < d->nr_stripes; i++)
|
|
ret += atomic_read(d->stripe_sectors_dirty + i);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int offset_to_stripe(struct bcache_device *d,
|
|
uint64_t offset)
|
|
{
|
|
do_div(offset, d->stripe_size);
|
|
|
|
/* d->nr_stripes is in range [1, INT_MAX] */
|
|
if (unlikely(offset >= d->nr_stripes)) {
|
|
pr_err("Invalid stripe %llu (>= nr_stripes %d).\n",
|
|
offset, d->nr_stripes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Here offset is definitly smaller than INT_MAX,
|
|
* return it as int will never overflow.
|
|
*/
|
|
return offset;
|
|
}
|
|
|
|
static inline bool bcache_dev_stripe_dirty(struct cached_dev *dc,
|
|
uint64_t offset,
|
|
unsigned int nr_sectors)
|
|
{
|
|
int stripe = offset_to_stripe(&dc->disk, offset);
|
|
|
|
if (stripe < 0)
|
|
return false;
|
|
|
|
while (1) {
|
|
if (atomic_read(dc->disk.stripe_sectors_dirty + stripe))
|
|
return true;
|
|
|
|
if (nr_sectors <= dc->disk.stripe_size)
|
|
return false;
|
|
|
|
nr_sectors -= dc->disk.stripe_size;
|
|
stripe++;
|
|
}
|
|
}
|
|
|
|
extern unsigned int bch_cutoff_writeback;
|
|
extern unsigned int bch_cutoff_writeback_sync;
|
|
|
|
static inline bool should_writeback(struct cached_dev *dc, struct bio *bio,
|
|
unsigned int cache_mode, bool would_skip)
|
|
{
|
|
unsigned int in_use = dc->disk.c->gc_stats.in_use;
|
|
|
|
if (cache_mode != CACHE_MODE_WRITEBACK ||
|
|
test_bit(BCACHE_DEV_DETACHING, &dc->disk.flags) ||
|
|
in_use > bch_cutoff_writeback_sync)
|
|
return false;
|
|
|
|
if (bio_op(bio) == REQ_OP_DISCARD)
|
|
return false;
|
|
|
|
if (dc->partial_stripes_expensive &&
|
|
bcache_dev_stripe_dirty(dc, bio->bi_iter.bi_sector,
|
|
bio_sectors(bio)))
|
|
return true;
|
|
|
|
if (would_skip)
|
|
return false;
|
|
|
|
return (op_is_sync(bio->bi_opf) ||
|
|
bio->bi_opf & (REQ_META|REQ_PRIO) ||
|
|
in_use <= bch_cutoff_writeback);
|
|
}
|
|
|
|
static inline void bch_writeback_queue(struct cached_dev *dc)
|
|
{
|
|
if (!IS_ERR_OR_NULL(dc->writeback_thread))
|
|
wake_up_process(dc->writeback_thread);
|
|
}
|
|
|
|
static inline void bch_writeback_add(struct cached_dev *dc)
|
|
{
|
|
if (!atomic_read(&dc->has_dirty) &&
|
|
!atomic_xchg(&dc->has_dirty, 1)) {
|
|
if (BDEV_STATE(&dc->sb) != BDEV_STATE_DIRTY) {
|
|
SET_BDEV_STATE(&dc->sb, BDEV_STATE_DIRTY);
|
|
/* XXX: should do this synchronously */
|
|
bch_write_bdev_super(dc, NULL);
|
|
}
|
|
|
|
bch_writeback_queue(dc);
|
|
}
|
|
}
|
|
|
|
void bcache_dev_sectors_dirty_add(struct cache_set *c, unsigned int inode,
|
|
uint64_t offset, int nr_sectors);
|
|
|
|
void bch_sectors_dirty_init(struct bcache_device *d);
|
|
void bch_cached_dev_writeback_init(struct cached_dev *dc);
|
|
int bch_cached_dev_writeback_start(struct cached_dev *dc);
|
|
|
|
#endif
|