From 54644a50747605f1209d9200c0a9ad1e0843a262 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 13 May 2021 12:23:22 -0700 Subject: [PATCH] Add data_alloc_zone_blocks volume option Add the data_alloc_zone_blocks volume option. This changes the behaviour of the server to try and give mounts free data extents which fall in exclusive fixed-size zones. We add the field to the scoutfs_volume_options struct and add it to the set_volopt server handler which enforces constrains on the size of the zones. We then add fields to the log_trees struct which records the size of the zones and sets bits for the zones that contain free extents in the data_avail allocator root. The get_log_trees handler is changed to read all the zone bitmaps from all the items, pass those bitmaps in to _alloc_move to direct data allocations, and finally update the bitmaps in the log_trees items to cover the newly allocated extents. The log_trees data_alloc_zone fields are cleared as the mount's logs are reclaimed to indicate that the mount is no longer writing to the zone. The policy mechanism of finding free extents based on the bitmaps is ipmlemented down in _data_alloc_move(). Signed-off-by: Zach Brown --- kmod/src/format.h | 21 ++++- kmod/src/server.c | 211 ++++++++++++++++++++++++++++++++++++++++++-- kmod/src/volopt.c | 1 + utils/man/scoutfs.5 | 14 +++ utils/src/print.c | 28 +++++- 5 files changed, 264 insertions(+), 11 deletions(-) diff --git a/kmod/src/format.h b/kmod/src/format.h index 49acf4e9..3c0d789f 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -428,6 +428,10 @@ struct scoutfs_srch_compact { /* client -> server: compaction failed */ #define SCOUTFS_SRCH_COMPACT_FLAG_ERROR (1 << 5) +#define SCOUTFS_DATA_ALLOC_MAX_ZONES 1024 +#define SCOUTFS_DATA_ALLOC_ZONE_BYTES DIV_ROUND_UP(SCOUTFS_DATA_ALLOC_MAX_ZONES, 8) +#define SCOUTFS_DATA_ALLOC_ZONE_LE64S DIV_ROUND_UP(SCOUTFS_DATA_ALLOC_MAX_ZONES, 64) + /* * XXX I imagine we should rename these now that they've evolved to track * all the btrees that clients use during a transaction. It's not just @@ -441,6 +445,8 @@ struct scoutfs_log_trees { struct scoutfs_alloc_root data_avail; struct scoutfs_alloc_root data_freed; struct scoutfs_srch_file srch_file; + __le64 data_alloc_zone_blocks; + __le64 data_alloc_zones[SCOUTFS_DATA_ALLOC_ZONE_LE64S]; __le64 max_item_vers; __le64 rid; __le64 nr; @@ -631,10 +637,18 @@ struct scoutfs_quorum_block { * * @set_bits: bits for each 64bit starting offset after set_bits * indicate which logical option is set. + * + * @data_alloc_zone_blocks: if set, the data device is logically divided + * into contiguous zones of this many blocks. Data allocation will try + * and isolate allocated extents for each mount to their own zone. The + * zone size must be larger than the data alloc high water mark and + * large enough such that the number of zones is kept within its static + * limit. */ struct scoutfs_volume_options { __le64 set_bits; - __le64 __future_expansion[64]; + __le64 data_alloc_zone_blocks; + __le64 __future_expansion[63]; }; #define scoutfs_volopt_nr(field) \ @@ -644,6 +658,11 @@ struct scoutfs_volume_options { #define scoutfs_volopt_bit(field) \ (1ULL << scoutfs_volopt_nr(field)) +#define SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_NR \ + scoutfs_volopt_nr(data_alloc_zone_blocks) +#define SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_BIT \ + scoutfs_volopt_bit(data_alloc_zone_blocks) + #define SCOUTFS_VOLOPT_EXPANSION_BITS \ (~(scoutfs_volopt_bit(__future_expansion) - 1)) diff --git a/kmod/src/server.c b/kmod/src/server.c index 74e41a40..884ec50d 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -398,9 +398,9 @@ out: * Refill the destination root if it's fallen below the lo threshold by * moving from the src root to bring it up to the target. */ -static int alloc_move_refill(struct super_block *sb, - struct scoutfs_alloc_root *dst, - struct scoutfs_alloc_root *src, u64 lo, u64 target) +static int alloc_move_refill_zoned(struct super_block *sb, struct scoutfs_alloc_root *dst, + struct scoutfs_alloc_root *src, u64 lo, u64 target, + __le64 *exclusive, __le64 *vacant, u64 zone_blocks) { DECLARE_SERVER_INFO(sb, server); @@ -409,7 +409,14 @@ static int alloc_move_refill(struct super_block *sb, return scoutfs_alloc_move(sb, &server->alloc, &server->wri, dst, src, min(target - le64_to_cpu(dst->total_len), - le64_to_cpu(src->total_len)), NULL, NULL, 0); + le64_to_cpu(src->total_len)), + exclusive, vacant, zone_blocks); +} + +static inline int alloc_move_refill(struct super_block *sb, struct scoutfs_alloc_root *dst, + struct scoutfs_alloc_root *src, u64 lo, u64 target) +{ + return alloc_move_refill_zoned(sb, dst, src, lo, target, NULL, NULL, 0); } static int alloc_move_empty(struct super_block *sb, @@ -419,7 +426,134 @@ static int alloc_move_empty(struct super_block *sb, DECLARE_SERVER_INFO(sb, server); return scoutfs_alloc_move(sb, &server->alloc, &server->wri, - dst, src, le64_to_cpu(src->total_len)); + dst, src, le64_to_cpu(src->total_len), NULL, NULL, 0); +} + +/* + * Set all the bits in the destination which overlap with the extent. + */ +static void mod_extent_bits(__le64 *bits, u64 zone_blocks, u64 blkno, u64 len, bool set) +{ + u64 nr = div64_u64(blkno, zone_blocks); + u64 last_nr = div64_u64(blkno + len - 1, zone_blocks); + + if (WARN_ON_ONCE(len == 0)) + return; + + while (nr <= last_nr) { + if (set) + set_bit_le(nr, bits); + else + clear_bit_le(nr, bits); + + nr++; + } +} + +/* + * Translate the bits in the source bitmap into extents and modify bits + * in the destination that map those extents. + */ +static void mod_bitmap_bits(__le64 *dst, u64 dst_zone_blocks, + __le64 *src, u64 src_zone_blocks, bool set) +{ + int nr = 0; + + for (;;) { + nr = find_next_bit_le(src, SCOUTFS_DATA_ALLOC_MAX_ZONES, nr); + if (nr >= SCOUTFS_DATA_ALLOC_MAX_ZONES) + break; + + mod_extent_bits(dst, dst_zone_blocks, + (u64)nr * src_zone_blocks, src_zone_blocks, set); + nr++; + } +} + +/* + * Iterate over all the log_tree items and initialize the caller's zone + * bitmaps. Exclusive bits are only found in the caller's items. + * Vacant bits are not found in any items. + * + * The log_tree item zone bitmaps could have been stored with different + * zone_blocks sizes. We translate the bits into block extents and + * record overlaps with the current zone size. + * + * The caller has the log items locked. + */ +static int get_data_alloc_zone_bits(struct super_block *sb, u64 rid, __le64 *exclusive, + __le64 *vacant, u64 zone_blocks) +{ + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + SCOUTFS_BTREE_ITEM_REF(iref); + struct scoutfs_log_trees *lt; + struct scoutfs_key key; + int ret; + + memset(exclusive, 0, SCOUTFS_DATA_ALLOC_ZONE_BYTES); + memset(vacant, 0, SCOUTFS_DATA_ALLOC_ZONE_BYTES); + + mod_extent_bits(vacant, zone_blocks, 0, le64_to_cpu(super->total_data_blocks), true); + + scoutfs_key_init_log_trees(&key, 0, 0); + for (;;) { + ret = scoutfs_btree_next(sb, &super->logs_root, &key, &iref); + if (ret == 0) { + if (iref.val_len == sizeof(struct scoutfs_log_trees)) { + lt = iref.val; + + /* vacant bits have no bits found in items */ + mod_bitmap_bits(vacant, zone_blocks, + lt->data_alloc_zones, + le64_to_cpu(lt->data_alloc_zone_blocks), + false); + + /* exclusive bits are only found in caller's items */ + if (le64_to_cpu(iref.key->sklt_rid) == rid) { + mod_bitmap_bits(exclusive, zone_blocks, + lt->data_alloc_zones, + le64_to_cpu(lt->data_alloc_zone_blocks), + true); + } else { + mod_bitmap_bits(exclusive, zone_blocks, + lt->data_alloc_zones, + le64_to_cpu(lt->data_alloc_zone_blocks), + false); + } + + key = *iref.key; + scoutfs_key_inc(&key); + } else { + ret = -EIO; + } + scoutfs_btree_put_iref(&iref); + } + if (ret < 0) { + if (ret == -ENOENT) + ret = 0; + break; + } + } + + return ret; +} + +static void zero_data_alloc_zone_bits(struct scoutfs_log_trees *lt) +{ + lt->data_alloc_zone_blocks = 0; + memset(lt->data_alloc_zones, 0, sizeof(lt->data_alloc_zones)); +} + +struct alloc_extent_cb_args { + __le64 *zones; + u64 zone_blocks; +}; + +static void set_extent_zone_bits(struct super_block *sb, void *cb_arg, struct scoutfs_extent *ext) +{ + struct alloc_extent_cb_args *cba = cb_arg; + + mod_extent_bits(cba->zones, cba->zone_blocks, ext->start, ext->len, true); } /* @@ -439,9 +573,13 @@ static int server_get_log_trees(struct super_block *sb, struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; u64 rid = scoutfs_net_client_rid(conn); DECLARE_SERVER_INFO(sb, server); + __le64 exclusive[SCOUTFS_DATA_ALLOC_ZONE_LE64S]; + __le64 vacant[SCOUTFS_DATA_ALLOC_ZONE_LE64S]; + struct alloc_extent_cb_args cba; SCOUTFS_BTREE_ITEM_REF(iref); struct scoutfs_log_trees lt; struct scoutfs_key key; + u64 data_zone_blocks; int ret; if (arg_len != 0) { @@ -483,6 +621,14 @@ static int server_get_log_trees(struct super_block *sb, lt.nr = key.sklt_nr; } + if (get_volopt_val(server, SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_NR, &data_zone_blocks)) { + ret = get_data_alloc_zone_bits(sb, rid, exclusive, vacant, data_zone_blocks); + if (ret < 0) + goto unlock; + } else { + data_zone_blocks = 0; + } + /* return freed to server for emptying, refill avail */ mutex_lock(&server->alloc_mutex); ret = scoutfs_alloc_splice_list(sb, &server->alloc, &server->wri, @@ -493,13 +639,28 @@ static int server_get_log_trees(struct super_block *sb, <.meta_avail, server->meta_avail, SCOUTFS_SERVER_META_FILL_LO, SCOUTFS_SERVER_META_FILL_TARGET) ?: - alloc_move_refill(sb, <.data_avail, &super->data_alloc, - SCOUTFS_SERVER_DATA_FILL_LO, - SCOUTFS_SERVER_DATA_FILL_TARGET); + alloc_move_refill_zoned(sb, <.data_avail, &super->data_alloc, + SCOUTFS_SERVER_DATA_FILL_LO, + SCOUTFS_SERVER_DATA_FILL_TARGET, + exclusive, vacant, data_zone_blocks); mutex_unlock(&server->alloc_mutex); if (ret < 0) goto unlock; + /* record data alloc zone bits */ + zero_data_alloc_zone_bits(<); + if (data_zone_blocks != 0) { + cba.zones = lt.data_alloc_zones; + cba.zone_blocks = data_zone_blocks; + ret = scoutfs_alloc_extents_cb(sb, <.data_avail, set_extent_zone_bits, &cba); + if (ret < 0) { + zero_data_alloc_zone_bits(<); + goto unlock; + } + + lt.data_alloc_zone_blocks = cpu_to_le64(data_zone_blocks); + } + /* update client's log tree's item */ ret = scoutfs_btree_force(sb, &server->alloc, &server->wri, &super->logs_root, &key, <, sizeof(lt)); @@ -671,6 +832,9 @@ static int reclaim_log_trees(struct super_block *sb, u64 rid) alloc_move_empty(sb, &super->data_alloc, <.data_freed); mutex_unlock(&server->alloc_mutex); + /* the mount is no longer writing to the zones */ + zero_data_alloc_zone_bits(<); + err = scoutfs_btree_update(sb, &server->alloc, &server->wri, &super->logs_root, &key, <, sizeof(lt)); BUG_ON(err != 0); /* alloc and log item roots out of sync */ @@ -1147,6 +1311,8 @@ static int server_set_volopt(struct super_block *sb, struct scoutfs_net_connecti DECLARE_SERVER_INFO(sb, server); struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; struct scoutfs_volume_options *volopt; + u64 opt; + u64 nr; int ret = 0; if (arg_len != sizeof(struct scoutfs_volume_options)) { @@ -1166,6 +1332,35 @@ static int server_set_volopt(struct super_block *sb, struct scoutfs_net_connecti if (ret) goto unlock; + if (le64_to_cpu(volopt->set_bits) & SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_BIT) { + opt = le64_to_cpu(volopt->data_alloc_zone_blocks); + if (opt < SCOUTFS_SERVER_DATA_FILL_TARGET) { + scoutfs_err(sb, "setting data_alloc_zone_blocks to '%llu' failed, must be at least %llu mount data allocation target blocks", + opt, SCOUTFS_SERVER_DATA_FILL_TARGET); + ret = -EINVAL; + goto apply; + } + + nr = div_u64(le64_to_cpu(super->total_data_blocks), SCOUTFS_DATA_ALLOC_MAX_ZONES); + if (opt < nr) { + scoutfs_err(sb, "setting data_alloc_zone_blocks to '%llu' failed, must be greater than %llu blocks which results in max %u zones", + opt, nr, SCOUTFS_DATA_ALLOC_MAX_ZONES); + ret = -EINVAL; + goto apply; + } + + if (opt > le64_to_cpu(super->total_data_blocks)) { + scoutfs_err(sb, "setting data_alloc_zone_blocks to '%llu' failed, must be at most %llu total data device blocks", + opt, le64_to_cpu(super->total_data_blocks)); + ret = -EINVAL; + goto apply; + } + + super->volopt.data_alloc_zone_blocks = volopt->data_alloc_zone_blocks; + super->volopt.set_bits |= cpu_to_le64(SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_BIT); + } + +apply: ret = scoutfs_server_apply_commit(sb, ret); write_seqcount_begin(&server->volopt_seqcount); diff --git a/kmod/src/volopt.c b/kmod/src/volopt.c index 48ddf90e..303d94b0 100644 --- a/kmod/src/volopt.c +++ b/kmod/src/volopt.c @@ -46,6 +46,7 @@ static struct volopt_nr_name { int nr; char *name; } volopt_table[] = { + { SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_NR, "data_alloc_zone_blocks" }, }; /* initialized by setup, pointer array is null terminated */ diff --git a/utils/man/scoutfs.5 b/utils/man/scoutfs.5 index d6b6bbe5..c489a5d5 100644 --- a/utils/man/scoutfs.5 +++ b/utils/man/scoutfs.5 @@ -54,6 +54,20 @@ to the console. .sp The following volume options are supported: .TP +.B data_alloc_zone_blocks= +When the data_alloc_zone_blocks option is set the data device is +logically divided into zones of equal length as specified by the value +of the option. The size of the zones must be greater than a minimum +allocation pool size, large enough to result in no more than 1024 zones, +and not more than the total number of blocks in the data device. +.sp +When set, the server will try to provide each mount with free data +extents that don't share a zone with other mounts. When a mount has free +extents in a given zone the server will try and find more free extents +in that zone. When the mount is not in a zone, or its zone has no more +free extents, the server will try and find free extents in a zone that +no other mount currently occupies. The result is to try and produce +write streams where only one mount is writing into each zone. .SH FURTHER READING A .B scoutfs diff --git a/utils/src/print.c b/utils/src/print.c index 6e82251b..9ddc3e4b 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -1,3 +1,4 @@ +#define _GNU_SOURCE /* ffsll for glibc < 2.27 */ #include #include #include @@ -272,6 +273,9 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val, unsigned val_len, void *arg) { struct scoutfs_log_trees *lt = val; + u64 zones; + int bit; + int i; printf(" rid %llu nr %llu\n", le64_to_cpu(key->sklt_rid), le64_to_cpu(key->sklt_nr)); @@ -287,7 +291,9 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val, " srch_file: "SRF_FMT"\n" " max_item_vers: %llu\n" " rid: %016llx\n" - " nr: %llu\n", + " nr: %llu\n" + " data_alloc_zone_blocks: %llu\n" + " data_alloc_zones: ", AL_HEAD_A(<->meta_avail), AL_HEAD_A(<->meta_freed), lt->item_root.height, @@ -300,7 +306,21 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val, SRF_A(<->srch_file), le64_to_cpu(lt->max_item_vers), le64_to_cpu(lt->rid), - le64_to_cpu(lt->nr)); + le64_to_cpu(lt->nr), + le64_to_cpu(lt->data_alloc_zone_blocks)); + + for (i = 0; i < SCOUTFS_DATA_ALLOC_ZONE_LE64S; i++) { + if (lt->data_alloc_zones[i] == 0) + continue; + + zones = le64_to_cpu(lt->data_alloc_zones[i]); + while (zones) { + bit = ffsll(zones) - 1; + printf("%u ", (i * 64) + bit); + zones ^= (1ULL << bit); + } + } + printf("\n"); } return 0; @@ -903,6 +923,10 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno) printf(" volume options:\n" " set_bits: %016llx\n", le64_to_cpu(super->volopt.set_bits)); + if (le64_to_cpu(super->volopt.set_bits) & SCOUTFS_VOLOPT_DATA_ALLOC_ZONE_BLOCKS_BIT) { + printf(" data_alloc_zone_blocks: %llu\n", + le64_to_cpu(super->volopt.data_alloc_zone_blocks)); + } printf(" quorum config version %llu\n", le64_to_cpu(super->qconf.version));