Commit ecb94f5f authored by Theodore Ts'o's avatar Theodore Ts'o

ext4: collapse a single extent tree block into the inode if possible

If an inode has more than 4 extents, but then later some of the
extents are merged together, we can optimize the file system by moving
the extents up into the inode, and discarding the extent tree block.
This is important, because if there are a large number of inodes with
an external extent tree blocks where the contents could fit in the
inode, this can significantly increase the fsck time of the file
system.

Google-Bug-Id: 6801242
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 89a4e48f
...@@ -1655,17 +1655,61 @@ static int ext4_ext_try_to_merge_right(struct inode *inode, ...@@ -1655,17 +1655,61 @@ static int ext4_ext_try_to_merge_right(struct inode *inode,
return merge_done; return merge_done;
} }
/*
* This function does a very simple check to see if we can collapse
* an extent tree with a single extent tree leaf block into the inode.
*/
static void ext4_ext_try_to_merge_up(handle_t *handle,
struct inode *inode,
struct ext4_ext_path *path)
{
size_t s;
unsigned max_root = ext4_ext_space_root(inode, 0);
ext4_fsblk_t blk;
if ((path[0].p_depth != 1) ||
(le16_to_cpu(path[0].p_hdr->eh_entries) != 1) ||
(le16_to_cpu(path[1].p_hdr->eh_entries) > max_root))
return;
/*
* We need to modify the block allocation bitmap and the block
* group descriptor to release the extent tree block. If we
* can't get the journal credits, give up.
*/
if (ext4_journal_extend(handle, 2))
return;
/*
* Copy the extent data up to the inode
*/
blk = ext4_idx_pblock(path[0].p_idx);
s = le16_to_cpu(path[1].p_hdr->eh_entries) *
sizeof(struct ext4_extent_idx);
s += sizeof(struct ext4_extent_header);
memcpy(path[0].p_hdr, path[1].p_hdr, s);
path[0].p_depth = 0;
path[0].p_ext = EXT_FIRST_EXTENT(path[0].p_hdr) +
(path[1].p_ext - EXT_FIRST_EXTENT(path[1].p_hdr));
path[0].p_hdr->eh_max = cpu_to_le16(max_root);
brelse(path[1].p_bh);
ext4_free_blocks(handle, inode, NULL, blk, 1,
EXT4_FREE_BLOCKS_METADATA | EXT4_FREE_BLOCKS_FORGET);
}
/* /*
* This function tries to merge the @ex extent to neighbours in the tree. * This function tries to merge the @ex extent to neighbours in the tree.
* return 1 if merge left else 0. * return 1 if merge left else 0.
*/ */
static int ext4_ext_try_to_merge(struct inode *inode, static void ext4_ext_try_to_merge(handle_t *handle,
struct inode *inode,
struct ext4_ext_path *path, struct ext4_ext_path *path,
struct ext4_extent *ex) { struct ext4_extent *ex) {
struct ext4_extent_header *eh; struct ext4_extent_header *eh;
unsigned int depth; unsigned int depth;
int merge_done = 0; int merge_done = 0;
int ret = 0;
depth = ext_depth(inode); depth = ext_depth(inode);
BUG_ON(path[depth].p_hdr == NULL); BUG_ON(path[depth].p_hdr == NULL);
...@@ -1675,9 +1719,9 @@ static int ext4_ext_try_to_merge(struct inode *inode, ...@@ -1675,9 +1719,9 @@ static int ext4_ext_try_to_merge(struct inode *inode,
merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1); merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1);
if (!merge_done) if (!merge_done)
ret = ext4_ext_try_to_merge_right(inode, path, ex); (void) ext4_ext_try_to_merge_right(inode, path, ex);
return ret; ext4_ext_try_to_merge_up(handle, inode, path);
} }
/* /*
...@@ -1893,7 +1937,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode, ...@@ -1893,7 +1937,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
merge: merge:
/* try to merge extents */ /* try to merge extents */
if (!(flag & EXT4_GET_BLOCKS_PRE_IO)) if (!(flag & EXT4_GET_BLOCKS_PRE_IO))
ext4_ext_try_to_merge(inode, path, nearex); ext4_ext_try_to_merge(handle, inode, path, nearex);
/* time to correct all indexes above */ /* time to correct all indexes above */
...@@ -1901,7 +1945,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode, ...@@ -1901,7 +1945,7 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
if (err) if (err)
goto cleanup; goto cleanup;
err = ext4_ext_dirty(handle, inode, path + depth); err = ext4_ext_dirty(handle, inode, path + path->p_depth);
cleanup: cleanup:
if (npath) { if (npath) {
...@@ -2924,9 +2968,9 @@ static int ext4_split_extent_at(handle_t *handle, ...@@ -2924,9 +2968,9 @@ static int ext4_split_extent_at(handle_t *handle,
ext4_ext_mark_initialized(ex); ext4_ext_mark_initialized(ex);
if (!(flags & EXT4_GET_BLOCKS_PRE_IO)) if (!(flags & EXT4_GET_BLOCKS_PRE_IO))
ext4_ext_try_to_merge(inode, path, ex); ext4_ext_try_to_merge(handle, inode, path, ex);
err = ext4_ext_dirty(handle, inode, path + depth); err = ext4_ext_dirty(handle, inode, path + path->p_depth);
goto out; goto out;
} }
...@@ -2958,8 +3002,8 @@ static int ext4_split_extent_at(handle_t *handle, ...@@ -2958,8 +3002,8 @@ static int ext4_split_extent_at(handle_t *handle,
goto fix_extent_len; goto fix_extent_len;
/* update the extent length and mark as initialized */ /* update the extent length and mark as initialized */
ex->ee_len = cpu_to_le16(ee_len); ex->ee_len = cpu_to_le16(ee_len);
ext4_ext_try_to_merge(inode, path, ex); ext4_ext_try_to_merge(handle, inode, path, ex);
err = ext4_ext_dirty(handle, inode, path + depth); err = ext4_ext_dirty(handle, inode, path + path->p_depth);
goto out; goto out;
} else if (err) } else if (err)
goto fix_extent_len; goto fix_extent_len;
...@@ -3191,8 +3235,8 @@ static int ext4_ext_convert_to_initialized(handle_t *handle, ...@@ -3191,8 +3235,8 @@ static int ext4_ext_convert_to_initialized(handle_t *handle,
if (err) if (err)
goto out; goto out;
ext4_ext_mark_initialized(ex); ext4_ext_mark_initialized(ex);
ext4_ext_try_to_merge(inode, path, ex); ext4_ext_try_to_merge(handle, inode, path, ex);
err = ext4_ext_dirty(handle, inode, path + depth); err = ext4_ext_dirty(handle, inode, path + path->p_depth);
goto out; goto out;
} }
...@@ -3333,10 +3377,10 @@ static int ext4_convert_unwritten_extents_endio(handle_t *handle, ...@@ -3333,10 +3377,10 @@ static int ext4_convert_unwritten_extents_endio(handle_t *handle,
/* note: ext4_ext_correct_indexes() isn't needed here because /* note: ext4_ext_correct_indexes() isn't needed here because
* borders are not changed * borders are not changed
*/ */
ext4_ext_try_to_merge(inode, path, ex); ext4_ext_try_to_merge(handle, inode, path, ex);
/* Mark modified extent as dirty */ /* Mark modified extent as dirty */
err = ext4_ext_dirty(handle, inode, path + depth); err = ext4_ext_dirty(handle, inode, path + path->p_depth);
out: out:
ext4_ext_show_leaf(inode, path); ext4_ext_show_leaf(inode, path);
return err; return err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment