Btrfs have been available in Fedora for quite some time and starting from Fedora 33, new installations of Workstation edition use it by default. Btrfs is pretty capable file system with lots of options, let’s take a look on one aspect: transparent per-file compression.

There’s little bit of misunderstanding how this works and some people recommend to mount with compress option. This is actually not necessary and I would actually strongly suggest NOT to use this option. See, this option makes btrfs to attempt to compress all files that are being written. If the beginning of a file cannot be effectively compressed, it’s marked as “not for compression” and this is never attempted again. This can be even forced via a different option. This looks nice on paper.

The problem is, not all files are good candidates for compression. Compression takes time and it can dramatically worsen performance, things like database files or virtual machine images should never be compressed. Performance of libvirt/KVM goes terribly down by order of magnitude if an inefficient backing store is used (qcow2).

I suggest to keep the default mount options Anaconda installer deploys, note there is none related to compression. Instead, use per-file (per-directory) feature of btrfs to mark files and directories to be compressed. A great candidate is /usr which contains most of the system including binaries or documentation.

One mount option is actually useful which Anaconda does not set by default and that is noatime. Writing access times for copy on write file system can be very inefficient. Note this option implies nodiratime so it’s not necessary to set both.

To enable compression for /usr simply mark this directory to be compressed. There are several compression algorithms available: zlib (slowest, best ratio), zstd (decent ratio, good performance) and lzo (best performance, worse ratio). I suggest to stick with zstd which lies in the middle. There are also compression level options, unfortunately the utility does not allow setting those at the time of writing. Luckily, the default level (3) is reasolable.

# btrfs property set /usr compression zstd

Now, btrfs does not immediately start compressing contents of the directory. Instead, everytime a file is written data in those blocks is compressed. To explicitly compress all files recursively, do this:

# btrfs filesystem defragment -r -v -czstd /usr

Let’s find out how much space have we saved:

# compsize /usr
Processed 55341 files, 38902 regular extents (40421 refs), 26932 inline.
Type       Perc     Disk Usage   Uncompressed Referenced  
TOTAL       50%      1.1G         2.2G         2.3G       
none       100%      337M         337M         338M       
zstd        42%      844M         1.9G         1.9G  

Exactly half of space is saved on a standard installation of Fedora 33 Server when /usr is compressed using zstd algorithm with the default level. Note some files are not compressed, these are too small files when it does not make any sense (a block would be used anyway). Not bad.

To disable compression perform the following command:

# btrfs property set /usr compression ""

Unfortunately, at the time of writing it is not possible to force decompression of a directory (or files), there is no defragment command to do this. If you really need to do this, create a script which reads and writes all files but be careful.

Keep in mind the btrfs property command will force all files to be compressed, even if they do not compress well. This will work pretty well for /usr just make sure there is no 3rd party software installed there writing files. There is also a way to mark files for compression and if they don’t compress well btrfs could give up on it. You can do that by setting chattr +c on files or directories. Unfortunately, you can’t set compression algorithm that way - btrfs will default to slower zlib.

Remember: Do not compress everything, specifically directory /var should definitely not be compressed. If you happened to accidentally mark files within /var to be compressed, you can fix this with:

# find /var -exec btrfs property set {} compression "" \;

Again, this will only mark them not to be compressed, it’s currently not possible to explicitly decompress them. Use the compsize utility to find out how much of data is still compressed.

That’s all for today, I will probably sharing some more btrfs posts.