mirror of
https://github.com/versity/scoutfs.git
synced 2026-07-20 06:52:20 +00:00
fc003a5038
With many concurrent writers we were seeing excessive commits forced because it thought the data allocator was running low. The transaction was checking the raw total_len value in the data_avail alloc_root for the number of free data blocks. But this read wasn't locked, and allocators could completely remove a large free extent and then re-insert a slightly smaller free extent as they perform their alloction. The transaction could see a temporary very small total_len and trigger a commit. Data allocations are serialized by a heavy mutex so we don't want to have the reader try and use that to see a consistent total_len. Instead we create a data allocator run-time struct that has a consistent total_len that is updated after all the extent items are manipulated. This also gives us a place to put the caller's cached extent so that it can be included in the total_len, previously it wasn't included in the free total that the transaction saw. The file data allocator can then initialize and use this struct instead of its raw use of the root and cached extent. Then the transaction can sample its consistent total_len that reflects the root and cached extent. A subtle detail is that fallocate can't use _free_data to return an allocated extent on error to the avail pool. It instead frees into the data_free pool like normal frees. It doesn't really matter that this could prematurely drain the avail pool because it's in an error path. Signed-off-by: Zach Brown <zab@versity.com>
156 lines
5.8 KiB
C
156 lines
5.8 KiB
C
#ifndef _SCOUTFS_ALLOC_H_
|
|
#define _SCOUTFS_ALLOC_H_
|
|
|
|
#include "ext.h"
|
|
|
|
/*
|
|
* These are implementation-specific metrics, they don't need to be
|
|
* consistent across implementations. They should probably be run-time
|
|
* knobs.
|
|
*/
|
|
|
|
/*
|
|
* The largest extent that we'll try to allocate with fallocate. We're
|
|
* trying not to completely consume a transactions data allocation all
|
|
* at once. This is only allocation granularity, repeated allocations
|
|
* can produce large contiguous extents.
|
|
*/
|
|
#define SCOUTFS_FALLOCATE_ALLOC_LIMIT \
|
|
(128ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
|
|
|
/*
|
|
* The largest aligned region that we'll try to allocate at the end of
|
|
* the file as it's extended. This is also limited to the current file
|
|
* size so we can only waste at most twice the total file size when
|
|
* files are less than this. We try to keep this around the point of
|
|
* diminishing returns in streaming performance of common data devices
|
|
* to limit waste.
|
|
*/
|
|
#define SCOUTFS_DATA_EXTEND_PREALLOC_LIMIT \
|
|
(8ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
|
|
|
/*
|
|
* Small data allocations are satisfied by cached extents stored in
|
|
* the run-time alloc struct to minimize item operations for small
|
|
* block allocations. Large allocations come directly from btree
|
|
* extent items, and this defines the threshold beetwen them.
|
|
*/
|
|
#define SCOUTFS_ALLOC_DATA_LG_THRESH \
|
|
(8ULL * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
|
|
|
/*
|
|
* Fill client alloc roots to the target when they fall below the lo
|
|
* threshold.
|
|
*
|
|
* We're giving the client the most available meta blocks we can so that
|
|
* it has the freedom to build large transactions before worrying that
|
|
* it might run out of meta allocs during commits.
|
|
*/
|
|
#define SCOUTFS_SERVER_META_FILL_TARGET \
|
|
SCOUTFS_ALLOC_LIST_MAX_BLOCKS
|
|
#define SCOUTFS_SERVER_META_FILL_LO \
|
|
(SCOUTFS_ALLOC_LIST_MAX_BLOCKS / 2)
|
|
#define SCOUTFS_SERVER_DATA_FILL_TARGET \
|
|
(4ULL * 1024 * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
|
#define SCOUTFS_SERVER_DATA_FILL_LO \
|
|
(1ULL * 1024 * 1024 * 1024 >> SCOUTFS_BLOCK_SM_SHIFT)
|
|
|
|
/*
|
|
* Each of the server meta_alloc roots will try to keep a minimum amount
|
|
* of free blocks. The server will swap roots when its current avail
|
|
* falls below the threshold while the freed root is still above it. It
|
|
* must have room for all the largest allocation attempted in a
|
|
* transaction on the server.
|
|
*/
|
|
#define SCOUTFS_SERVER_META_ALLOC_MIN \
|
|
(SCOUTFS_SERVER_META_FILL_TARGET * 2)
|
|
|
|
/*
|
|
* A run-time use of a pair of persistent avail/freed roots as a
|
|
* metadata allocator. It has the machinery needed to lock and avoid
|
|
* recursion when dirtying the list blocks that are used during the
|
|
* transaction.
|
|
*/
|
|
struct scoutfs_alloc {
|
|
spinlock_t lock;
|
|
struct mutex mutex;
|
|
struct scoutfs_block *dirty_avail_bl;
|
|
struct scoutfs_block *dirty_freed_bl;
|
|
struct scoutfs_alloc_list_head avail;
|
|
struct scoutfs_alloc_list_head freed;
|
|
};
|
|
|
|
/*
|
|
* A run-time data allocator. We have a cached extent in memory that is
|
|
* a lot cheaper to work with than the extent items, and we have a
|
|
* consistent record of the total_len that can be sampled outside of the
|
|
* usual heavy serialization of the extent modifications.
|
|
*/
|
|
struct scoutfs_data_alloc {
|
|
struct scoutfs_alloc_root root;
|
|
struct scoutfs_extent cached;
|
|
atomic64_t total_len;
|
|
};
|
|
|
|
void scoutfs_alloc_init(struct scoutfs_alloc *alloc,
|
|
struct scoutfs_alloc_list_head *avail,
|
|
struct scoutfs_alloc_list_head *freed);
|
|
int scoutfs_alloc_prepare_commit(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri);
|
|
|
|
int scoutfs_alloc_meta(struct super_block *sb, struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri, u64 *blkno);
|
|
int scoutfs_free_meta(struct super_block *sb, struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri, u64 blkno);
|
|
|
|
void scoutfs_dalloc_init(struct scoutfs_data_alloc *dalloc,
|
|
struct scoutfs_alloc_root *data_avail);
|
|
void scoutfs_dalloc_get_root(struct scoutfs_data_alloc *dalloc,
|
|
struct scoutfs_alloc_root *data_avail);
|
|
u64 scoutfs_dalloc_total_len(struct scoutfs_data_alloc *dalloc);
|
|
int scoutfs_dalloc_return_cached(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_data_alloc *dalloc);
|
|
int scoutfs_alloc_data(struct super_block *sb, struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_data_alloc *dalloc, u64 count,
|
|
u64 *blkno_ret, u64 *count_ret);
|
|
int scoutfs_free_data(struct super_block *sb, struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_alloc_root *root, u64 blkno, u64 count);
|
|
|
|
int scoutfs_alloc_move(struct super_block *sb, struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_alloc_root *dst,
|
|
struct scoutfs_alloc_root *src, u64 total);
|
|
|
|
int scoutfs_alloc_fill_list(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_alloc_list_head *lhead,
|
|
struct scoutfs_alloc_root *root,
|
|
u64 lo, u64 target);
|
|
int scoutfs_alloc_empty_list(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_alloc_root *root,
|
|
struct scoutfs_alloc_list_head *lhead);
|
|
int scoutfs_alloc_splice_list(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc,
|
|
struct scoutfs_block_writer *wri,
|
|
struct scoutfs_alloc_list_head *dst,
|
|
struct scoutfs_alloc_list_head *src);
|
|
|
|
bool scoutfs_alloc_meta_low(struct super_block *sb,
|
|
struct scoutfs_alloc *alloc, u32 nr);
|
|
|
|
typedef int (*scoutfs_alloc_foreach_cb_t)(struct super_block *sb, void *arg,
|
|
int owner, u64 id,
|
|
bool meta, bool avail, u64 blocks);
|
|
int scoutfs_alloc_foreach(struct super_block *sb,
|
|
scoutfs_alloc_foreach_cb_t cb, void *arg);
|
|
|
|
#endif
|