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

s.c*_*.tt 1 freebsd zfs compression

使用 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
Run Code Online (Sandbox Code Playgroud)

但我预计 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
Run Code Online (Sandbox Code Playgroud)

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

对于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 ]
Run Code Online (Sandbox Code Playgroud)

对于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 ]
Run Code Online (Sandbox Code Playgroud)

那么是不是 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

s.c*_*.tt 5

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

\n

来自 OpenZFS:

\n

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

\n
\n

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

\n
    \n
  • 1个4K数据块

    \n
  • \n
  • 1 个 4K 填充块

    \n
  • \n
\n

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

\n

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

\n
    \n
  • 总条纹宽度为 3

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

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

    \n
  • \n
\n

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

\n

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

\n
\n

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

\n

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

\n

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

\n

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

\n

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

\n
\n

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

\n
\n

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

\n
# zfs set recordsize=256K pool02/redactedStorage\n\n# dd if=/dev/zero of=testfile256.40M.zeroes bs=1M count=40\n# du -h testfile256.40M.zeroes\n512B    testfile256.40M.zeroes\n\n# dd if=/dev/random of=testfile256.40M.rnd bs=1M count=40\n# du -h testfile256.40M.rnd\n 40M    testfile256.40M.rnd\n\n# dd if=/dev/random of=testfile256.8K.rnd bs=8192 count=1\n# du -h testfile256.8K.rnd\n 37K    testfile256.8K.rnd\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n
# zfs set recordsize=128K pool02/redactedStorage\n# cp testfile256.40M.rnd testfile128.40M.rnd\n# du -h testfile128.40M.rnd\n512B    testfile128.40M.rnd\n# mv testfile128.40M.rnd testfile128.40M.rnd2\n# du -h testfile128.40M.rnd2\n 40M    testfile128.40M.rnd2\n\n# cp testfile256.8K.rnd testfile128.8K.rnd\n# mv testfile128.8K.rnd testfile128.8K.rnd2\n# du -h testfile128.8K.rnd2\n 19K    testfile128.8K.rnd2\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n

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

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