Btrfs/Compression

From Forza's ramblings

Compression

Picture of a an old air compressor
This Atlas compressor provided compressed air to drilling equipment and the compressed air train which were used in the mine Gröndalsgruvan in the Klackberg mining fields.

Compression can be useful for various different reasons. It reduces the amount of data written which reduces the wear on flash based storage such as SSD and NVME. Compression using LZO and Zstd is very fast and can improve read/write speeds on HDDs at very little CPU cost.

Btrfs currently supports three types of data compression methods. zlib, lzo and zstd. For legacy reasons, the default compression mode is zlib. Zstd was introduced in the Linux kernel v4.14 (Nov 2017) and generally offers better versatility.

Algorithm Compression levels Default Description
zlib 1-9 3 slow, good compression ratios, default method
LZO N/A N/A very fast, low compression ratios
zstd 1-15 3 slow to very fast, good compression ratios at all levels.
Setting compression level is available since Linux Kernel 5.1.

The compression speeds with zstd and lzo are generally real-time. This means that they are faster than the storage medium can read or write. This can increase the overall read/write throughput as well as increasing the life span of flash based storage such as NVME and SSD.

Take a look at Btrfs/Zstd benchmarks to see examples on how the compression level affects performance.

Enable Compression

Compression can be enabled with mount -o compress, with chattr +c[1] (only zlib) or with btrfs property set[2].

Btrfs contains an internal heuristics that determines if some data is compressible so that it doesn't try to compress data that isn't compressible as this wastes CPU time. The compress-force mount option bypasses this heuristics in order to gain better compression ratios. A downside is that this increases fragmentation with non-compressible files.

Compression levels can currently only be set using mount[3] options

To see if a file has a compression flag set you can use lsattr[4] or getfattr[5]. Files compressed through the compress mount option do not get the compression flag set.

# lsattr
 --------c----------- ./myfile.db
# getfattr -n btrfs.compression myfile.db
# file: myfile.db
btrfs.compression="zstd"

zlib

Zlib is the default method used unless specified otherwise.

Enable zlib with one of these options:

# chattr +c <filename>
# btrfs property set <filename> compression zlib 
# setfattr -n btrfs.compression -v zlib <filename>
# mount -o compress=zlib
# mount -o compress=zlib:5
# mount -o compress-force=zlib
# mount -o compress-force=zlib:5

LZO

LZO is extremely fast but offers worse compression ratios that zlib and zstd. It is available since the beginning of Btrfs.

Enable LZO with one of these options:

# btrfs property set <filename> compression lzo
# setfattr -n btrfs.compression -v lzo <filename>
# mount -o compress=lzo
# mount -o compress-force=lzo

Zstandard - Zstd

Zstd is the newest compression algorithm in Btrfs. It is available since Linux Kernel 4.14. It offers good compression ratios with very high speeds. By choosing an appropriate compression level you can tailor performance to your workloads.

Enable Zstd with one these options:

# btrfs property set <filename> compression zstd
# setfattr -n btrfs.compression -v zstd <filename>
# mount -o compress=zstd
# mount -o compress=zstd:5
# mount -o compress-force=zstd
# mount -o compress-force=zstd:5

Disable or prevent Compression

Disabling compression can be done in the same way as enabling compression. Even after disabling compression, already compressed data will remain compressed until its contents are rewritten. btrfs filesystem defrag (without -c) can decompress data if the filesystem is mounted without the compress or compress-force options.

If you use the mount -o compress mount option you can use mount -o remount,nocompress to disable compression.

There are three ways to remove the compression attribute on individual files and directories:

# btrfs property set <filename> compression ""
# setfattr -n btrfs.compression -v "" <filename>
# chattr -c <filename>

Since Linux kernel 5.14 it is possible to prevent compression on a file using chattr +m. Chattr from e2fsprogs-1.46.2 and newer has support for +m.

# lsattr
 -------------------m ./myfile.db

Using defrag to compress files

Enabling compression does not re-compress existing files. Instead you have to use btrfs filesystem defrag to re-compress them. There is more information on defragmenting files at Btrfs/Defrag.

Use the -c<algo> to select zstd, zlib or lzo compression.

/media/vm/libvirt/images # btrfs fi defrag -v -czstd qBit_root.img
qBit_root.img

We see that the disk image was compressed from 4.1 to 3.3GiB.

/media/vm/libvirt/images # compsize  qBit_root.img
Processed 1 file, 9460 regular extents (9471 refs), 0 inline.
Type       Perc     Disk Usage   Uncompressed Referenced  
TOTAL       81%      3.3G         4.1G         4.1G       
none       100%      3.0G         3.0G         3.0G       
zstd        29%      317M         1.0G         1.0G     

If the filesystem is mounted with -o compress or -o compress-force, defrag will inherit compression mount option and compress all files, even without the -c<algo> option. Defrag will also inherit any compression levels from the mount options.

# mount /mnt/btrfs -o remount,compress-force=zstd:15
# btrfs fi defrag -v -r /mnt/btrfs/backups/
# mount /mnt/btrfs -o remount,compress=zstd:2

Benchmark

Best way to know what compression levels suit your needs is to simply try out the different compression levels and options with your intended workload.

The following benchmark tests the zstd compression using the zstd tool, not the btrfs internal compression, so it is not truly representing the internal filesystem compression. However, it can give an insight into how well your CPU handles different compression levels. There are many factors affecting read/write speeds and a quick benchmark does not give all answers.

A quick one-line benchmark for zstd would otherwise be:

# for i in {1..15}; do  zstd -b$i -i3 <myfile>; done

This will benchmark zstd and iterate through all levels between 1 and 15.

See Btrfs/Zstd for more in depth results.


  1. chattr man page[1]
  2. btrfs-property man page[2]
  3. btrfs-mount man page[3]
  4. lsattr man page[4]
  5. getfattr man page[5]