Skip to content

Commit

Permalink
Btrfs: turbo charge fsync
Browse files Browse the repository at this point in the history
At least for the vm workload.  Currently on fsync we will

1) Truncate all items in the log tree for the given inode if they exist

and

2) Copy all items for a given inode into the log

The problem with this is that for things like VMs you can have lots of
extents from the fragmented writing behavior, and worst yet you may have
only modified a few extents, not the entire thing.  This patch fixes this
problem by tracking which transid modified our extent, and then when we do
the tree logging we find all of the extents we've modified in our current
transaction, sort them and commit them.  We also only truncate up to the
xattrs of the inode and copy that stuff in normally, and then just drop any
extents in the range we have that exist in the log already.  Here are some
numbers of a 50 meg fio job that does random writes and fsync()s after every
write

		Original	Patched
SATA drive	82KB/s		140KB/s
Fusion drive	431KB/s		2532KB/s

So around 2-6 times faster depending on your hardware.  There are a few
corner cases, for example if you truncate at all we have to do it the old
way since there is no way to be sure what is in the log is ok.  This
probably could be done smarter, but if you write-fsync-truncate-write-fsync
you deserve what you get.  All this work is in RAM of course so if your
inode gets evicted from cache and you read it in and fsync it we'll do it
the slow way if we are still in the same transaction that we last modified
the inode in.

The biggest cool part of this is that it requires no changes to the recovery
code, so if you fsync with this patch and crash and load an old kernel, it
will run the recovery and be a-ok.  I have tested this pretty thoroughly
with an fsync tester and everything comes back fine, as well as xfstests.
Thanks,

Signed-off-by: Josef Bacik <jbacik@fusionio.com>
  • Loading branch information
Josef Bacik authored and Chris Mason committed Oct 1, 2012
1 parent 224ecce commit 5dc562c
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 42 deletions.
1 change: 1 addition & 0 deletions fs/btrfs/btrfs_inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#define BTRFS_INODE_DELALLOC_META_RESERVED 4
#define BTRFS_INODE_HAS_ORPHAN_ITEM 5
#define BTRFS_INODE_HAS_ASYNC_EXTENT 6
#define BTRFS_INODE_NEEDS_FULL_SYNC 7

/* in memory btrfs inode */
struct btrfs_inode {
Expand Down
12 changes: 10 additions & 2 deletions fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -3315,9 +3315,17 @@ int btrfs_run_defrag_inodes(struct btrfs_fs_info *fs_info);
int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync);
int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
int skip_pinned);
int btrfs_replace_extent_cache(struct inode *inode, struct extent_map *replace,
u64 start, u64 end, int skip_pinned,
int modified);
extern const struct file_operations btrfs_file_operations;
int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
u64 start, u64 end, u64 *hint_byte, int drop_cache);
int __btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode,
struct btrfs_path *path, u64 start, u64 end,
u64 *hint_byte, int drop_cache);
int btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode, u64 start,
u64 end, u64 *hint_byte, int drop_cache);
int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
struct inode *inode, u64 start, u64 end);
int btrfs_release_file(struct inode *inode, struct file *file);
Expand Down
34 changes: 32 additions & 2 deletions fs/btrfs/extent_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void extent_map_exit(void)
void extent_map_tree_init(struct extent_map_tree *tree)
{
tree->map = RB_ROOT;
INIT_LIST_HEAD(&tree->modified_extents);
rwlock_init(&tree->lock);
}

Expand All @@ -54,7 +55,9 @@ struct extent_map *alloc_extent_map(void)
em->in_tree = 0;
em->flags = 0;
em->compress_type = BTRFS_COMPRESS_NONE;
em->generation = 0;
atomic_set(&em->refs, 1);
INIT_LIST_HEAD(&em->list);
return em;
}

Expand All @@ -72,6 +75,7 @@ void free_extent_map(struct extent_map *em)
WARN_ON(atomic_read(&em->refs) == 0);
if (atomic_dec_and_test(&em->refs)) {
WARN_ON(em->in_tree);
WARN_ON(!list_empty(&em->list));
kmem_cache_free(extent_map_cache, em);
}
}
Expand Down Expand Up @@ -198,6 +202,12 @@ static void try_merge_map(struct extent_map_tree *tree, struct extent_map *em)
em->block_len += merge->block_len;
em->block_start = merge->block_start;
merge->in_tree = 0;
if (merge->generation > em->generation) {
em->generation = merge->generation;
list_move(&em->list, &tree->modified_extents);
}

list_del_init(&merge->list);
rb_erase(&merge->rb_node, &tree->map);
free_extent_map(merge);
}
Expand All @@ -211,11 +221,29 @@ static void try_merge_map(struct extent_map_tree *tree, struct extent_map *em)
em->block_len += merge->len;
rb_erase(&merge->rb_node, &tree->map);
merge->in_tree = 0;
if (merge->generation > em->generation) {
em->generation = merge->generation;
list_move(&em->list, &tree->modified_extents);
}
list_del_init(&merge->list);
free_extent_map(merge);
}
}

int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len)
/**
* unpint_extent_cache - unpin an extent from the cache
* @tree: tree to unpin the extent in
* @start: logical offset in the file
* @len: length of the extent
* @gen: generation that this extent has been modified in
* @prealloc: if this is set we need to clear the prealloc flag
*
* Called after an extent has been written to disk properly. Set the generation
* to the generation that actually added the file item to the inode so we know
* we need to sync this extent when we call fsync().
*/
int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len,
u64 gen)
{
int ret = 0;
struct extent_map *em;
Expand All @@ -228,10 +256,11 @@ int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len)
if (!em)
goto out;

list_move(&em->list, &tree->modified_extents);
em->generation = gen;
clear_bit(EXTENT_FLAG_PINNED, &em->flags);

try_merge_map(tree, em);

free_extent_map(em);
out:
write_unlock(&tree->lock);
Expand Down Expand Up @@ -358,6 +387,7 @@ int remove_extent_mapping(struct extent_map_tree *tree, struct extent_map *em)

WARN_ON(test_bit(EXTENT_FLAG_PINNED, &em->flags));
rb_erase(&em->rb_node, &tree->map);
list_del_init(&em->list);
em->in_tree = 0;
return ret;
}
5 changes: 4 additions & 1 deletion fs/btrfs/extent_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ struct extent_map {
u64 orig_start;
u64 block_start;
u64 block_len;
u64 generation;
unsigned long flags;
struct block_device *bdev;
atomic_t refs;
unsigned int in_tree;
unsigned int compress_type;
struct list_head list;
};

struct extent_map_tree {
struct rb_root map;
struct list_head modified_extents;
rwlock_t lock;
};

Expand Down Expand Up @@ -60,7 +63,7 @@ struct extent_map *alloc_extent_map(void);
void free_extent_map(struct extent_map *em);
int __init extent_map_init(void);
void extent_map_exit(void);
int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len);
int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len, u64 gen);
struct extent_map *search_extent_mapping(struct extent_map_tree *tree,
u64 start, u64 len);
#endif
62 changes: 43 additions & 19 deletions fs/btrfs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,13 +459,14 @@ int btrfs_dirty_pages(struct btrfs_root *root, struct inode *inode,
* [start, end]. Existing extents are split as required.
*/
int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
int skip_pinned)
int skip_pinned)
{
struct extent_map *em;
struct extent_map *split = NULL;
struct extent_map *split2 = NULL;
struct extent_map_tree *em_tree = &BTRFS_I(inode)->extent_tree;
u64 len = end - start + 1;
u64 gen;
int ret;
int testend = 1;
unsigned long flags;
Expand All @@ -490,6 +491,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
break;
}
flags = em->flags;
gen = em->generation;
if (skip_pinned && test_bit(EXTENT_FLAG_PINNED, &em->flags)) {
if (testend && em->start + em->len >= start + len) {
free_extent_map(em);
Expand Down Expand Up @@ -518,12 +520,13 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
split->block_len = em->block_len;
else
split->block_len = split->len;

split->generation = gen;
split->bdev = em->bdev;
split->flags = flags;
split->compress_type = em->compress_type;
ret = add_extent_mapping(em_tree, split);
BUG_ON(ret); /* Logic error */
list_move(&split->list, &em_tree->modified_extents);
free_extent_map(split);
split = split2;
split2 = NULL;
Expand All @@ -537,6 +540,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
split->bdev = em->bdev;
split->flags = flags;
split->compress_type = em->compress_type;
split->generation = gen;

if (compressed) {
split->block_len = em->block_len;
Expand All @@ -550,6 +554,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,

ret = add_extent_mapping(em_tree, split);
BUG_ON(ret); /* Logic error */
list_move(&split->list, &em_tree->modified_extents);
free_extent_map(split);
split = NULL;
}
Expand All @@ -576,13 +581,13 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
* it is either truncated or split. Anything entirely inside the range
* is deleted from the tree.
*/
int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
u64 start, u64 end, u64 *hint_byte, int drop_cache)
int __btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode,
struct btrfs_path *path, u64 start, u64 end,
u64 *hint_byte, int drop_cache)
{
struct btrfs_root *root = BTRFS_I(inode)->root;
struct extent_buffer *leaf;
struct btrfs_file_extent_item *fi;
struct btrfs_path *path;
struct btrfs_key key;
struct btrfs_key new_key;
u64 ino = btrfs_ino(inode);
Expand All @@ -597,14 +602,11 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
int recow;
int ret;
int modify_tree = -1;
int update_refs = (root->ref_cows || root == root->fs_info->tree_root);

if (drop_cache)
btrfs_drop_extent_cache(inode, start, end - 1, 0);

path = btrfs_alloc_path();
if (!path)
return -ENOMEM;

if (start >= BTRFS_I(inode)->disk_i_size)
modify_tree = 0;

Expand Down Expand Up @@ -707,7 +709,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
extent_end - start);
btrfs_mark_buffer_dirty(leaf);

if (disk_bytenr > 0) {
if (update_refs && disk_bytenr > 0) {
ret = btrfs_inc_extent_ref(trans, root,
disk_bytenr, num_bytes, 0,
root->root_key.objectid,
Expand All @@ -734,7 +736,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
btrfs_set_file_extent_num_bytes(leaf, fi,
extent_end - end);
btrfs_mark_buffer_dirty(leaf);
if (disk_bytenr > 0) {
if (update_refs && disk_bytenr > 0) {
inode_sub_bytes(inode, end - key.offset);
*hint_byte = disk_bytenr;
}
Expand All @@ -753,7 +755,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
btrfs_set_file_extent_num_bytes(leaf, fi,
start - key.offset);
btrfs_mark_buffer_dirty(leaf);
if (disk_bytenr > 0) {
if (update_refs && disk_bytenr > 0) {
inode_sub_bytes(inode, extent_end - start);
*hint_byte = disk_bytenr;
}
Expand All @@ -777,12 +779,13 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
del_nr++;
}

if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
if (update_refs &&
extent_type == BTRFS_FILE_EXTENT_INLINE) {
inode_sub_bytes(inode,
extent_end - key.offset);
extent_end = ALIGN(extent_end,
root->sectorsize);
} else if (disk_bytenr > 0) {
} else if (update_refs && disk_bytenr > 0) {
ret = btrfs_free_extent(trans, root,
disk_bytenr, num_bytes, 0,
root->root_key.objectid,
Expand All @@ -806,7 +809,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
del_nr);
if (ret) {
btrfs_abort_transaction(trans, root, ret);
goto out;
break;
}

del_nr = 0;
Expand All @@ -825,7 +828,22 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
btrfs_abort_transaction(trans, root, ret);
}

out:
btrfs_release_path(path);
return ret;
}

int btrfs_drop_extents(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode, u64 start,
u64 end, u64 *hint_byte, int drop_cache)
{
struct btrfs_path *path;
int ret;

path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
ret = __btrfs_drop_extents(trans, root, inode, path, start, end,
hint_byte, drop_cache);
btrfs_free_path(path);
return ret;
}
Expand Down Expand Up @@ -892,8 +910,6 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
int ret;
u64 ino = btrfs_ino(inode);

btrfs_drop_extent_cache(inode, start, end - 1, 0);

path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
Expand Down Expand Up @@ -1556,6 +1572,14 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
BTRFS_I(inode)->last_trans <=
root->fs_info->last_trans_committed) {
BTRFS_I(inode)->last_trans = 0;

/*
* We'v had everything committed since the last time we were
* modified so clear this flag in case it was set for whatever
* reason, it's no longer relevant.
*/
clear_bit(BTRFS_INODE_NEEDS_FULL_SYNC,
&BTRFS_I(inode)->runtime_flags);
mutex_unlock(&inode->i_mutex);
goto out;
}
Expand Down
Loading

0 comments on commit 5dc562c

Please sign in to comment.