prjquota 的前身其实是 subtreequota,最早由 openvz 提出,2010年之后好像就没有消息了,没有进入内核主干,有点遗憾。后来我们移植过一次,但是由于设计过于复杂,功能不稳定,并且缺少社区的技术支持,最终选择了放弃。
prjquota 是 xfs 文件系统的一个原生特性,其设计简单,功能健壮。并且有人尝试把他移植到了 ext4 文件系统上。4系内核已经进入主干
prjquota 功能和 subtreequota 一样,能够限制一组具有相同 prjid 属性的文件的总大小。这些具有相同属性的 prjid,可能散落在不同的目录下,但属于同一个项目的文件拥有一个想同的project id标示,正如同一个用户的文件,或者同一个用户组的文件有相同的UserID,或者GroupID
具体实现,可以参考内核 patch:https://lwn.net/Articles/671627/
1)使能 prjquota 特性
磁盘project quota初始化,如下任意一种方法都可以:
- 重新格式化一个磁盘来支持project quota: /root/ext4/e2fsprogs/misc/mke2fs /dev/hdb -O quota,project
- 或者在已有的磁盘上使能project quota:/root/ext4/e2fsprogs/misc/tune2fs /dev/hdb -O quota,project
mount设备支持project quota:
- mount -t ext4 -o prjquota /dev/hdb xxxx/ 或者:
- mount -t ext4 /dev/hdb xxxx/; /root/ext4/quota-tools/quotaon xxxx/,但是这个方法,需要在磁盘上没有任何文件被打开的时候才能执行
创建 project id和quota限制管理
- 设置一个目录属于一个project id:/root//ext4/e2fsprogs/misc/chattr -p 1001 xxxx/test1
- 使得这个目录下的文件默认继承这个project id:/root//ext4/e2fsprogs/misc/chattr +P xxxx/test1
- 设置project的配额:/root/ext4/quota-tools/setquota -P 1001 100 100 400 500 xxxx,可以重复设置,例如更新quota,立即生效
2)使用测试
project id的继承:
sh-3.00# touch xxxx/test1/a1 sh-3.00# touch xxxx/test1/a2 sh-3.00# lsattr sh-3.00# /root//ext4/e2fsprogs/misc/lsattr -p xxxx/test1/ 1001 ------------------P xxxx/test1/a1 1001 ------------------P xxxx/test1/a2 sh-3.00#
限额测试
sh-3.00# dd if=/dev/zero of=xxxx/test1/xxx bs=1024 count=101 hdb: write failed, project block limit reached. dd: writing `xxxx/test1/xxx': Disk quota exceeded 97+0 records in 96+0 records out sh-3.00#
观测限额使用情况
sh-3.00# /root/ext4/quota-tools/quota -P 1001 Disk quotas for project #1001 (pid 1001): Filesystem blocks quota limit grace files quota limit grace /dev/hdb 100* 100 100 4 400 500 sh-3.00#
3)性能
prjquota 的原理是在文件写入的时候,检查文件所属的 prjquota 是否已经超限,这个检查是极其轻量的,几乎不会发生性能耗损(除非存在大量文件并发读写试,内核锁竞争的问题)
所以理论上来说,prjquota 性能可以保持跟原生文件系统一致。这个在我们的实际测试结果中,也是比较符合预期的
IO类型\设备类型 | Block size | ext4 & no quota | ext4 & prj quota |
随机读 | 4k | 147 | 148 |
随机读 | 16k | 145 | 146 |
随机读 | 32k | 137 | 138 |
随机读 | 64k | 126 | 125 |
随机读 | 128k | 108 | 106 |
随机读 | 256k | 82 | 83 |
顺序读 | 4k | 6083 | 6267 |
顺序读 | 16k | 2429 | 2484 |
顺序读 | 32k | 1295 | 1271 |
顺序读 | 64k | 599 | 652 |
顺序读 | 128k | 376 | 373 |
顺序读 | 256k | 188 | 185 |
随机写 | 4k | 141 | 148 |
随机写 | 16k | 138 | 143 |
随机写 | 32k | 130 | 135 |
随机写 | 64k | 121 | 168 |
随机写 | 128k | 104 | 110 |
随机写 | 256k | 87 | 122 |
顺序写 | 4k | 10731 | 11518 |
顺序写 | 16k | 2480 | 2549 |
顺序写 | 32k | 1287 | 1329 |
顺序写 | 64k | 682 | 649 |
顺序写 | 128k | 328 | 355 |
顺序写 | 256k | 169 | 175 |
4)一些约定、一些坑
- project id有继承性,如果一个目录没有使能project id的继承,在这个路径下,新建的文件的project id为0,否者将继承目录的project id
- 对于系统调用rename(), 如果oldpath和newpath不属于同一个project id,rename将失败。但是对于使用这个系统调用的程序,比如mv是不会失败的,因为mv调用rename失败后,将通过创建文件,读老的文件内容,写内容到新的文件的方式达到move的效果。在实际落地过程中,我们修改了内核实现,允许了跨 prjquota mv 文件
- 如果目标没有projectid,则rename是可以的
- 如果创建一个文件的硬连接到一个目录下,但这个目录和文件的project id不同,创建将失败
- project id是文件系统域的,不同的文件系统可以有相同的projectid,之间没有关系
- 文件系统相关的工具链需要升级:例如fsck,否则老版本的fsck会破坏project quota的元数据
- 如果不是通过格式化,而是tune2fs方式打开磁盘的prjquota功能,对于磁盘上的一些老的文件系统数据,可能不支持prjquota属性扩展,需要对所有老文件全部 touch 一遍才行,touch一遍的时候,内核会修改文件的扩展属性,增加 prjquota 扩展字段