From 847916860d6bf5a0a12aa556aeacd3652a5938d1 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Wed, 28 Jun 2023 16:47:41 -0700 Subject: [PATCH] Advance move_blocks extent search offset The move_blocks ioctl finds extents to move in the source file by searching from the starting block offset of the region to move. Logically, this is fine. After each extent item is deleted the next search will find the next extent. The problem is that deleted items still exist in the item cache. The next iteration has to skip over all the deleted extents from the start of the region. This is fine with large extents, but with heavily fragmented extents this creates a huge amplification of the number of items to traverse when moving the fragmented extents in a large file. (It's not quite O(n^2)/2 for the total extents, deleted items are purged as we write out the dirty items in each transaction.. but it's still immense.) The fix is to simply start searching for the next extent after the one we just moved. Signed-off-by: Zach Brown --- kmod/src/data.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kmod/src/data.c b/kmod/src/data.c index e05c23c4..6b581a3b 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -1267,6 +1267,7 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, from_iblock = from_off >> SCOUTFS_BLOCK_SM_SHIFT; count = (byte_len + SCOUTFS_BLOCK_SM_MASK) >> SCOUTFS_BLOCK_SM_SHIFT; to_iblock = to_off >> SCOUTFS_BLOCK_SM_SHIFT; + from_start = from_iblock; /* only move extent blocks inside i_size, careful not to wrap */ from_size = i_size_read(from); @@ -1343,7 +1344,7 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, /* find the next extent to move */ ret = scoutfs_ext_next(sb, &data_ext_ops, &from_args, - from_iblock, 1, &ext); + from_start, 1, &ext); if (ret < 0) { if (ret == -ENOENT) { done = true; @@ -1431,6 +1432,12 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, i_size_read(from); i_size_write(to, to_size); } + + /* find next after moved extent, avoiding wrapping */ + if (from_start + len < from_start) + from_start = from_iblock + count + 1; + else + from_start += len; }