Btrfs: fix split_leaf double split corner case

split_leaf was not properly balancing leaves when it was forced to
split a leaf twice.  This commit adds an extra push left and right
before forcing the double split in hopes of getting the slot where
we want to insert at either the start or end of the leaf.

If the extra pushes do work, then we are able to avoid splitting twice
and we keep the tree properly balanced.

Signed-off-by: Chris Mason <chris.mason@oracle.com>
This commit is contained in:
Chris Mason 2010-07-07 10:51:48 -04:00
Родитель 6f902af400
Коммит 99d8f83c98
1 изменённых файлов: 111 добавлений и 18 удалений

Просмотреть файл

@ -2304,12 +2304,17 @@ noinline int btrfs_leaf_free_space(struct btrfs_root *root,
return ret; return ret;
} }
/*
* min slot controls the lowest index we're willing to push to the
* right. We'll push up to and including min_slot, but no lower
*/
static noinline int __push_leaf_right(struct btrfs_trans_handle *trans, static noinline int __push_leaf_right(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, struct btrfs_path *path,
int data_size, int empty, int data_size, int empty,
struct extent_buffer *right, struct extent_buffer *right,
int free_space, u32 left_nritems) int free_space, u32 left_nritems,
u32 min_slot)
{ {
struct extent_buffer *left = path->nodes[0]; struct extent_buffer *left = path->nodes[0];
struct extent_buffer *upper = path->nodes[1]; struct extent_buffer *upper = path->nodes[1];
@ -2327,7 +2332,7 @@ static noinline int __push_leaf_right(struct btrfs_trans_handle *trans,
if (empty) if (empty)
nr = 0; nr = 0;
else else
nr = 1; nr = max_t(u32, 1, min_slot);
if (path->slots[0] >= left_nritems) if (path->slots[0] >= left_nritems)
push_space += data_size; push_space += data_size;
@ -2469,10 +2474,14 @@ out_unlock:
* *
* returns 1 if the push failed because the other node didn't have enough * returns 1 if the push failed because the other node didn't have enough
* room, 0 if everything worked out and < 0 if there were major errors. * room, 0 if everything worked out and < 0 if there were major errors.
*
* this will push starting from min_slot to the end of the leaf. It won't
* push any slot lower than min_slot
*/ */
static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct btrfs_path *path, int data_size, *root, struct btrfs_path *path,
int empty) int min_data_size, int data_size,
int empty, u32 min_slot)
{ {
struct extent_buffer *left = path->nodes[0]; struct extent_buffer *left = path->nodes[0];
struct extent_buffer *right; struct extent_buffer *right;
@ -2514,8 +2523,8 @@ static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root
if (left_nritems == 0) if (left_nritems == 0)
goto out_unlock; goto out_unlock;
return __push_leaf_right(trans, root, path, data_size, empty, return __push_leaf_right(trans, root, path, min_data_size, empty,
right, free_space, left_nritems); right, free_space, left_nritems, min_slot);
out_unlock: out_unlock:
btrfs_tree_unlock(right); btrfs_tree_unlock(right);
free_extent_buffer(right); free_extent_buffer(right);
@ -2525,12 +2534,17 @@ out_unlock:
/* /*
* push some data in the path leaf to the left, trying to free up at * push some data in the path leaf to the left, trying to free up at
* least data_size bytes. returns zero if the push worked, nonzero otherwise * least data_size bytes. returns zero if the push worked, nonzero otherwise
*
* max_slot can put a limit on how far into the leaf we'll push items. The
* item at 'max_slot' won't be touched. Use (u32)-1 to make us do all the
* items
*/ */
static noinline int __push_leaf_left(struct btrfs_trans_handle *trans, static noinline int __push_leaf_left(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, int data_size, struct btrfs_path *path, int data_size,
int empty, struct extent_buffer *left, int empty, struct extent_buffer *left,
int free_space, int right_nritems) int free_space, u32 right_nritems,
u32 max_slot)
{ {
struct btrfs_disk_key disk_key; struct btrfs_disk_key disk_key;
struct extent_buffer *right = path->nodes[0]; struct extent_buffer *right = path->nodes[0];
@ -2549,9 +2563,9 @@ static noinline int __push_leaf_left(struct btrfs_trans_handle *trans,
slot = path->slots[1]; slot = path->slots[1];
if (empty) if (empty)
nr = right_nritems; nr = min(right_nritems, max_slot);
else else
nr = right_nritems - 1; nr = min(right_nritems - 1, max_slot);
for (i = 0; i < nr; i++) { for (i = 0; i < nr; i++) {
item = btrfs_item_nr(right, i); item = btrfs_item_nr(right, i);
@ -2712,10 +2726,14 @@ out:
/* /*
* push some data in the path leaf to the left, trying to free up at * push some data in the path leaf to the left, trying to free up at
* least data_size bytes. returns zero if the push worked, nonzero otherwise * least data_size bytes. returns zero if the push worked, nonzero otherwise
*
* max_slot can put a limit on how far into the leaf we'll push items. The
* item at 'max_slot' won't be touched. Use (u32)-1 to make us push all the
* items
*/ */
static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct btrfs_path *path, int data_size, *root, struct btrfs_path *path, int min_data_size,
int empty) int data_size, int empty, u32 max_slot)
{ {
struct extent_buffer *right = path->nodes[0]; struct extent_buffer *right = path->nodes[0];
struct extent_buffer *left; struct extent_buffer *left;
@ -2761,8 +2779,9 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
goto out; goto out;
} }
return __push_leaf_left(trans, root, path, data_size, return __push_leaf_left(trans, root, path, min_data_size,
empty, left, free_space, right_nritems); empty, left, free_space, right_nritems,
max_slot);
out: out:
btrfs_tree_unlock(left); btrfs_tree_unlock(left);
free_extent_buffer(left); free_extent_buffer(left);
@ -2854,6 +2873,64 @@ static noinline int copy_for_split(struct btrfs_trans_handle *trans,
return ret; return ret;
} }
/*
* double splits happen when we need to insert a big item in the middle
* of a leaf. A double split can leave us with 3 mostly empty leaves:
* leaf: [ slots 0 - N] [ our target ] [ N + 1 - total in leaf ]
* A B C
*
* We avoid this by trying to push the items on either side of our target
* into the adjacent leaves. If all goes well we can avoid the double split
* completely.
*/
static noinline int push_for_double_split(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_path *path,
int data_size)
{
int ret;
int progress = 0;
int slot;
u32 nritems;
slot = path->slots[0];
/*
* try to push all the items after our slot into the
* right leaf
*/
ret = push_leaf_right(trans, root, path, 1, data_size, 0, slot);
if (ret < 0)
return ret;
if (ret == 0)
progress++;
nritems = btrfs_header_nritems(path->nodes[0]);
/*
* our goal is to get our slot at the start or end of a leaf. If
* we've done so we're done
*/
if (path->slots[0] == 0 || path->slots[0] == nritems)
return 0;
if (btrfs_leaf_free_space(root, path->nodes[0]) >= data_size)
return 0;
/* try to push all the items before our slot into the next leaf */
slot = path->slots[0];
ret = push_leaf_left(trans, root, path, 1, data_size, 0, slot);
if (ret < 0)
return ret;
if (ret == 0)
progress++;
if (progress)
return 0;
return 1;
}
/* /*
* split the path's leaf in two, making sure there is at least data_size * split the path's leaf in two, making sure there is at least data_size
* available for the resulting leaf level of the path. * available for the resulting leaf level of the path.
@ -2876,6 +2953,7 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
int wret; int wret;
int split; int split;
int num_doubles = 0; int num_doubles = 0;
int tried_avoid_double = 0;
l = path->nodes[0]; l = path->nodes[0];
slot = path->slots[0]; slot = path->slots[0];
@ -2884,12 +2962,14 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
return -EOVERFLOW; return -EOVERFLOW;
/* first try to make some room by pushing left and right */ /* first try to make some room by pushing left and right */
if (data_size && ins_key->type != BTRFS_DIR_ITEM_KEY) { if (data_size) {
wret = push_leaf_right(trans, root, path, data_size, 0); wret = push_leaf_right(trans, root, path, data_size,
data_size, 0, 0);
if (wret < 0) if (wret < 0)
return wret; return wret;
if (wret) { if (wret) {
wret = push_leaf_left(trans, root, path, data_size, 0); wret = push_leaf_left(trans, root, path, data_size,
data_size, 0, (u32)-1);
if (wret < 0) if (wret < 0)
return wret; return wret;
} }
@ -2923,6 +3003,8 @@ again:
if (mid != nritems && if (mid != nritems &&
leaf_space_used(l, mid, nritems - mid) + leaf_space_used(l, mid, nritems - mid) +
data_size > BTRFS_LEAF_DATA_SIZE(root)) { data_size > BTRFS_LEAF_DATA_SIZE(root)) {
if (data_size && !tried_avoid_double)
goto push_for_double;
split = 2; split = 2;
} }
} }
@ -2939,6 +3021,8 @@ again:
if (mid != nritems && if (mid != nritems &&
leaf_space_used(l, mid, nritems - mid) + leaf_space_used(l, mid, nritems - mid) +
data_size > BTRFS_LEAF_DATA_SIZE(root)) { data_size > BTRFS_LEAF_DATA_SIZE(root)) {
if (data_size && !tried_avoid_double)
goto push_for_double;
split = 2 ; split = 2 ;
} }
} }
@ -3019,6 +3103,13 @@ again:
} }
return ret; return ret;
push_for_double:
push_for_double_split(trans, root, path, data_size);
tried_avoid_double = 1;
if (btrfs_leaf_free_space(root, path->nodes[0]) >= data_size)
return 0;
goto again;
} }
static noinline int setup_leaf_for_split(struct btrfs_trans_handle *trans, static noinline int setup_leaf_for_split(struct btrfs_trans_handle *trans,
@ -3915,13 +4006,15 @@ int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root,
extent_buffer_get(leaf); extent_buffer_get(leaf);
btrfs_set_path_blocking(path); btrfs_set_path_blocking(path);
wret = push_leaf_left(trans, root, path, 1, 1); wret = push_leaf_left(trans, root, path, 1, 1,
1, (u32)-1);
if (wret < 0 && wret != -ENOSPC) if (wret < 0 && wret != -ENOSPC)
ret = wret; ret = wret;
if (path->nodes[0] == leaf && if (path->nodes[0] == leaf &&
btrfs_header_nritems(leaf)) { btrfs_header_nritems(leaf)) {
wret = push_leaf_right(trans, root, path, 1, 1); wret = push_leaf_right(trans, root, path, 1,
1, 1, 0);
if (wret < 0 && wret != -ENOSPC) if (wret < 0 && wret != -ENOSPC)
ret = wret; ret = wret;
} }