Recovering from Btrfs Metadata Space Exhaustion
This guide documents a failure I hit on a Btrfs root filesystem: the system started throwing “out of space” errors even though df reported plenty of free space. The actual cause was metadata chunk exhaustion — the filesystem had allocated nearly all of its space dedicated to metadata to half-empty data chunks, leaving no room for metadata to grow.
The fix is not to delete files. It’s to reclaim chunks with btrfs balance so unallocated space is returned to the filesystem.
What it looks like
Section titled “What it looks like”What i was experiencing during this:
ENOSPC/ “No space left on device” errors whiledfstill shows free space.- Applications crash on launch.
- System instability or freezing when disk is under load.
- Deleting a BTRFS snapshot or a few large files temporarily fixes it.
dmesgshows Btrfs warnings about block groups or space cache.
In my case it also triggered a downstream GPU cascade: write latency spiked, the Wayland compositor (KWin) missed frame deadlines, and amdgpu logged a ring timeout / GPU reset, which then crashed the desktop and browser. If you’re seeing GPU resets and storage pressure at the same time, I would advise fixing the storage issue first.
Understanding the failure: chunks, data, and metadata
Section titled “Understanding the failure: chunks, data, and metadata”Btrfs does not split your disk into a fixed “data area” and “metadata area.” Instead it carves the device into chunks (also called block groups) on demand:
- Data chunks hold file contents (usually ~1 GiB each)
- Metadata chunks hold the filesystem trees: directory structure, extent maps, checksums, inline small files (usually ~256 MiB, and doubled under the default
DUPprofile on single-disk setups) - System chunks are tiny and hold chunk-mapping info
once raw device space is allocated to a chunk, that space belongs to that chunk type even if the chunk is nearly empty. If you write a lot of data and later delete it, you can end up with many half-empty data chunks that still “own” the raw space. When all raw space is owned by data chunks, metadata has nowhere to grow, and you get ENOSPC/Out of space errors — even with hundreds gigabytes showing free when running df.
Wrote 400 GiB → 400 GiB of data chunks allocatedDeleted 250 GiB → chunks now half-empty, but still allocatedUnallocated space → ~0Metadata needs to grow → no raw space left → ENOSPCHow to check
Section titled “How to check”The single most useful command for diagnosing a metadata issue is the following.
sudo btrfs filesystem usage /Example output from a wedged system:
Overall: Device size: 500.00GiB Device allocated: 499.00GiB Device unallocated: 1.00GiB ← the number that matters ...Data,single: Size:480.00GiB, Used:300.00GiB ← 180 GiB trapped in half-empty chunksMetadata,DUP: Size: 18.00GiB, Used: 17.60GiB ← nearly full, and can't growSystem,DUP: Size: 64.00MiB, Used: 48.00KiBWhat to read, in order of importance:
Device unallocated— this is the real free space Btrfs can hand to any chunk type. If this is near zero (under ~1 GiB), you are in the danger zone regardless of whatdfsays.- Metadata
UsedvsSize— ifUsedis close toSizeand unallocated is near zero, metadata is wedged. This is the actual ENOSPC trigger. - Data
UsedvsSize— a big gap here (300 used / 480 sized) means lots of reclaimable space is trapped in chunks. That gap is exactly whatbalancerecovers.
Supporting commands:
sudo btrfs filesystem df / # quick per-type used/totalsudo btrfs filesystem usage -T / # tabular view, easier to scansudo btrfs device stats / # corruption / error counters (should be all 0)Recovering: reclaim chunks with balance
Section titled “Recovering: reclaim chunks with balance”In my case the fix was running a filtered balance. The -dusage=N filter tells Btrfs: rewrite only data chunks that are less than N% full, consolidate their contents, and return the freed chunks to unallocated space. Lower N = cheaper, faster, less data moved.
-
Free the empty chunks first (zero cost). This reclaims data chunks that are already 100% empty, moving no data:
Terminal window sudo btrfs balance start -dusage=0 /Re-check after it finishes:
Terminal window sudo btrfs filesystem usage /If
Device unallocatedjumped up, you’ve already bought breathing room. -
Escalate the filter gradually. Each step consolidates progressively fuller chunks. Run them in order, re-checking usage between runs:
Terminal window sudo btrfs balance start -dusage=5 /sudo btrfs balance start -dusage=10 /sudo btrfs balance start -dusage=20 / -
Continue up only as needed. Once
Device unallocatedis healthy (say, several GiB), you can stop. If you want a fuller defrag of chunk usage and you now have free space to work with, you can go higher:Terminal window sudo btrfs balance start -dusage=50 / -
Verify metadata can now grow. Re-run usage and confirm unallocated is up and the data
Size/Usedgap has shrunk:Terminal window sudo btrfs filesystem usage /
:::
You generally do not need -musage here. Metadata exhaustion is a problem of metadata wanting more room, not metadata chunks being fragmented — compacting metadata is rarely the fix and can be counterproductive mid-crisis.
If balance itself fails with ENOSPC (fully wedged)
Section titled “If balance itself fails with ENOSPC (fully wedged)”If Device unallocated is truly at zero, even -dusage=0 may refuse to run because Btrfs has no scratch space at all. The escape hatch is to temporarily add a second device, balance, then remove it.
When the wedged filesystem is your root filesystem, use a real external device — never a loop file living on the same broken filesystem.
# Plug in a USB stick — assume it shows up as /dev/sdXsudo btrfs device add -f /dev/sdX /
# Now there is room to worksudo btrfs balance start -dusage=0 /sudo btrfs balance start -dusage=20 /
# Once unallocated space is recovered, remove the temp devicesudo btrfs device remove /dev/sdX /Only safe if the loop file lives on a different, healthy filesystem than the wedged one.
truncate -s 5G /mnt/other-fs/btrfs-temp.imgsudo losetup -f --show /mnt/other-fs/btrfs-temp.img # prints /dev/loopNsudo btrfs device add -f /dev/loopN /
sudo btrfs balance start -dusage=0 /sudo btrfs balance start -dusage=20 /
sudo btrfs device remove /dev/loopN /sudo losetup -d /dev/loopNrm /mnt/other-fs/btrfs-temp.imgSnapshots: the usual hidden cause
Section titled “Snapshots: the usual hidden cause”If you use Snapper or Timeshift, old snapshots pin extents that would otherwise be freed, so deleted data never actually releases its chunks. List and prune them:
sudo btrfs subvolume list / # see all subvolumes/snapshots# Snapper:sudo snapper listsudo snapper delete <number># Timeshift: prune from the GUI or `timeshift --delete`After deleting snapshots, run a balance again — deletion frees the extents, but the now-empty chunks still need -dusage=0 to return to unallocated.
Prevention
Section titled “Prevention”-
Watch unallocated space, not
df. KeepDevice unallocatedcomfortably above a few GiB. A quick health check:sudo btrfs filesystem usage / | grep -i unallocated. -
Run a light balance periodically (monthly is plenty for a desktop), once you actually have headroom:
Terminal window sudo btrfs balance start -dusage=50 -musage=50 / -
Cap snapshot retention. Avoid hourly snapshots or prune them regularly.
-
Don’t run the disk past ~85% data usage. Fragmentation and allocation pressure both get worse fast above that and can cause metadata exhaustion.
Quick reference
Section titled “Quick reference”# Most useful command for system analysis.sudo btrfs filesystem usage /
# Recover space — start LOW then increment gradually.sudo btrfs balance start -dusage=0 /sudo btrfs balance start -dusage=10 /sudo btrfs balance start -dusage=20 /
# Check for errors (should all be 0)sudo btrfs device stats /
# Integrity check (not a space fix)sudo btrfs scrub start -Bd /Summary
Section titled “Summary”This is not a bug (like i assumed) — it’s how Btrfs allocates space. Raw device space gets locked inside half-empty data chunks, leaving metadata unable to grow, and you get ENOSPC while df still shows free space. The cure is to reclaim chunks with a filtered balance, starting at -dusage=0 and escalating, and to keep an eye on unallocated space going forward. If the filesystem is fully wedged, add a temporary device to create the scratch space balance needs.