Quota support

From btrfs Wiki
Jump to: navigation, search

OBSOLETE CONTENT

This wiki has been archived and the content is no longer updated.

Quota support in BTRFS is implemented at a subvolume level by the use of quota groups or qgroup. Since kernel 3.11[1], it is possible to rescan the filesystem so that subvolumes created before quota was enabled gets accounted for.

In the quota system, each subvolume is assigned a quota groups in the form of 0/<subvolume id>. It is possible to create a quota group identified with any number but by default the subvolume ID is used.

Contents

Enabling quota

On a fresh BTRFS filesystem, enabling quota is as simple as typing btrfs quota enable <path>

On an existing BTRFS filesystem first enable the quotas and check if btrfs qgroup show <path> returns anything, if not, your BTRFS version does not create qgroups automatically. To create the qgroups, you must:

  1. Enable the quota system. btrfs quota enable <path>
  2. Create the basic qgroups. btrfs subvolume list <path> | cut -d' ' -f2 | xargs -I{} -n1 btrfs qgroup create 0/{} <path>
  3. Rescan the filesystem. btrfs quota rescan <path>

Using limits

Limits on quota groups can be applied either to the total data usage, un-shared data usage, compressed data usage or both. File copy and file deletion may both affect limits since the unshared limit of another qgroup can change if the original volume's files are deleted and only one copy is remaining.

To set a limit, you use the command btrfs qgroup limit, you can apply it to any qgroup and set a total limit, unshared limit (-e) or compressed limit (-c).

To show usage and limits for a given path within a filesystem use btrfs qgroup show -reF /mountpoint/path1/path2/.

Commands

<at BTRFS root>
mkdir test
cd test
btrfs sub create a
dd if=/dev/urandom of=a/a1 bs=1M count=10
btrfs sub snap a b
dd if=/dev/urandom of=a/a2 bs=1M count=10
dd if=/dev/urandom of=b/b1 bs=1M count=10
btrfs sub snap a c
dd if=/dev/urandom of=a/a3 bs=1M count=10
dd if=/dev/urandom of=c/c1 bs=1M count=10
btrfs sub snap a d
dd if=/dev/urandom of=a/a4 bs=1M count=10
btrfs sub snap a e
rm a/a1
rm a/a4
sync
btrfsQuota.py .. --unit=M

Results

subvol						group         total    unshared
-------------------------------------------------------------------------------
test/a                                  	0/970	     20.05M       0.05M
test/b                                  	0/971	     20.05M      10.05M
test/c                                  	0/972	     30.05M      10.05M
test/d                                  	0/973	     30.05M       0.05M
test/e                                  	0/974	     30.05M      10.05M

Explanations

  • test/a
    • total of 20M because it has 2 10M files, a2 and a3, a1 was deleted.
  • test/b
    • total of 20M: a1 and b1.
    • unshared of 10M because of b1 which only appears on test/b.
  • test/c
    • total of 20M: a1, a2 and c1.
    • unshared of 10M because of c1 which only appears on test/c.
  • test/d
    • total of 30M because the same files as test/a but without a1 deleted, note that a1 is not unshared since it is part of test/{b,c}.
  • test/e
    • total of 30M: a2, a3, a4.
    • unshared of 10M since a4 has been deleted from test/a and is now only appearing on test/e.

Parent qgroups

Each quota group can be grouped into a parent qgroup so to share a common limit. Be sure to take note of what qgroup is assigned so you can remove them afterward if you delete the subvolume.

Commands

<at BTRFS root>
mkdir test
cd test
btrfs sub create a
btrfs sub create b
btrfs qgroup create 1/100 ..
btrfs qgroup assign 0/965 1/100 .. # subvolume a taken from btrfsQuota.py output
btrfs qgroup assign 0/966 1/100 .. # subvolume b
dd if=/dev/urandom of=a/a1 bs=1M count=10
dd if=/dev/urandom of=b/b1 bs=1M count=10
sync
btrfsQuota.py .. --unit=M

Results

subvol						group	total	unshared
------------------------------------------------------------------------
test/a                                  	0/965	10.04M	10.04M
test/b                                  	0/966	10.04M	10.04M
(unknown)                               	1/100	20.07M	20.07M

Known issues

  • To get accurate information, you must issue a sync before using the qgroup show command.
  • The qgroup show command is missing some information, for example you cannot see which subvolume is part of a parent qgroup.
  • Creating a qgroup from an existing directory will show a 0 usage until a full filesystem quota rescan is issued.
  • Using btrfs subvolume delete will break qgroup unshared space usage.
  • After deleting a subvolume, you must manually delete the associated qgroup.
  • Bugs in accounting code might cause false out of space situations.
  • Combining quota with (too many) snapshots of subvolumes can cause performance problems, for example when deleting snapshots.

/usr/local/bin/btrfsQuota.sh

#!/bin/bash

[[ ! -d $1 ]] && { echo Please pass mountpoint as first argument >&2 ;
exit 1 ; }

while read x i x g x x l x p
do
    volName[i]=$p
done < <(btrfs subvolume list $1)

while read g r e
do
    [[ -z $name ]] && echo -e "subvol\tqgroup\ttotal\tunshared"
    group=${g##*/}
    [[ ! -z ${volName[group]} ]] && name=${volName[group]} || name='(unknown)'
    echo $name $g `numfmt --to=iec $r` `numfmt --to=iec $e`
done < <(btrfs qgroup show --raw $1 | tail -n+3) | column -t

/usr/local/bin/btrfsQuota.py

Note only tested on python 2.7

#!/usr/bin/env python2

import argparse
import subprocess

parser = argparse.ArgumentParser(
    description='Gives quotas from a BTRFS filesystem in a readable form'
)
parser.add_argument(
    '--unit', metavar='U', type=str,
    default='G',
    help='SI Unit, [B]ytes, K, M, G, T, P',
)
parser.add_argument(
    'mount_point', metavar='PATH', type=str,
    default='/',
    help='BTRFS mount point',
)
sys_args = parser.parse_args()
mount_point = sys_args.mount_point

multiplicator_lookup = ['B', 'K', 'M', 'G', 'T', 'P']

subvolume_data = dict()
cmd = ["btrfs",  "subvolume", "list", mount_point]
for line in subprocess.check_output(cmd).splitlines():
    args = line.strip().split(' ')
    subvolume_data[int(args[1])] = args[-1]

print("subvol\t\t\t\t\t\tgroup         total    unshared")
print("-" * 79)
cmd = ["btrfs", "qgroup", "show", "--raw", mount_point]
for line in subprocess.check_output(cmd).splitlines():
    args = [x for x in line.strip().split(' ') if len(x)>0]

    try:
        subvolume_id = args[0].split('/')[-1]
        subvolume_name = subvolume_data[int(subvolume_id)]
    except:
        subvolume_name = "(unknown)"

    multiplicator = 1024 ** multiplicator_lookup.index(sys_args.unit)

    try:
        try:
            total = "%02.2f" % (float(args[1]) / multiplicator)
            unshared = "%02.2f" % (float(args[2]) / multiplicator)
        except ValueError:
            continue
        print("%s\t%s\t%s%s %s%s" % (
            subvolume_name.ljust(40),
            args[0],
            total.rjust(10), sys_args.unit,
            unshared.rjust(10), sys_args.unit,
        ))
    except IndexError:
        pass

python3 version, tested on Debian's python3.3 version 3.3.5-1:

#!/usr/bin/env python3

import argparse
import subprocess

parser = argparse.ArgumentParser(
    description='Gives quotas from a BTRFS filesystem in a readable form'
)
parser.add_argument(
    '--unit', metavar='U', type=str,
    default='G',
    help='SI Unit, [B]ytes, K, M, G, T, P',
)
parser.add_argument(
    'mount_point', metavar='PATH', type=str,
    default='/',
    help='BTRFS mount point',
)
sys_args = parser.parse_args()
mount_point = sys_args.mount_point

multiplicator_lookup = ['B', 'K', 'M', 'G', 'T', 'P']

subvolume_data = dict()
cmd = ["btrfs",  "subvolume", "list", mount_point]
for line in subprocess.check_output(cmd).splitlines():
    args = str(line, encoding='utf8').split()
    subvolume_data[int(args[1])] = args[-1]

print("subvol\t\t\t\t\t\tgroup         total    unshared")
print("-" * 79)
cmd = ["btrfs", "qgroup", "show", "--raw", mount_point]
for line in subprocess.check_output(cmd).splitlines():
    args = str(line, encoding='utf8').split()

    try:
        subvolume_id = args[0].split('/')[-1]
        subvolume_name = subvolume_data[int(subvolume_id)]
    except:
        subvolume_name = "(unknown)"

    multiplicator = 1024 ** multiplicator_lookup.index(sys_args.unit)

    try:
        try:
            total = "%02.2f" % (float(args[1]) / multiplicator)
            unshared = "%02.2f" % (float(args[2]) / multiplicator)
        except ValueError:
            continue
        print("%s\t%s\t%s%s %s%s" % (
            subvolume_name.ljust(40),
            args[0],
            total.rjust(10), sys_args.unit,
            unshared.rjust(10), sys_args.unit,
        ))
    except IndexError:
        pass

Documentation

Personal tools