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 <zab@versity.com>
This commit is contained in:
Zach Brown
2021-05-21 15:31:02 -07:00
parent 52c2a465db
commit 54644a5074
5 changed files with 264 additions and 11 deletions
+20 -1
View File
@@ -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))
+203 -8
View File
@@ -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,
&lt.meta_avail, server->meta_avail,
SCOUTFS_SERVER_META_FILL_LO,
SCOUTFS_SERVER_META_FILL_TARGET) ?:
alloc_move_refill(sb, &lt.data_avail, &super->data_alloc,
SCOUTFS_SERVER_DATA_FILL_LO,
SCOUTFS_SERVER_DATA_FILL_TARGET);
alloc_move_refill_zoned(sb, &lt.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(&lt);
if (data_zone_blocks != 0) {
cba.zones = lt.data_alloc_zones;
cba.zone_blocks = data_zone_blocks;
ret = scoutfs_alloc_extents_cb(sb, &lt.data_avail, set_extent_zone_bits, &cba);
if (ret < 0) {
zero_data_alloc_zone_bits(&lt);
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, &lt, sizeof(lt));
@@ -671,6 +832,9 @@ static int reclaim_log_trees(struct super_block *sb, u64 rid)
alloc_move_empty(sb, &super->data_alloc, &lt.data_freed);
mutex_unlock(&server->alloc_mutex);
/* the mount is no longer writing to the zones */
zero_data_alloc_zone_bits(&lt);
err = scoutfs_btree_update(sb, &server->alloc, &server->wri,
&super->logs_root, &key, &lt, 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);
+1
View File
@@ -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 */
+14
View File
@@ -54,6 +54,20 @@ to the console.
.sp
The following volume options are supported:
.TP
.B data_alloc_zone_blocks=<zone size in 4KiB 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
+26 -2
View File
@@ -1,3 +1,4 @@
#define _GNU_SOURCE /* ffsll for glibc < 2.27 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -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(&lt->meta_avail),
AL_HEAD_A(&lt->meta_freed),
lt->item_root.height,
@@ -300,7 +306,21 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val,
SRF_A(&lt->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));