Btrfs deep dive: subvolumes, snapshots, RAID & compression
Btrfs is a B-tree based copy-on-write (CoW) filesystem that has been in the Linux kernel since 2009. It is the default filesystem on Fedora (since 33) and SUSE Linux Enterprise (since 15). Unlike traditional journaling filesystems, Btrfs never overwrites data in place, every modification writes to a new location and atomically updates metadata pointers.
Architecture
Btrfs is built on five main B-trees:
| Tree | Purpose |
|---|---|
| Chunk tree | Maps logical addresses to physical block locations across devices. Stores device information (DEV_ITEM) and chunk mappings (CHUNK_ITEM). |
| Tree of roots | Points to all other trees, including subvolume trees. Tracks the default subvolume and subvolume deletion progress. |
| Extent tree | Records which byte ranges are in use, reference counts, and back-references to the file or tree using each extent. |
| Checksum tree | Stores detached checksums for each data block. Metadata blocks have inline checksums in the B-tree node header. |
| Device allocation tree | Records which physical extents on each device have been allocated into chunks. |
Why CoW matters
- Crash consistency: either the old data or the new data is present after a crash, never a partially updated block
- Snapshots: unchanged blocks are shared between source and snapshot, making snapshots instant and space-efficient
- Checksums: every block is checksummed; corruption is detected on read and self-healed when redundancy exists
Checksum algorithms
| Algorithm | Since kernel | Notes |
|---|---|---|
crc32c | Always | Default, backward compatible |
xxhash64 | 5.5 | Faster than crc32c |
sha256 | 5.5 | Stronger, slower |
blake2b | 5.5 | Strong and fast |
Subvolumes
A subvolume is an independently-rooted namespace within a Btrfs filesystem. It looks like a directory but has its own inode tree and can be snapshotted, mounted independently, and assigned quota limits.
# Create a subvolume
btrfs subvolume create /mnt/data
# List subvolumes
btrfs subvolume list /mnt
# Show subvolume info
btrfs subvolume show /mnt/data
# Delete a subvolume
btrfs subvolume delete /mnt/dataMounting subvolumes
# By name
mount -o subvol=@ /dev/sdb1 /
mount -o subvol=@home /dev/sdb1 /home
# By ID
mount -o subvolid=256 /dev/sdb1 /mnt
# Get the subvolume ID
btrfs subvolume list /mnt
btrfs inspect-internal rootid /mnt/dataThe @ naming convention
The @ prefix is a convention (not a kernel requirement) used by Fedora, openSUSE, and Ubuntu to mark subvolumes in directory listings. A typical layout:
@ → mounted at /
@home → mounted at /home
@var → mounted at /var (excluded from root snapshots)Example /etc/fstab entries:
UUID=xxxx / btrfs subvol=@,defaults 0 0
UUID=xxxx /home btrfs subvol=@home,defaults 0 0Default subvolume
# Get current default
btrfs subvolume get-default /mnt
# Set default (by path or ID)
btrfs subvolume set-default /mnt/@ /mntThe top-level subvolume always has ID 5 and cannot be removed.
Snapshots
A snapshot is a subvolume whose initial content is shared with another subvolume. When either the snapshot or the source is modified, CoW ensures the changes are private.
# Read-write snapshot
btrfs subvolume snapshot /mnt/data /mnt/data_snap
# Read-only snapshot (for send/receive backups)
btrfs subvolume snapshot -r /mnt/data /mnt/data_snapA snapshot of a subvolume containing nested subvolumes will contain empty stubs for the nested subvolumes, not their contents. You must snapshot each nested subvolume separately.
If the underlying disk is damaged, both the snapshot and the source are damaged. Use btrfs send to replicate snapshots to separate storage for a real backup.
Send / receive
# Full send to a backup filesystem
btrfs send /.snapshots/home-day1 | btrfs receive /backup
# Incremental send (parent must exist on receiver)
btrfs send -p /.snapshots/home-day1 /.snapshots/home-day2 \
| btrfs receive /backup
# Stream over SSH to a remote host
btrfs send /.snapshots/home-day1 \
| ssh backuphost "btrfs receive /backup"All snapshots involved in a send must be read-only. Each command in the stream is CRC32C-checksummed.
Compression
| Algorithm | Since kernel | Levels | Notes |
|---|---|---|---|
zlib | Always | 1-9 (default 3) | Balanced speed and ratio |
lzo | 3.1 | None | Fast, lower ratio |
zstd | 4.14 | 1-15 (default 3) | Best modern choice, high ratio, fast |
# Mount with compression
mount -o compress=zstd /dev/sdb1 /mnt
mount -o compress=zstd:3 /dev/sdb1 /mnt
# Force compression (bypass heuristics)
mount -o compress-force=zstd /dev/sdb1 /mnt
# Per-file compression attribute
chattr +c file # legacy, sets zlib
btrfs property set file compression zstd
# Compress existing files via defrag
btrfs filesystem defrag -czstd /mnt/largefile
btrfs filesystem defrag -r -czstd /mnt/dataBtrfs uses heuristics (frequency sampling, repeated pattern detection, Shannon entropy) to skip incompressible files. compress-force bypasses this at the cost of wasted CPU on data that won't compress.
Defragmenting a file breaks its shared extents with snapshots and reflink copies, causing space usage to increase. Avoid defrag on subvolumes with snapshots.
CoW and NOCOW
For workloads with heavy random writes (databases, VM disk images), CoW causes severe fragmentation. Disable CoW on a per-file or per-directory basis:
# Set NOCOW on a directory BEFORE creating files in it
chattr +C /mnt/VMs
# Files created in a NOCOW directory inherit the attributechattr +C disables checksums (nodatasum) and compression. Btrfs cannot detect silent corruption on NOCOW files, and on mirrored profiles it cannot determine which copy is good. Put databases and VM images in a separate NOCOW subvolume that is excluded from snapshots.
Checksums and self-healing (scrub)
# Start scrub (runs in background)
btrfs scrub start /mnt
# Run in foreground
btrfs scrub start -B /mnt
# Check scrub status
btrfs scrub status /mnt
# Cancel
btrfs scrub cancel /mnt
# Resume
btrfs scrub resume /mntScrub walks all data and metadata, verifies checksums, and automatically repairs corrupted blocks by copying from a good replica (requires RAID1, RAID1C3, RAID1C4, or DUP profiles). Check per-device error counters:
btrfs device stats /mnt
btrfs device stats -z /mnt # reset countersRAID profiles
| Profile | Copies | Space efficiency | Min devices | Protects against |
|---|---|---|---|---|
single | 1 | 100% | 1 | Nothing |
DUP | 2 (same device) | 50% | 1 | Bit errors (not disk failure) |
RAID1 | 2 | 50% | 2 | 1 disk failure |
RAID1C3 | 3 | 33% | 3 | 2 disk failures |
RAID1C4 | 4 | 25% | 4 | 3 disk failures |
RAID0 | 1 | 100% | 2 | Nothing |
RAID10 | 2 | 50% | 4 | 1 disk per mirror pair |
RAID5 | 1+parity | (N−1)/N | 2 | 1 disk, not production-ready, see below |
RAID6 | 1+2parity | (N−2)/N | 3 | 2 disks, not production-ready, see below |
Setting profiles
# At mkfs time
mkfs.btrfs -d raid1 -m raid1 /dev/sdb /dev/sdc
# Change profiles via balance
btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt
# Convert to single (for device removal)
btrfs balance start -f -dconvert=single -mconvert=dup /mntRAID 5/6 status: not recommended for production
Btrfs RAID5/6 has historically suffered from the write hole: parity and data writes are not atomic, and Btrfs has no battery-backed cache or CoW transaction safety for parity stripes. Kernel 6.5+ introduced comprehensive RAID56 read-modify-write checksum verification that significantly mitigates the write hole, but the implementation is still not considered production-ready and the stripe-tree rewrite remains incomplete. Use RAID1C3 or RAID1C4 for higher redundancy, or use ZFS RAIDZ if parity RAID is required.
Balance
Balance relocates block groups to match profile constraints, reclaim space, or compact the filesystem. Always use filters, a full balance on a large filesystem can take days.
# Convert data to RAID1
btrfs balance start -dconvert=raid1 /mnt
# Only balance chunks that are >70% used (reclaims space)
btrfs balance start -dusage=70 -musage=70 /mnt
# Pause / resume / cancel
btrfs balance pause /mnt
btrfs balance resume /mnt
btrfs balance cancel /mnt
# Check status
btrfs balance status /mntDevice management
# Add a device
btrfs device add /dev/sdc1 /mnt
# Remove a device (data is relocated off it first)
btrfs device remove /dev/sdb1 /mnt
# Remove a missing/failed device
btrfs device remove missing /mnt
# Replace a failing drive online
btrfs replace start /dev/sdb1 /dev/sdc1 /mnt
btrfs replace status /mnt
btrfs replace cancel /mnt
# Show filesystem and device info
btrfs filesystem show /mnt
btrfs filesystem usage /mnt
btrfs filesystem df /mntQuota groups (qgroups)
# Enable quotas
btrfs quota enable /mnt
# Set a limit on a subvolume (by qgroup 0/<subvolid>)
btrfs qgroup limit 100G 0/256 /mnt
# Show qgroup usage
btrfs qgroup show /mnt
# Disable quotas
btrfs quota disable /mntQgroups track rfer (referenced, total space including shared) and excl (exclusive, space that would be freed if the qgroup were deleted). Qgroups have a performance cost on all extent processing; enable only if you need them.
Btrfs swapfiles
Swapfiles on Btrfs require NOCOW (which implies no checksums, no compression, no snapshots of the containing subvolume) and must be fully preallocated with no holes.
# Modern method (btrfs-progs 6.1+)
btrfs filesystem mkswapfile --size 4G /swap/swapfile
swapon /swap/swapfile
# Get resume offset for hibernation
btrfs inspect-internal map-swapfile /swap/swapfileDo not use fallocate, it can create holes incompatible with swap. The btrfs filesystem mkswapfile command handles NOCOW, preallocation, and mkswap correctly in one step.
Key mount options
| Option | Effect |
|---|---|
compress=zstd | Enable zstd compression |
compress-force=zstd | Force compression on all files |
noatime | Don't update access times (performance) |
space_cache=v2 | Use free space tree (default since 4.5) |
discard=async | Asynchronous TRIM (default since 6.2 when supported) |
subvol=@ | Mount a specific subvolume |
skip_balance | Don't auto-resume an interrupted balance on mount |
autodefrag | Auto-defrag files with random writes |
Common pitfalls
ENOSPC despite free space
Btrfs allocates space in chunks. If all data chunks are full but metadata chunks have space (or vice versa), writes fail with ENOSPC even though df shows free space. Run btrfs balance start -dusage=70 to reclaim, or add a device.
Balance hanging
A full balance without filters on a large or nearly-full filesystem can hang for days or hit ENOSPC mid-operation. Always use -dusage / -musage filters, run during maintenance windows, and mount with skip_balance to prevent auto-resume of an interrupted balance.
Snapshot accumulation
Many snapshots accumulate metadata overhead even with shared data extents. Implement cleanup policies (snapper, timeshift) and monitor with btrfs qgroup show.
btrfs-progs
The userspace toolset (btrfs-progs) provides the btrfs command. Current stable as of 2026 is v7.1. Check your version:
btrfs version
# btrfs-progs v7.1