btrfs: log mode COW
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.
Signed-off-by Yan Zheng <[email protected]>
---
fs/btrfs/ctree.c +150 -81
@@ -276,15 +276,44 @@ int btrfs_block_can_be_shared(struct btrreturn 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(sBUG_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(streturn 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
@@ -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
@@ -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
@@ -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
@@ -3293,6 +3293,8 @@ static noinline_for_stack int relocate_bclear_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_bunset_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
@@ -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_roocase 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 *sbbtrfs_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
@@ -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(strspin_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
@@ -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
@@ -3188,3 +3188,40 @@ int btrfs_log_new_name(struct btrfs_tranreturn 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
@@ -48,4 +48,7 @@ void btrfs_record_unlink_dir(struct btrfint 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