Skip to contentSkip to conversation

btrfs: log mode COW

PATCH New Yan, Zheng 1 participant 1 message May 11, 2010 Btrfs file system

lore
Share & tools
lore

Yan, Zheng

author
#0 lore raw

PATCH 5/5 btrfs: log mode COW

The aim of this patch is solving extent tree performance issue when free metadata space are scarce and fragmented. The basic idea of log mode COW is: when cowing a non-shared block, we insert a log entry that maps the new block to the old block into a special log tree, and do not update the extent tree. After transaction is fully committed, we copy the new block back to the old location. Since tree blocks used by the log tree are not recorded in the extent tree, updating extent tree is no longer recursive.

---

fs/btrfs/ctree.c +150 -81
diff -urpN 5/fs/btrfs/ctree.c 6/fs/btrfs/ctree.c
@@ -276,15 +276,44 @@ int btrfs_block_can_be_shared(struct btr
 	return 0;
 }
 
+struct __btrfs_block_info {
+	u64 refs;
+	u64 flags;
+};
+
+static noinline int lookup_tree_block_info(struct btrfs_trans_handle *trans,
+					   struct btrfs_root *root,
+					   struct extent_buffer *buf,
+					   struct __btrfs_block_info *info)
+{
+	if (root->root_key.objectid == BTRFS_TREE_LOG_OBJECTID ||
+	    root->root_key.objectid == BTRFS_EXTENT_LOG_OBJECTID) {
+		info->refs = 0;
+		info->flags = 0;
+	} else if (btrfs_block_can_be_shared(root, buf)) {
+		int ret;
+		ret = btrfs_lookup_extent_info(trans, root,
+						buf->start, buf->len,
+						&info->refs, &info->flags);
+		BUG_ON(ret);
+	} else {
+		info->refs = 1;
+		if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
+		    btrfs_header_backref_rev(buf) < BTRFS_MIXED_BACKREF_REV)
+			info->flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF;
+		else
+			info->flags = 0;
+	}
+	return 0;
+}
+
 static noinline int update_ref_for_cow(struct btrfs_trans_handle *trans,
 				       struct btrfs_root *root,
 				       struct extent_buffer *buf,
 				       struct extent_buffer *cow,
-				       int *last_ref)
+				       struct __btrfs_block_info *info)
 {
-	u64 refs;
 	u64 owner;
-	u64 flags;
 	u64 new_flags = 0;
 	int ret;
 
@@ -305,28 +334,14 @@ static noinline int update_ref_for_cow(s
 	 * are only allowed for blocks use full backrefs.
 	 */
 
-	if (btrfs_block_can_be_shared(root, buf)) {
-		ret = btrfs_lookup_extent_info(trans, root, buf->start,
-					       buf->len, &refs, &flags);
-		BUG_ON(ret);
-		BUG_ON(refs == 0);
-	} else {
-		refs = 1;
-		if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
-		    btrfs_header_backref_rev(buf) < BTRFS_MIXED_BACKREF_REV)
-			flags = BTRFS_BLOCK_FLAG_FULL_BACKREF;
-		else
-			flags = 0;
-	}
-
 	owner = btrfs_header_owner(buf);
 	BUG_ON(owner == BTRFS_TREE_RELOC_OBJECTID &&
-	       !(flags & BTRFS_BLOCK_FLAG_FULL_BACKREF));
+	       !(info->flags & BTRFS_BLOCK_FLAG_FULL_BACKREF));
 
-	if (refs > 1) {
+	if (info->refs > 1) {
 		if ((owner == root->root_key.objectid ||
 		     root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID) &&
-		    !(flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)) {
+		    !(info->flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)) {
 			ret = btrfs_inc_ref(trans, root, buf, 1);
 			BUG_ON(ret);
 
@@ -349,11 +364,11 @@ static noinline int update_ref_for_cow(s
 		}
 		if (new_flags != 0) {
 			ret = btrfs_update_tree_block_info(trans, root, buf,
-							   NULL, new_flags, 0);
+							NULL, new_flags, 0);
 			BUG_ON(ret);
 		}
 	} else {
-		if (flags & BTRFS_BLOCK_FLAG_FULL_BACKREF) {
+		if (info->flags & BTRFS_BLOCK_FLAG_FULL_BACKREF) {
 			if (root->root_key.objectid ==
 			    BTRFS_TREE_RELOC_OBJECTID)
 				ret = btrfs_inc_ref(trans, root, cow, 1);
@@ -362,61 +377,41 @@ static noinline int update_ref_for_cow(s
 			BUG_ON(ret);
 			ret = btrfs_dec_ref(trans, root, buf, 1);
 			BUG_ON(ret);
+		} else {
+			BUG_ON(root->root_key.objectid != owner);
+			BUG_ON(root->root_key.objectid ==
+			       BTRFS_TREE_RELOC_OBJECTID);
 		}
 		clean_tree_block(trans, root, buf);
-		*last_ref = 1;
 	}
 	return 0;
 }
 
-/*
- * does the dirty work in cow of a single block.  The parent block (if
- * supplied) is updated to point to the new cow copy.  The new buffer is marked
- * dirty and returned locked.  If you modify the block it needs to be marked
- * dirty again.
- *
- * search_start -- an allocation hint for the new block
- *
- * empty_size -- a hint that you plan on doing more cow.  This is the size in
- * bytes the allocator should try to find free next to the block it returns.
- * This is just a hint and may be ignored by the allocator.
- */
-static noinline int __btrfs_cow_block(struct btrfs_trans_handle *trans,
-			     struct btrfs_root *root,
-			     struct extent_buffer *buf,
-			     struct extent_buffer *parent, int parent_slot,
-			     struct extent_buffer **cow_ret,
-			     u64 search_start, u64 empty_size)
+static noinline int do_cow_block(struct btrfs_trans_handle *trans,
+				 struct btrfs_root *root,
+				 struct extent_buffer *buf,
+				 struct extent_buffer *parent,
+				 struct extent_buffer **cow_ret,
+				 struct __btrfs_block_info *info,
+				 u64 search_start, u64 empty_size)
 {
 	struct btrfs_disk_key disk_key;
 	struct extent_buffer *cow;
 	int level;
-	int unlock_orig = 0;
-	int last_ref = 0;
 	u64 parent_start;
 
-	if (*cow_ret == buf)
-		unlock_orig = 1;
-
-	btrfs_assert_tree_locked(buf);
-
-	WARN_ON(root->ref_cows && trans->transid !=
-		root->fs_info->running_transaction->transid);
-	WARN_ON(root->ref_cows && trans->transid != root->last_trans);
-
 	level = btrfs_header_level(buf);
-
-	if (level == 0)
-		btrfs_item_key(buf, &disk_key, 0);
-	else
-		btrfs_node_key(buf, &disk_key, 0);
-
-	if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID) {
-		if (parent)
-			parent_start = parent->start;
+	if (btrfs_header_nritems(buf) > 0) {
+		if (level == 0)
+			btrfs_item_key(buf, &disk_key, 0);
 		else
-			parent_start = 0;
+			btrfs_node_key(buf, &disk_key, 0);
 	} else
+		memset(&disk_key, 0, sizeof(disk_key));
+
+	if (parent && root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID)
+		parent_start = parent->start;
+	else
 		parent_start = 0;
 
 	cow = btrfs_alloc_free_block(trans, root, buf->len, parent_start,
@@ -426,13 +421,13 @@ static noinline int __btrfs_cow_block(st
 		return PTR_ERR(cow);
 
 	/* cow is set to blocking by btrfs_init_new_buffer */
-
 	copy_extent_buffer(cow, buf, 0, 0, cow->len);
 	btrfs_set_header_bytenr(cow, cow->start);
 	btrfs_set_header_generation(cow, trans->transid);
 	btrfs_set_header_backref_rev(cow, BTRFS_MIXED_BACKREF_REV);
 	btrfs_clear_header_flag(cow, BTRFS_HEADER_FLAG_WRITTEN |
-				     BTRFS_HEADER_FLAG_RELOC);
+				     BTRFS_HEADER_FLAG_RELOC |
+				     BTRFS_HEADER_FLAG_LOGS);
 	if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID)
 		btrfs_set_header_flag(cow, BTRFS_HEADER_FLAG_RELOC);
 	else
@@ -442,41 +437,115 @@ static noinline int __btrfs_cow_block(st
 			    (unsigned long)btrfs_header_fsid(cow),
 			    BTRFS_FSID_SIZE);
 
-	update_ref_for_cow(trans, root, buf, cow, &last_ref);
+	if (info->refs > 0)
+		update_ref_for_cow(trans, root, buf, cow, info);
+
+	btrfs_mark_buffer_dirty(cow);
+	*cow_ret = cow;
+	return 0;
+}
+
+static noinline int setup_ptr_for_cow(struct btrfs_trans_handle *trans,
+				      struct btrfs_root *root,
+				      struct extent_buffer *buf,
+				      struct extent_buffer *cow,
+				      struct extent_buffer *parent, int pslot,
+				      int free_old, int last_ref)
+{
+	u64 parent_start;
 
 	if (buf == root->node) {
 		WARN_ON(parent && parent != buf);
-		if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
-		    btrfs_header_backref_rev(buf) < BTRFS_MIXED_BACKREF_REV)
-			parent_start = buf->start;
-		else
-			parent_start = 0;
 
+		extent_buffer_get(cow);
 		spin_lock(&root->node_lock);
 		root->node = cow;
-		extent_buffer_get(cow);
 		spin_unlock(&root->node_lock);
 
-		btrfs_free_tree_block(trans, root, buf, parent_start,
-				      last_ref);
 		free_extent_buffer(buf);
 		add_root_to_dirty_list(root);
+
+		if (!free_old)
+			goto out;
+
+		if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID ||
+		    btrfs_header_backref_rev(buf) < BTRFS_MIXED_BACKREF_REV)
+			parent_start = buf->start;
+		else
+			parent_start = 0;
+		btrfs_free_tree_block(trans, root, buf, parent_start,
+				      last_ref);
 	} else {
+		btrfs_set_node_blockptr(parent, pslot, cow->start);
+		btrfs_set_node_ptr_generation(parent, pslot, trans->transid);
+		btrfs_mark_buffer_dirty(parent);
+
+		if (!free_old)
+			goto out;
+
 		if (root->root_key.objectid == BTRFS_TREE_RELOC_OBJECTID)
 			parent_start = parent->start;
 		else
 			parent_start = 0;
-
-		WARN_ON(trans->transid != btrfs_header_generation(parent));
-		btrfs_set_node_blockptr(parent, parent_slot,
-					cow->start);
-		btrfs_set_node_ptr_generation(parent, parent_slot,
-					      trans->transid);
-		btrfs_mark_buffer_dirty(parent);
 		btrfs_free_tree_block(trans, root, buf, parent_start,
 				      last_ref);
 	}
-	if (unlock_orig)
+out:
+	return 0;
+}
+
+/*
+ * does the dirty work in cow of a single block.  The parent block (if
+ * supplied) is updated to point to the new cow copy.  The new buffer is marked
+ * dirty and returned locked.  If you modify the block it needs to be marked
+ * dirty again.
+ *
+ * search_start -- an allocation hint for the new block
+ *
+ * empty_size -- a hint that you plan on doing more cow.  This is the size in
+ * bytes the allocator should try to find free next to the block it returns.
+ * This is just a hint and may be ignored by the allocator.
+ */
+static int __btrfs_cow_block(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root,
+			     struct extent_buffer *buf,
+			     struct extent_buffer *parent, int parent_slot,
+			     struct extent_buffer **cow_ret,
+			     u64 search_start, u64 empty_size)
+{
+	struct __btrfs_block_info info;
+	struct extent_buffer *cow;
+	int ret;
+
+	btrfs_assert_tree_locked(buf);
+	WARN_ON(root->ref_cows && trans->transid !=
+		root->fs_info->running_transaction->transid);
+	WARN_ON(root->ref_cows && trans->transid != root->last_trans);
+
+	lookup_tree_block_info(trans, root, buf, &info);
+
+	if (info.refs == 1 && !(info.flags & BTRFS_BLOCK_FLAG_FULL_BACKREF)) {
+		ret = btrfs_log_cow_block(trans, root, buf, &cow,
+					  search_start, empty_size);
+		if (!ret) {
+			setup_ptr_for_cow(trans, root, buf, cow, parent,
+					  parent_slot, 0, 1);
+			goto done;
+		}
+		if (ret != -EAGAIN)
+			return ret;
+	}
+
+	BUG_ON(btrfs_header_flags(buf) & BTRFS_HEADER_FLAG_LOGS);
+	ret = do_cow_block(trans, root, buf, parent, &cow, &info,
+			   search_start, empty_size);
+	if (ret)
+		return ret;
+
+	setup_ptr_for_cow(trans, root, buf, cow, parent, parent_slot,
+			  1, info.refs <= 1);
+done:
+	if (*cow_ret == buf)
 		btrfs_tree_unlock(buf);
 	free_extent_buffer(buf);
 	btrfs_mark_buffer_dirty(cow);
fs/btrfs/ctree.h +77 -3

169 lines not on this page · raw message

fs/btrfs/disk-io.c +97 -28

265 lines not on this page · raw message

fs/btrfs/disk-io.h +3 -0
diff -urpN 5/fs/btrfs/disk-io.h 6/fs/btrfs/disk-io.h
@@ -101,6 +101,9 @@ int btrfs_init_log_root_tree(struct btrf
 			     struct btrfs_fs_info *fs_info);
 int btrfs_add_log_tree(struct btrfs_trans_handle *trans,
 		       struct btrfs_root *root);
+struct btrfs_root *
+btrfs_alloc_extent_log_tree(struct btrfs_trans_handle *trans,
+			    struct btrfs_fs_info *fs_info);
 int btree_lock_page_hook(struct page *page);
 
 
fs/btrfs/extent-log.c +1560 -0

1,560 lines not on this page · raw message

fs/btrfs/extent-tree.c +74 -6

174 lines not on this page · raw message

fs/btrfs/ioctl.c +7 -0
diff -urpN 5/fs/btrfs/ioctl.c 6/fs/btrfs/ioctl.c
@@ -313,6 +313,7 @@ static noinline int create_subvol(struct
 	new_root = btrfs_read_fs_root_no_name(root->fs_info, &key);
 	BUG_ON(IS_ERR(new_root));
 
+	new_root->last_log_trans = 0;
 	btrfs_record_root_in_trans(trans, new_root);
 
 	ret = btrfs_create_subvol_root(trans, new_root, new_dirid,
@@ -360,6 +361,8 @@ static int create_snapshot(struct btrfs_
 	if (!root->ref_cows)
 		return -EINVAL;
 
+	btrfs_disable_extent_log_sync(root, 0);
+
 	/*
 	 * 1 - inode item
 	 * 2 - refs
@@ -401,9 +404,11 @@ static int create_snapshot(struct btrfs_
 		goto fail;
 	}
 	BUG_ON(!inode);
+	BTRFS_I(inode)->root->last_log_trans = 0;
 	d_instantiate(dentry, inode);
 	ret = 0;
 fail:
+	btrfs_enable_extent_log(root, 0);
 	return ret;
 }
 
@@ -1321,6 +1326,8 @@ static noinline int btrfs_ioctl_snap_des
 	ret = btrfs_commit_transaction(trans, root);
 	BUG_ON(ret);
 	inode->i_flags |= S_DEAD;
+
+	btrfs_disable_extent_log_sync(dest, 0);
 out_up_write:
 	up_write(&root->fs_info->subvol_sem);
 out_unlock:
fs/btrfs/Makefile +1 -1
diff -urpN 5/fs/btrfs/Makefile 6/fs/btrfs/Makefile
@@ -7,4 +7,4 @@ btrfs-y += super.o ctree.o extent-tree.o
 	   extent_map.o sysfs.o struct-funcs.o xattr.o ordered-data.o \
 	   extent_io.o volumes.o async-thread.o ioctl.o locking.o orphan.o \
 	   export.o tree-log.o acl.o free-space-cache.o zlib.o \
-	   compression.o delayed-ref.o relocation.o
+	   compression.o delayed-ref.o relocation.o extent-log.o
fs/btrfs/relocation.c +4 -0
diff -urpN 5/fs/btrfs/relocation.c 6/fs/btrfs/relocation.c
@@ -3293,6 +3293,8 @@ static noinline_for_stack int relocate_b
 	clear_extent_bits(&rc->processed_blocks, 0, (u64)-1, EXTENT_DIRTY,
 			  GFP_NOFS);
 
+	btrfs_disable_extent_log_sync(rc->extent_root, 1);
+
 	rc->create_reloc_root = 1;
 	set_reloc_control(rc);
 
@@ -3418,6 +3420,8 @@ static noinline_for_stack int relocate_b
 
 	unset_reloc_control(rc);
 
+	btrfs_enable_extent_log(rc->extent_root, 1);
+
 	/* get rid of pinned extents */
 	trans = btrfs_start_transaction(rc->extent_root, 1);
 	btrfs_commit_transaction(trans, rc->extent_root);
fs/btrfs/super.c +8 -2
diff -urpN 5/fs/btrfs/super.c 6/fs/btrfs/super.c
@@ -67,7 +67,7 @@ enum {
 	Opt_nodatacow, Opt_max_inline, Opt_alloc_start, Opt_nobarrier, Opt_ssd,
 	Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress,
 	Opt_compress_force, Opt_notreelog, Opt_ratio, Opt_flushoncommit,
-	Opt_discard, Opt_err,
+	Opt_discard, Opt_log_mode, Opt_err,
 };
 
 static match_table_t tokens = {
@@ -91,6 +91,7 @@ static match_table_t tokens = {
 	{Opt_flushoncommit, "flushoncommit"},
 	{Opt_ratio, "metadata_ratio=%d"},
 	{Opt_discard, "discard"},
+	{Opt_log_mode, "log_mode=%d"},
 	{Opt_err, NULL},
 };
 
@@ -234,6 +235,11 @@ int btrfs_parse_options(struct btrfs_roo
 		case Opt_discard:
 			btrfs_set_opt(info->mount_opt, DISCARD);
 			break;
+		case Opt_log_mode:
+			intarg = 0;
+			if (!match_int(&args[0], &intarg))
+				btrfs_set_extent_log_mode(info, intarg);
+			break;
 		case Opt_err:
 			printk(KERN_INFO "btrfs: unrecognized mount option "
 			       "'%s'\n", p);
@@ -497,7 +503,7 @@ int btrfs_sync_fs(struct super_block *sb
 	btrfs_start_delalloc_inodes(root, 0);
 	btrfs_wait_ordered_extents(root, 0, 0);
 
-	trans = btrfs_start_transaction(root, 1);
+	trans = btrfs_join_transaction(root, 1);
 	ret = btrfs_commit_transaction(trans, root);
 	return ret;
 }
fs/btrfs/transaction.c +13 -3
diff -urpN 5/fs/btrfs/transaction.c 6/fs/btrfs/transaction.c
@@ -67,6 +67,7 @@ static noinline int join_transaction(str
 		cur_trans->blocked = 0;
 		cur_trans->use_count = 1;
 		cur_trans->commit_done = 0;
+		cur_trans->replay_log = 0;
 		cur_trans->start_time = get_seconds();
 
 		cur_trans->delayed_refs.root = RB_ROOT;
@@ -85,6 +86,8 @@ static noinline int join_transaction(str
 		spin_lock(&root->fs_info->new_trans_lock);
 		root->fs_info->running_transaction = cur_trans;
 		spin_unlock(&root->fs_info->new_trans_lock);
+
+		btrfs_async_replay_extent_log(root);
 	} else {
 		cur_trans->num_writers++;
 		cur_trans->num_joined++;
@@ -312,6 +315,8 @@ static int __btrfs_end_transaction(struc
 		count++;
 	}
 
+	btrfs_flush_extent_log(trans, root, 0);
+
 	mutex_lock(&info->trans_mutex);
 	cur_trans = info->running_transaction;
 	WARN_ON(cur_trans != trans->transaction);
@@ -547,12 +552,16 @@ static noinline int commit_cowonly_roots
 	ret = btrfs_run_delayed_refs(trans, root, (unsigned long)-1);
 	BUG_ON(ret);
 
+	btrfs_flush_extent_log(trans, root, 1);
+
 	while (!list_empty(&fs_info->dirty_cowonly_roots)) {
 		next = fs_info->dirty_cowonly_roots.next;
 		list_del_init(next);
 		root = list_entry(next, struct btrfs_root, dirty_list);
 
 		update_cowonly_root(trans, root);
+
+		btrfs_flush_extent_log(trans, root, 1);
 	}
 
 	down_write(&fs_info->extent_commit_sem);
@@ -975,6 +984,9 @@ int btrfs_commit_transaction(struct btrf
 		 */
 		btrfs_run_ordered_operations(root, 1);
 
+		ret = btrfs_replay_extent_log(trans, root, 1);
+		BUG_ON(ret);
+
 		smp_mb();
 		if (cur_trans->num_writers > 1 || should_grow)
 			schedule_timeout(timeout);
@@ -1058,14 +1070,12 @@ int btrfs_commit_transaction(struct btrf
 	 */
 	mutex_unlock(&root->fs_info->tree_log_mutex);
 
+	root->fs_info->last_trans_committed = cur_trans->transid;
 	btrfs_finish_extent_commit(trans, root);
 
 	mutex_lock(&root->fs_info->trans_mutex);
 
 	cur_trans->commit_done = 1;
-
-	root->fs_info->last_trans_committed = cur_trans->transid;
-
 	wake_up(&cur_trans->commit_wait);
 
 	put_transaction(cur_trans);
fs/btrfs/transaction.h +1 -0
diff -urpN 5/fs/btrfs/transaction.h 6/fs/btrfs/transaction.h
@@ -34,6 +34,7 @@ struct btrfs_transaction {
 	int use_count;
 	int commit_done;
 	int blocked;
+	int replay_log;
 	struct list_head list;
 	struct extent_io_tree dirty_pages;
 	unsigned long start_time;
fs/btrfs/tree-log.c +37 -0
diff -urpN 5/fs/btrfs/tree-log.c 6/fs/btrfs/tree-log.c
@@ -3188,3 +3188,40 @@ int btrfs_log_new_name(struct btrfs_tran
 	return btrfs_log_inode_parent(trans, root, inode, parent, 1);
 }
 
+struct __walker_struct {
+	int (*proc)(struct btrfs_root *root,
+		    struct extent_buffer *eb, void *data);
+	void *data;
+};
+
+static int __process_buffer(struct btrfs_root *root,
+			    struct extent_buffer *eb,
+			    struct walk_control *wc, u64 gen)
+{
+	struct __walker_struct *walker;
+	int ret;
+
+	walker = (struct __walker_struct *)wc->replay_dest;
+
+	ret = btrfs_read_buffer(eb, gen);
+	BUG_ON(ret);
+
+	ret = walker->proc(root, eb, walker->data);
+	return ret;
+}
+
+int btrfs_walk_log_tree(struct btrfs_root *root, void *data,
+			int (*proc)(struct btrfs_root *root,
+				    struct extent_buffer *eb, void *data))
+{
+	struct __walker_struct walker = {
+		.proc = proc,
+		.data = data,
+	};
+	struct walk_control wc = {
+		.process_func = __process_buffer,
+		.replay_dest = (struct btrfs_root *)&walker,
+	};
+
+	return walk_log_tree(NULL, root, &wc);
+}
fs/btrfs/tree-log.h +3 -0
diff -urpN 5/fs/btrfs/tree-log.h 6/fs/btrfs/tree-log.h
@@ -48,4 +48,7 @@ void btrfs_record_unlink_dir(struct btrf
 int btrfs_log_new_name(struct btrfs_trans_handle *trans,
 			struct inode *inode, struct inode *old_dir,
 			struct dentry *parent);
+int btrfs_walk_log_tree(struct btrfs_root *root, void *data,
+			int (*proc)(struct btrfs_root *root,
+				    struct extent_buffer *eb, void *data));
 #endif