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));