ZFS - 为什么压缩文件显示的磁盘使用量几乎是表观大小的两倍?

ZFS - 为什么压缩文件显示的磁盘使用量几乎是表观大小的两倍?

使用 FreeBSD 11.1-STABLE,我有一个配置了 gzip-9 压缩的 ZFS 数据集,以及 8K 的记录大小。 (此卷用于小文件的归档,而不是速度。)

zfs get all pool02/redactedStorage显示了 1.4 倍的压缩比,这比我预期的要差,但那里存储着文本文件和压缩文件的混合,所以并不令人担忧。然后我查看了该数据集中存储的一些大型 zip 文件,感到很困惑。

du -h和的输出du -hA不是我对压缩文件的期望。

例如,我预计 40 MB 的零文件几乎不会占用任何磁盘空间:

# dd if=/dev/zero of=testfile bs=4M count=10
# du -h testfile
512B    testfile
# du -hA testfile
 40M    testfile

但我预计 40 MB 的随机文件会消耗约 40 MB 的磁盘空间,因为它是不可压缩的(出于所有实际目的)。但没想到消耗了将近双倍的空间:

# dd if=/dev/random of=testfile.rnd bs=4M count=10
# du -h testfile.rnd
 92M    testfile.rnd
# du -hA testfile.rnd
 40M    testfile.rnd

通过研究,它看起来像是间接块消耗了额外的空间。

对于testfile(零):

Dataset pool02/redactedStorage [ZPL], ID 56, cr_txg 360697, 958G, 22881480 objects, rootbp DVA[0]=<0:9dd68959000:3000> DVA[1]=<0:14e1475b5000:3000> [L0 DMU objset] fletcher4 uncompressed LE contiguous unique double size=800L/800P birth=25863270L/25863270P fill=22881480 cksum=13497492df:14cc540c2b5f:e089aa02d6109:73afb0d244bcb42

    Object  lvl   iblk   dblk  dsize  lsize   %full  type
  22910497    3   128K     8K      0  40.0M    0.00  ZFS plain file
                                        168   bonus  System attributes
        dnode flags: USED_BYTES USERUSED_ACCOUNTED
        dnode maxblkid: 5119
        path    /testfile
        uid     0
        gid     1004
        atime   Wed Apr 19 00:08:20 2023
        mtime   Wed Apr 19 00:08:20 2023
        ctime   Wed Apr 19 00:08:20 2023
        crtime  Wed Apr 19 00:08:20 2023
        gen     25862395
        mode    100644
        size    41943040
        parent  17938432
        links   1
        pflags  40800000004
Indirect blocks:
    [ No Indirect blocks ]

对于testfile.rnd(随机性):

Dataset pool02/redactedStorage [ZPL], ID 56, cr_txg 360697, 958G, 22881480 objects, rootbp DVA[0]=<0:9dbfec9d000:3000> DVA[1]=<0:14ffe1461000:3000> [L0 DMU objset] fletcher4 uncompressed LE contiguous unique double size=800L/800P birth=25863170L/25863170P fill=22881480 cksum=13b3f2c021:15912a82ff8a:ebef1e0641453:7abda3903292dba

    Object  lvl   iblk   dblk  dsize  lsize   %full  type
  22910499    3   128K     8K  91.9M  40.0M  100.00  ZFS plain file
                                        168   bonus  System attributes
        dnode flags: USED_BYTES USERUSED_ACCOUNTED
        dnode maxblkid: 5119
        path    /testfile.rnd
        uid     0
        gid     1004
        atime   Wed Apr 19 00:16:47 2023
        mtime   Wed Apr 19 00:16:48 2023
        ctime   Wed Apr 19 00:16:48 2023
        crtime  Wed Apr 19 00:16:47 2023
        gen     25862495
        mode    100644
        size    41943040
        parent  17938432
        links   1
        pflags  40800000004
Indirect blocks:
    [ 5120 Indirect blocks redacted ]

那么是不是 5120 个间接块 * 128K = 640M,然后这些块被压缩,导致 51.9M 的开销?

如果是这样,解决这个问题的最佳方法是什么? 创建一个具有更大记录大小的新数据集并将内容移过来?

这是我的数据集参数:

NAME                  PROPERTY              VALUE                      SOURCE
pool02/redactedStorage  type                  filesystem                 -
pool02/redactedStorage  creation              Mon Jan 28  1:03 2019      -
pool02/redactedStorage  used                  958G                       -
pool02/redactedStorage  available             15.1T                      -
pool02/redactedStorage  referenced            958G                       -
pool02/redactedStorage  compressratio         1.40x                      -
pool02/redactedStorage  mounted               yes                        -
pool02/redactedStorage  quota                 none                       local
pool02/redactedStorage  reservation           none                       local
pool02/redactedStorage  recordsize            8K                         local
pool02/redactedStorage  mountpoint            /mnt/pool02/redactedStorage  default
pool02/redactedStorage  sharenfs              off                        default
pool02/redactedStorage  checksum              on                         default
pool02/redactedStorage  compression           gzip-9                     local
pool02/redactedStorage  atime                 on                         default
pool02/redactedStorage  devices               on                         default
pool02/redactedStorage  exec                  on                         default
pool02/redactedStorage  setuid                on                         default
pool02/redactedStorage  readonly              off                        default
pool02/redactedStorage  jailed                off                        default
pool02/redactedStorage  snapdir               hidden                     default
pool02/redactedStorage  aclmode               passthrough                local
pool02/redactedStorage  aclinherit            passthrough                inherited from pool02
pool02/redactedStorage  canmount              on                         default
pool02/redactedStorage  xattr                 off                        temporary
pool02/redactedStorage  copies                1                          default
pool02/redactedStorage  version               5                          -
pool02/redactedStorage  utf8only              off                        -
pool02/redactedStorage  normalization         none                       -
pool02/redactedStorage  casesensitivity       sensitive                  -
pool02/redactedStorage  vscan                 off                        default
pool02/redactedStorage  nbmand                off                        default
pool02/redactedStorage  sharesmb              off                        default
pool02/redactedStorage  refquota              none                       local
pool02/redactedStorage  refreservation        none                       local
pool02/redactedStorage  primarycache          all                        default
pool02/redactedStorage  secondarycache        all                        default
pool02/redactedStorage  usedbysnapshots       0                          -
pool02/redactedStorage  usedbydataset         958G                       -
pool02/redactedStorage  usedbychildren        0                          -
pool02/redactedStorage  usedbyrefreservation  0                          -
pool02/redactedStorage  logbias               latency                    default
pool02/redactedStorage  dedup                 off                        inherited from pool02
pool02/redactedStorage  mlslabel                                         -
pool02/redactedStorage  sync                  standard                   default
pool02/redactedStorage  refcompressratio      1.40x                      -
pool02/redactedStorage  written               958G                       -
pool02/redactedStorage  logicalused           501G                       -
pool02/redactedStorage  logicalreferenced     501G                       -
pool02/redactedStorage  volmode               default                    default
pool02/redactedStorage  filesystem_limit      none                       default
pool02/redactedStorage  snapshot_limit        none                       default
pool02/redactedStorage  filesystem_count      none                       default
pool02/redactedStorage  snapshot_count        none                       default
pool02/redactedStorage  redundant_metadata    all                        default

zdb以及显示相关池的部分输出:

(注意ashift: 12此池的底层 vdev。)

pool02:
    version: 5000
    name: 'pool02'
    state: 0
    txg: 25383030
    pool_guid: 1288056053628670413
    hostid: 3785389258
    hostname: 'redacted'
    com.delphix:has_per_vdev_zaps
    vdev_children: 1
    vdev_tree:
        type: 'root'
        id: 0
        guid: 1288056053628670413
        create_txg: 4
        children[0]:
            type: 'raidz'
            id: 0
            guid: 9072182531784548301
            nparity: 2
            metaslab_array: 49
            metaslab_shift: 37
            ashift: 12
            asize: 23978959699968
            is_log: 0
            create_txg: 4
            com.delphix:vdev_zap_top: 36
            children[0]:
                type: 'disk'
                id: 0
                guid: 17108175667375824896
                path: '/dev/gptid/e07bacd6-1224-11e9-98bd-90b11c29519f'
                whole_disk: 1
                DTL: 293
                create_txg: 4
                com.delphix:vdev_zap_leaf: 37
            children[1]:
                type: 'disk'
                id: 1
                guid: 6726950469173540573
                path: '/dev/gptid/e443f9f2-1224-11e9-98bd-90b11c29519f'
                whole_disk: 1
                DTL: 292
                create_txg: 4
                com.delphix:vdev_zap_leaf: 38
--------==== 10 ADDITIONAL PHY DISKS REDACTED ====---------            
    features_for_read:
        com.delphix:hole_birth
        com.delphix:embedded_data

答案1

在由 12 个具有 4K 扇区(共 12 个)的磁盘组成的 vdev 上条带化 8K 记录ashift是一个糟糕的主意,并且会导致大量的开销:

来自 OpenZFS:

https://openzfs.github.io/openzfs-docs/基本概念/RAIDZ.html

由于这些输入,如果记录大小小于或等于扇区大小,则 RAIDZ 的奇偶校验大小将有效地等于具有相同冗余的镜像。例如,对于 3 个磁盘的 raidz1,ashift=12 且recordsize=4K,我们将在磁盘上分配:

  • 1个4K数据块

  • 1 个 4K 填充块

可用空间率为50%,与双镜相同。

另一个示例,对于 3 个磁盘的 raidz1,ashift=12 且记录大小=128K:

  • 总条纹宽度为 3

  • 由于有 1 个奇偶校验块,一个条带最多可以有 2 个 4K 大小的数据部分

  • 我们将有 128K/2 = 64 个条带,每个条带有 8K 数据和 4K 奇偶校验

因此本例中的可用空间率为 66%。

RAIDZ的磁盘越多,条带越宽,空间效率就越高。

该文本后面是一个图表,如果截屏并内嵌在此处,该图表将难以辨认,但它显示,对于 1 倍或 2 倍扇区大小的记录大小,在 RAIDZ2 下,开销将为 67%。

根据图表,这种情况下的解决方案是增加到recordsize256K,这在 12 磁盘 RAIDZ2 vdev 上的奇偶校验+填充成本为 18%。 (recordsize相比之下,128K会产生 24% 的开销)。

但事情没那么简单。对于“经典”文件系统来说,最初选择 8Krecordsize可能是正确的,就像recordsize最大限度块大小,不是固定的块大小。然而,对于较大recordsize且相对较小的文件仍然存在惩罚。

增加recordsize只会影响更改后创建的数据,但在这种情况下,池仅消耗 6% 的空间,当前压缩率为 1.4 倍。现有数据可以保留在原位,而不会造成任何长期容量问题。然而,在需要回收开销的情况下:

https://openzfs.github.io/openzfs-docs/性能和调优/工作负载调优.html

如果您更改记录大小,因为您的应用程序应该使用不同的记录大小来执行更好的操作,则您将需要重新创建其文件。每个文件上的 cp 后跟 mv 就足够了。或者,当完整接收完成时,send/recv 应重新创建具有正确记录大小的文件。

来自对相关池的真实实验:

# zfs set recordsize=256K pool02/redactedStorage

# dd if=/dev/zero of=testfile256.40M.zeroes bs=1M count=40
# du -h testfile256.40M.zeroes
512B    testfile256.40M.zeroes

# dd if=/dev/random of=testfile256.40M.rnd bs=1M count=40
# du -h testfile256.40M.rnd
 40M    testfile256.40M.rnd

# dd if=/dev/random of=testfile256.8K.rnd bs=8192 count=1
# du -h testfile256.8K.rnd
 37K    testfile256.8K.rnd

如您所见,40M 文件正在使用逻辑空间量。但是一个8K的文件就消耗了37K的空间!

因此recordsize应该根据数据集的内容进行调整。

当然,看起来128K默认值recordsize是最佳的,我只是不应该碰它。

# zfs set recordsize=128K pool02/redactedStorage
# cp testfile256.40M.rnd testfile128.40M.rnd
# du -h testfile128.40M.rnd
512B    testfile128.40M.rnd
# mv testfile128.40M.rnd testfile128.40M.rnd2
# du -h testfile128.40M.rnd2
 40M    testfile128.40M.rnd2

# cp testfile256.8K.rnd testfile128.8K.rnd
# mv testfile128.8K.rnd testfile128.8K.rnd2
# du -h testfile128.8K.rnd2
 19K    testfile128.8K.rnd2

这确实显示 8K 测试文件使用 19K 磁盘空间,但是存在必要的元数据开销。看着不可压缩现有文件大小 <=8K,所有文件在原始文件下recordsize=8K也显示 19K 的磁盘使用量。我进一步尝试recordsize=64K,它对这些示例文件的大小没有影响。

另请注意,cp后面的mv确实是在 new 下创建文件实例所必需的recordsize

这篇文章还对正在发生的事情进行了很好的描述,我将把这些描述供后代使用:

https://klarasystems.com/articles/choosing-the-right-zfs-pool-layout/

  1. 填充、磁盘扇区大小和记录大小设置:在 RAID-Z 中,奇偶校验信息与每个块相关联,而不是像 RAID-5 中那样与特定条带相关联,因此每个数据分配必须是 p+1(奇偶校验+1)的倍数,以避免释放的段太小被重复使用。如果分配的数据不是 p+1 的倍数,则使用“填充”,这就是 RAID-Z 需要比 RAID-5 多一点的奇偶校验和填充空间的原因。这是一个复杂的问题,但简而言之:为了避免空间效率低下,您必须保持 ZFS 记录大小远大于磁盘扇区大小;您可以对 512 字节扇区磁盘使用 Recordsize=4K 或 8K,但如果您使用 4K 扇区磁盘,则 Recordsize 应是该值的几倍(默认 128K 即可),否则最终可能会丢失太多空间。

相关内容