diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index bf7e3f23bba7..68790782bb00 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2021,8 +2021,7 @@ static u64 update_block_group_flags(struct btrfs_fs_info *fs_info, u64 flags)
 	return flags;
 }
 
-int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache)
-
+static int __btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache, bool force)
 {
 	struct btrfs_fs_info *fs_info = cache->fs_info;
 	struct btrfs_trans_handle *trans;
@@ -2057,7 +2056,7 @@ int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache)
 	 * block group with the new raid level.
 	 */
 	alloc_flags = update_block_group_flags(fs_info, cache->flags);
-	if (alloc_flags != cache->flags) {
+	if (alloc_flags != cache->flags && !force) {
 		ret = btrfs_chunk_alloc(trans, alloc_flags, CHUNK_ALLOC_FORCE);
 		/*
 		 * ENOSPC is allowed here, we may have enough space
@@ -2070,8 +2069,8 @@ int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache)
 			goto out;
 	}
 
-	ret = inc_block_group_ro(cache, 0);
-	if (!ret)
+	ret = inc_block_group_ro(cache, force);
+	if (!ret || force)
 		goto out;
 	alloc_flags = btrfs_get_alloc_profile(fs_info, cache->space_info->flags);
 	ret = btrfs_chunk_alloc(trans, alloc_flags, CHUNK_ALLOC_FORCE);
@@ -2091,6 +2090,30 @@ int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache)
 	return ret;
 }
 
+/*
+ * Mark one block group read only so we won't allocate extent from this bg.
+ *
+ * This variant can return -ENOSPC as it will do extra free space check, and
+ * may allocate new chunk if there is not enough free space.
+ * This is normally used by relocation as relocation needs to do new write
+ * immediately after marking a bg RO.
+ */
+int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache)
+{
+	return __btrfs_inc_block_group_ro(cache, false);
+}
+
+/*
+ * Pretty much the same as btrfs_inc_block_group_ro(), but without the free
+ * space check nor chunk allocation.
+ * This variant is mostly used by scrub/replace, as there is no immediate write
+ * after marking a block group RO.
+ */
+int btrfs_inc_block_group_ro_force(struct btrfs_block_group_cache *cache)
+{
+	return __btrfs_inc_block_group_ro(cache, false);
+}
+
 void btrfs_dec_block_group_ro(struct btrfs_block_group_cache *cache)
 {
 	struct btrfs_space_info *sinfo = cache->space_info;
diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h
index c391800388dd..07b6c1905fef 100644
--- a/fs/btrfs/block-group.h
+++ b/fs/btrfs/block-group.h
@@ -206,6 +206,7 @@ int btrfs_make_block_group(struct btrfs_trans_handle *trans, u64 bytes_used,
 			   u64 type, u64 chunk_offset, u64 size);
 void btrfs_create_pending_block_groups(struct btrfs_trans_handle *trans);
 int btrfs_inc_block_group_ro(struct btrfs_block_group_cache *cache);
+int btrfs_inc_block_group_ro_force(struct btrfs_block_group_cache *cache);
 void btrfs_dec_block_group_ro(struct btrfs_block_group_cache *cache);
 int btrfs_start_dirty_block_groups(struct btrfs_trans_handle *trans);
 int btrfs_write_dirty_block_groups(struct btrfs_trans_handle *trans);
diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c
index f7d4e03f4c5d..40a0d8b1a602 100644
--- a/fs/btrfs/scrub.c
+++ b/fs/btrfs/scrub.c
@@ -3563,7 +3563,26 @@ int scrub_enumerate_chunks(struct scrub_ctx *sctx,
 		 * -> btrfs_scrub_pause()
 		 */
 		scrub_pause_on(fs_info);
-		ret = btrfs_inc_block_group_ro(cache);
+
+		/*
+		 * Scrub itself doesn't always cause new write, so we don't need
+		 * to care free space right now.
+		 *
+		 * This forced RO is especially important for SYSTEM bgs, or we
+		 * can hit -EFBIG from btrfs_finish_chunk_alloc():
+		 * 1. The only SYSTEM bg is marked RO.
+		 *    Since SYSTEM bg is small, that's pretty common.
+		 * 2. New SYSTEM bg will be allocated
+		 *    Due to regular version will allocate new chunk.
+		 * 3. New SYSTEM bg is empty and will get cleaned up
+		 *    Before cleanup really happens, it's marked RO again.
+		 * 4. Empty SYSTEM bg get scrubbed
+		 *    We go back to 2.
+		 * This can easily boost the amount of SYSTEM chunks if cleaner
+		 * thread can't be triggered fast enough, and use up all space
+		 * of btrfs_super_block::sys_chunk_array
+		 */
+		ret = btrfs_inc_block_group_ro_force(cache);
 		if (!ret && sctx->is_dev_replace) {
 			/*
 			 * If we are doing a device replace wait for any tasks
