btrfs: qgroup: Make qgroup async transaction commit more aggressive
authorQu Wenruo <wqu@suse.com>
Thu, 24 Jan 2019 23:55:27 +0000 (07:55 +0800)
committerDavid Sterba <dsterba@suse.com>
Mon, 25 Feb 2019 13:13:39 +0000 (14:13 +0100)
[BUG]
Btrfs qgroup will still hit EDQUOT under the following case:

  $ dev=/dev/test/test
  $ mnt=/mnt/btrfs
  $ umount $mnt &> /dev/null
  $ umount $dev &> /dev/null

  $ mkfs.btrfs -f $dev
  $ mount $dev $mnt -o nospace_cache

  $ btrfs subv create $mnt/subv
  $ btrfs quota enable $mnt
  $ btrfs quota rescan -w $mnt
  $ btrfs qgroup limit -e 1G $mnt/subv

  $ fallocate -l 900M $mnt/subv/padding
  $ sync

  $ rm $mnt/subv/padding

  # Hit EDQUOT
  $ xfs_io -f -c "pwrite 0 512M" $mnt/subv/real_file

[CAUSE]
Since commit a514d63882c3 ("btrfs: qgroup: Commit transaction in advance
to reduce early EDQUOT"), btrfs is not forced to commit transaction to
reclaim more quota space.

Instead, we just check pertrans metadata reservation against some
threshold and try to do asynchronously transaction commit.

However in above case, the pertrans metadata reservation is pretty small
thus it will never trigger asynchronous transaction commit.

[FIX]
Instead of only accounting pertrans metadata reservation, we calculate
how much free space we have, and if there isn't much free space left,
commit transaction asynchronously to try to free some space.

This may slow down the fs when we have less than 32M free qgroup space,
but should reduce a lot of false EDQUOT, so the cost should be
acceptable.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/qgroup.c

index e618ea9cdf7ee16bb7ae7d9e90a2356f76b2eeba..c1cd5558a6462ce6b3fc4520d9dc4ec011c991d5 100644 (file)
@@ -2794,16 +2794,15 @@ out:
 /*
  * Two limits to commit transaction in advance.
  *
 /*
  * Two limits to commit transaction in advance.
  *
- * For RATIO, it will be 1/RATIO of the remaining limit
- * (excluding data and prealloc meta) as threshold.
+ * For RATIO, it will be 1/RATIO of the remaining limit as threshold.
  * For SIZE, it will be in byte unit as threshold.
  */
  * For SIZE, it will be in byte unit as threshold.
  */
-#define QGROUP_PERTRANS_RATIO          32
-#define QGROUP_PERTRANS_SIZE           SZ_32M
+#define QGROUP_FREE_RATIO              32
+#define QGROUP_FREE_SIZE               SZ_32M
 static bool qgroup_check_limits(struct btrfs_fs_info *fs_info,
                                const struct btrfs_qgroup *qg, u64 num_bytes)
 {
 static bool qgroup_check_limits(struct btrfs_fs_info *fs_info,
                                const struct btrfs_qgroup *qg, u64 num_bytes)
 {
-       u64 limit;
+       u64 free;
        u64 threshold;
 
        if ((qg->lim_flags & BTRFS_QGROUP_LIMIT_MAX_RFER) &&
        u64 threshold;
 
        if ((qg->lim_flags & BTRFS_QGROUP_LIMIT_MAX_RFER) &&
@@ -2822,20 +2821,21 @@ static bool qgroup_check_limits(struct btrfs_fs_info *fs_info,
         */
        if ((qg->lim_flags & (BTRFS_QGROUP_LIMIT_MAX_RFER |
                              BTRFS_QGROUP_LIMIT_MAX_EXCL))) {
         */
        if ((qg->lim_flags & (BTRFS_QGROUP_LIMIT_MAX_RFER |
                              BTRFS_QGROUP_LIMIT_MAX_EXCL))) {
-               if (qg->lim_flags & BTRFS_QGROUP_LIMIT_MAX_EXCL)
-                       limit = qg->max_excl;
-               else
-                       limit = qg->max_rfer;
-               threshold = (limit - qg->rsv.values[BTRFS_QGROUP_RSV_DATA] -
-                           qg->rsv.values[BTRFS_QGROUP_RSV_META_PREALLOC]) /
-                           QGROUP_PERTRANS_RATIO;
-               threshold = min_t(u64, threshold, QGROUP_PERTRANS_SIZE);
+               if (qg->lim_flags & BTRFS_QGROUP_LIMIT_MAX_EXCL) {
+                       free = qg->max_excl - qgroup_rsv_total(qg) - qg->excl;
+                       threshold = min_t(u64, qg->max_excl / QGROUP_FREE_RATIO,
+                                         QGROUP_FREE_SIZE);
+               } else {
+                       free = qg->max_rfer - qgroup_rsv_total(qg) - qg->rfer;
+                       threshold = min_t(u64, qg->max_rfer / QGROUP_FREE_RATIO,
+                                         QGROUP_FREE_SIZE);
+               }
 
                /*
                 * Use transaction_kthread to commit transaction, so we no
                 * longer need to bother nested transaction nor lock context.
                 */
 
                /*
                 * Use transaction_kthread to commit transaction, so we no
                 * longer need to bother nested transaction nor lock context.
                 */
-               if (qg->rsv.values[BTRFS_QGROUP_RSV_META_PERTRANS] > threshold)
+               if (free < threshold)
                        btrfs_commit_transaction_locksafe(fs_info);
        }
 
                        btrfs_commit_transaction_locksafe(fs_info);
        }