LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

LVM and Linux Filesystem Comparison

linuxlvmfilesystemext4btrfszfsstoragesysadmin

Storage on Linux is a deep subject that rewards careful study. The raw partition model that new users learn first — carve up a disk with fdisk, format with mkfs, mount it — works fine for a single machine with a single disk and a static workload. The moment your requirements become even slightly dynamic, that model starts working against you. You need more space on /var/lib/docker but the partition is at 95% and the adjacent one is nearly empty. You need a consistent backup of a live database. You want to provision fifty virtual machines without pre-allocating fifty separate disk images. Partitions, as a concept, cannot help you here.

LVM solves these problems at the block layer, below the filesystem. The filesystems — ext4, XFS, Btrfs, ZFS — solve complementary problems at the layer above: checksums, snapshots, compression, RAID, and varying levels of operational maturity. Understanding both layers, and the interaction between them, is fundamental to operating Linux storage infrastructure seriously.

This post works through LVM from first principles, covers all four major filesystems in depth, and ends with a decision guide that cuts through the hype in both directions.


Why LVM exists

A traditional partition is a contiguous range of sectors on a specific physical disk. Its size is fixed at creation time. If you need more space, you either delete the partition and recreate it (losing data), extend it if there happens to be unallocated space immediately after it on the same disk (fragile and unlikely in practice), or add a second disk and use a symlink or bind mount to overflow into it (architectural debt that compounds over time).

LVM introduces a virtualization layer between physical storage and the filesystems that consume it. Physical disks or partitions are registered as Physical Volumes (PVs). PVs are pooled into a Volume Group (VG). The VG is the flexible allocation pool from which Logical Volumes (LVs) are carved. LVs are what filesystems and applications actually see — they look like block devices (/dev/datavg/data), and everything above that layer is oblivious to the LVM machinery underneath.

The immediate practical consequences: an LV can span multiple physical disks transparently. You can extend an LV online while a filesystem is mounted and actively in use, without a maintenance window. You can take a point-in-time snapshot of a live LV for consistent backups. You can thin-provision a pool and overcommit physical storage against it. None of this requires reformatting, repartitioning, or taking the system down.


LVM concepts and setup

The three-layer hierarchy is worth internalising before touching any commands.

  Physical Disks
  +------------+    +------------+    +------------+
  |  /dev/sdb  |    |  /dev/sdc  |    |  /dev/sdd  |
  +------------+    +------------+    +------------+
        |                 |                 |
        v                 v                 v
  Physical Volumes (PVs) -- labelled and managed by LVM
  +------------+    +------------+    +------------+
  |  PV sdb    |    |  PV sdc    |    |  PV sdd    |
  |  200GB     |    |  200GB     |    |  200GB     |
  +------------+    +------------+    +------------+
        |_________________|_________________|
                          |
                          v
  Volume Group (VG) -- single unified pool
  +-----------------------------------------------+
  |              datavg   (600GB)                 |
  +-----------------------------------------------+
        |                 |                 |
        v                 v                 v
  Logical Volumes (LVs) -- what filesystems mount
  +------------+    +------------+    +------------+
  |  lv_home   |    |  lv_data   |    |  lv_logs   |
  |  100GB     |    |  300GB     |    |  50GB      |
  +------------+    +------------+    +------------+
        |                 |                 |
        v                 v                 v
  Filesystems / Block Device Users
  +------------+    +------------+    +------------+
  |   ext4     |    |    XFS     |    |    ext4    |
  +------------+    +------------+    +------------+

Initializing Physical Volumes

Any block device — a raw disk, a disk partition, even a loop device — can become a PV:

1
pvcreate /dev/sdb /dev/sdc

pvcreate writes an LVM metadata header to the beginning of each device. You can inspect what it wrote:

1
pvdisplay /dev/sdb

The output shows the PV UUID, total size, and the PE (Physical Extent) size — the fundamental allocation unit. The default PE size is 4 MB. LVM allocates storage in whole PEs, so an LV’s size is always a multiple of 4 MB. You can set a larger PE size at VG creation time (vgcreate -s 32M), which reduces metadata overhead on very large VGs, but 4 MB is the right answer for almost every workload.

Creating and populating a Volume Group

1
2
vgcreate datavg /dev/sdb /dev/sdc
vgdisplay datavg

vgdisplay shows total PE count, free PEs, the VG UUID, and the PE size. The VG is now a pool of PEs spread across both disks. LVM does not yet know or care how those PEs are distributed — it tracks individual PE allocations per LV.

Creating Logical Volumes

1
2
lvcreate -L 50G -n data datavg
lvcreate -L 20G -n logs datavg

The resulting devices appear at /dev/datavg/data and /dev/datavg/logs (symlinks to /dev/mapper/datavg-data and /dev/mapper/datavg-logs). Format and mount them as you would any block device:

1
2
mkfs.xfs /dev/datavg/data
mount /dev/datavg/data /mnt/data

lsblk shows the full device tree, making it easy to see which LVs are active and which filesystems are mounted where:

1
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT

Striping across PVs

For performance-sensitive workloads where an LV spans multiple PVs, you can instruct LVM to stripe writes across them:

1
lvcreate -L 100G -n fastdata --stripes 2 --stripesize 64K datavg

This distributes I/O across both PVs in 64 KB stripes, roughly doubling sequential throughput. The tradeoff is that striped LVs are less resilient — losing either PV loses the entire LV. For redundancy, use LVM mirroring or a filesystem-level RAID layer (or RAID under LVM via mdadm).


Resizing Logical Volumes online

This is one of LVM’s most operationally useful features. You can extend a mounted, active LV without unmounting it or interrupting the application.

Extending an LV

1
lvextend -L +20G /dev/datavg/data

This extends the block device. The filesystem does not yet know the device grew — it must be told separately. For ext4:

1
resize2fs /dev/datavg/data

For XFS (which can only grow online, never shrink):

1
xfs_growfs /mnt/data

Note that xfs_growfs takes a mount point, not a device path.

For Btrfs:

1
btrfs filesystem resize max /mnt/data

The -r flag on lvextend combines both steps into one command — it extends the LV and then calls the appropriate filesystem resize tool automatically:

1
lvextend -r -L +20G /dev/datavg/data

-r detects the filesystem type and calls resize2fs, xfs_growfs, or btrfs filesystem resize as appropriate. Convenient for scripted provisioning.

Shrinking an LV

Shrinking is more delicate and only supported by ext4 and Btrfs. XFS cannot shrink — full stop. The correct procedure for ext4:

  1. Unmount the filesystem.
  2. Run e2fsck -f /dev/datavg/data to ensure the filesystem is clean. Never skip this.
  3. Shrink the filesystem: resize2fs /dev/datavg/data 30G
  4. Shrink the LV: lvreduce -L 30G /dev/datavg/data
  5. Remount.

The ordering is critical. If you shrink the LV before the filesystem, you truncate live filesystem data and will likely destroy it. The filesystem must always be smaller than or equal to the LV that contains it.

Adding a disk to an existing VG

When a VG starts running low on free PEs, add another disk:

1
2
pvcreate /dev/sdd
vgextend datavg /dev/sdd

This is instant. No data movement, no downtime. The VG simply gains more free PEs and any subsequent lvcreate or lvextend can use them.

Evacuating and removing a PV with pvmove

If you need to decommission a physical disk — for replacement, retirement, or to remove it from the VG — you can evacuate all its extents to other PVs in the same VG while the system remains live:

1
pvmove /dev/sdb

LVM moves all PEs from /dev/sdb to other PVs in the VG. This can be slow (it is moving data), and it runs in the background. Monitor it with pvdisplay /dev/sdb or lvs -a -o +devices. Once complete:

1
2
vgreduce datavg /dev/sdb
pvremove /dev/sdb

This is the correct way to hot-swap a disk out of an LVM VG without data loss.


LVM snapshots

How COW snapshots work

An LVM snapshot captures the state of an LV at the moment of creation. It does not copy any data immediately. Instead, LVM tracks which blocks in the origin LV are about to be overwritten — the first write to any block after snapshot creation causes LVM to copy the original block to the snapshot volume first, then allow the write to proceed. This is copy-on-write.

The snapshot therefore stores only the delta between its creation point and the current state. A snapshot of a 100 GB LV may consume only a few hundred megabytes if writes have been sparse since creation.

Creating a snapshot

1
lvcreate -L 5G -s -n data_snap /dev/datavg/data

The -s flag designates this as a snapshot of /dev/datavg/data. The -L 5G is the snapshot’s private COW allocation — not the size of the data being snapshotted, but the space reserved for storing changed blocks during the snapshot’s lifetime.

Mount it read-only to inspect or back up:

1
2
3
4
mount -o ro /dev/datavg/data_snap /mnt/snap
rsync -a /mnt/snap/ /backup/data/
umount /mnt/snap
lvremove /dev/datavg/data_snap

Sizing snapshots correctly

If the COW storage fills completely, the snapshot is invalidated. LVM marks it as invalid and the snapshot device becomes unusable — the original data is safe, but the snapshot is lost. Size the snapshot allocation generously relative to your expected write rate during the backup window. lvs shows current snapshot usage:

1
lvs -o lv_name,origin,snap_percent

If snap_percent is climbing toward 100, either the snapshot was undersized or the backup is taking too long.

Using snapshots for consistent backups

The standard pattern for a consistent backup of a live database:

  1. Quiesce application writes, or use the database’s own snapshot/freeze mechanism (e.g., FLUSH TABLES WITH READ LOCK for MySQL, pg_start_backup() for PostgreSQL), or use fsfreeze -f /mountpoint to freeze filesystem I/O.
  2. lvcreate -L 5G -s -n data_snap /dev/datavg/data
  3. Immediately resume writes: fsfreeze -u /mountpoint or release the database lock.
  4. Mount the snapshot and run the backup tool.
  5. lvremove /dev/datavg/data_snap when done.

The application is only paused for the milliseconds it takes to create the snapshot — essentially zero from the user’s perspective. The backup itself runs against the frozen snapshot and can take as long as it needs.

Snapshot performance impact

Every write to the origin LV while a snapshot exists triggers a COW read-modify-write to the snapshot device. On write-heavy workloads, this can impose 20–50% write latency overhead. Keep the snapshot alive only as long as necessary.

Rolling back with snapshot merge

If you took a snapshot before a risky change and want to revert:

1
lvconvert --merge /dev/datavg/data_snap

The next mount of the origin LV will complete the merge, rolling the origin back to the snapshot state. This is destructive — all writes to the origin since the snapshot was taken are discarded.


LVM thin provisioning

The concept

Standard (thick) LVM LV creation reserves physical extents immediately. If you create a 100 GB LV, 100 GB of PEs are allocated from the VG at that moment, whether or not any data is ever written. Thin provisioning separates the allocation of the virtual size from the allocation of physical backing storage. A thin LV can be 100 GB from the filesystem’s perspective, but consume zero physical PEs until data is actually written to it.

This enables overprovisioning — creating more virtual storage than you have physical storage — which is standard practice in virtualization and container environments.

Creating a thin pool

A thin pool is a special LV that acts as the backing store for thin LVs:

1
lvcreate -L 200G --thinpool mypool datavg

LVM actually creates two internal LVs: the data LV (for the actual data blocks) and a metadata LV (for the thin provisioning bookkeeping). You can tune the metadata size with --poolmetadatasize on large pools.

Creating thin LVs

1
2
3
lvcreate -V 100G --thin -n vm1disk datavg/mypool
lvcreate -V 100G --thin -n vm2disk datavg/mypool
lvcreate -V 100G --thin -n vm3disk datavg/mypool

Three 100 GB virtual LVs exist, but the physical pool is only 200 GB. This is overprovisioned at 150%. As long as the three VMs don’t collectively write more than 200 GB of unique data, everything works.

  Thin Pool: mypool (200GB physical)
  +--------------------------------------------------+
  |  Physical blocks allocated as writes occur       |
  |  [/////used/////][         free                ] |
  +--------------------------------------------------+
        |                   |                   |
        v                   v                   v
  +----------+       +----------+       +----------+
  | vm1disk  |       | vm2disk  |       | vm3disk  |
  | 100GB    |       | 100GB    |       | 100GB    |
  | virtual  |       | virtual  |       | virtual  |
  | (writes  |       | (writes  |       | (writes  |
  | trigger  |       | trigger  |       | trigger  |
  | physical |       | physical |       | physical |
  | alloc)   |       | alloc)   |       | alloc)   |
  +----------+       +----------+       +----------+
  Total virtual: 300GB  |  Total physical: 200GB
  Overprovisioning: 150%

Monitoring thin pool usage

1
lvs -o+data_percent,metadata_percent datavg

Watch data_percent carefully. If a thin pool fills to 100%, all thin LVs in it immediately go offline with I/O errors. The data is not lost — it is intact — but nothing can read or write until the pool has free space again. You can extend a thin pool online:

1
lvextend -L +50G datavg/mypool

Set up monitoring alerts at 70% and 85% pool usage. The dmeventd daemon can auto-extend thin pools if configured in /etc/lvm/lvm.conf under activation.thin_pool_autoextend_threshold.

Thin snapshots

Thin LVs support a dramatically more efficient snapshot mechanism than thick LVM snapshots:

1
lvcreate -s -n vm1disk_snap datavg/vm1disk

Because the thin pool is already a COW structure, thin snapshots are nearly instantaneous and consume no initial space — they share all existing blocks with the origin. Creating fifty snapshots of a 100 GB thin LV is trivially cheap in both time and space. This makes thin provisioning the right choice for VM disk image management.

The thin_dump and thin_delta tools from the thin-provisioning-tools package allow you to inspect the thin pool metadata and compute the changed blocks between two snapshots — useful for incremental backup schemes.


ext4 — the reliable workhorse

ext4 is the default filesystem on Debian, Ubuntu, and most general-purpose Linux distributions. It is the direct successor to ext3, adding extents (contiguous block ranges that replace the old indirect block chains), delayed allocation, online defragmentation support, and a 1 EB theoretical maximum volume size.

When to use ext4

Use ext4 when compatibility and predictability matter more than features. It is the right choice for boot partitions, system root volumes, any environment where you need e2fsck to be the most battle-tested repair tool available, and any scenario where the failure modes need to be completely understood. Ext4 has been in production for well over a decade. Its behavior in every failure scenario — power loss mid-write, kernel OOM during heavy I/O, disk errors during journal replay — is documented and understood.

Journaling modes

Ext4 supports three journal modes, set at mount time via data= in fstab:

  • ordered (default): metadata is journaled; data is written to disk before the metadata that references it is committed. Solid protection against metadata corruption with reasonable performance.
  • writeback: metadata is journaled, data is not ordered relative to metadata. Fastest, but a crash can result in stale data appearing in files (not corruption of the filesystem structure, just wrong file contents).
  • data: both metadata and data are journaled. Safest, but doubles write I/O and is noticeably slower. Rarely worth it given modern battery-backed RAID controllers and SSDs.

For most workloads, ordered is correct. The writeback mode is appropriate for workloads where the application provides its own data durability guarantees (e.g., databases using fsync).

tune2fs

tune2fs reads and modifies ext4 filesystem parameters on a live or unmounted filesystem:

1
tune2fs -l /dev/sda1

This prints everything — block size, inode count, free inodes, journal size, last mount time, last check time, UUID, and the current error behavior setting.

Useful adjustments:

1
2
3
4
5
6
7
8
9
# On error, remount read-only rather than continuing (safer default)
tune2fs -e remount-ro /dev/sda1

# Disable time- and mount-count-based fsck (reasonable on SSDs with monitoring)
tune2fs -i 0 -c 0 /dev/sda1

# Reduce reserved block percentage from 5% to 1% on a large data volume
# (the 5% reserve makes sense on root FS for root processes; wasteful on pure data volumes)
tune2fs -m 1 /dev/sda1

The reserved block default of 5% dates from when disks were smaller and running out of space on the root filesystem was a serious operational hazard. On a 10 TB data volume, 5% is 500 GB sitting idle for a purpose that may not apply at all. Drop it to 1%.

Limitations

Ext4 has no built-in checksums for data or metadata. A silent bit flip in the storage layer — from a faulty DIMM, a misbehaving HBA, or a flaky cable — will be written to disk and read back without any detection. Ext4 trusts the storage layer to be correct. For most environments this is fine; for high-value data, it is a reason to prefer ZFS or Btrfs. Ext4 also has no native snapshot mechanism — use LVM snapshots underneath it.


XFS — high-performance for large files

XFS originated at SGI in the early 1990s, designed for the demanding I/O requirements of graphics and media workloads. It became the default filesystem on RHEL 7 and has remained so through RHEL 9 and AlmaLinux 9. It is particularly well-suited to large files and high-throughput sequential I/O.

When to use XFS

XFS is the right choice for large filesystems (tens of terabytes), workloads dominated by large sequential reads and writes (video production, database tablespaces, backup targets, HPC scratch space), and environments running RHEL or its derivatives where XFS is the supported default. It handles parallel I/O well due to its allocation group architecture — the filesystem is divided into independent allocation groups that can process metadata operations concurrently.

Architecture and key features

XFS uses allocation groups to parallelize metadata operations. Each AG has its own free space B-trees and inode B-trees, so threads writing to different AGs do not contend on the same metadata locks. The number of AGs is determined at mkfs.xfs time based on filesystem size.

Delayed logging (-l version=2, the default since XFS v5) batches journal writes into a large in-memory log buffer, dramatically reducing journal I/O overhead on write-heavy workloads. The tradeoff is that after an unclean shutdown, log recovery can take meaningful time proportional to the amount of in-flight transactions.

1
xfs_info /mnt/data

This shows the filesystem geometry — block size, sector size, AG count, log size, and whether v5 features (checksums on metadata, self-describing metadata) are enabled. Note that XFS v5 adds metadata checksums, which protects filesystem structure but not user data.

Working with XFS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Repair (must be unmounted)
xfs_repair /dev/datavg/data

# Dry run — report issues without fixing
xfs_repair -n /dev/datavg/data

# Online defragmentation
xfs_fsr /mnt/data

# Relabel
xfs_admin -L newlabel /dev/datavg/data

xfs_repair is robust but slow on large filesystems. On a multi-terabyte XFS filesystem, expect repair to take hours. Plan for this in your recovery time objectives.

Limitations

XFS cannot shrink. The filesystem and the underlying LV or partition can only be extended, never reduced. If you anticipate needing to shrink a filesystem, XFS is not the right choice. XFS also has no native snapshot mechanism — use LVM snapshots under XFS, and use xfs_freeze to quiesce before snapping:

1
2
3
xfs_freeze -f /mnt/data
lvcreate -L 5G -s -n data_snap /dev/datavg/data
xfs_freeze -u /mnt/data

XFS vs ext4 for databases: on large sequential I/O (full table scans, backup/restore, bulk inserts), XFS typically wins. For small random I/O (OLTP with many small transactions), ext4 with data=writeback is often comparable or slightly faster. Both are vastly inferior to ZFS with ARC or a properly tuned filesystem on NVMe for the highest-performance database workloads.


Btrfs — the modern Linux filesystem

Btrfs (B-tree filesystem) has been in the mainline kernel since 2.6.29 (2009). It is the default root filesystem on Fedora Workstation, openSUSE Tumbleweed, and openSUSE Leap. Synology uses it as their primary filesystem type on supported NAS units. Facebook ran it in production at scale for years. And yet it accumulated a reputation for data loss that still lingers — a reputation that was earned, partly, by the RAID 5/6 implementation, and that now overstates the risk for common single-disk and RAID 1 use cases.

What Btrfs brings to the table

The core architecture is copy-on-write. Every write allocates new blocks rather than overwriting existing ones, which is what makes snapshots nearly free, reflinks fast, and checksums practical. Built-in checksums cover both data and metadata — every block read from disk is verified against a stored checksum, and silent corruption is detected and reported. This is the most important single feature advantage Btrfs has over ext4 and XFS.

Additional major features: subvolumes (lightweight filesystem namespaces within a single volume), native snapshots (read-write or read-only, shared-block, instant), transparent compression (lzo, zlib, zstd), online balance (redistributes data across devices), send/receive for incremental replication, and RAID profiles at the filesystem level.

Subvolumes

A subvolume is a namespaced section of a Btrfs volume that behaves like an independent filesystem for most purposes — it can be mounted separately, have its own snapshot policy, and be assigned quota groups. Subvolumes are the foundation of good Btrfs layout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Create the filesystem
mkfs.btrfs /dev/datavg/data
mount /dev/datavg/data /mnt/btrfs

# Create subvolumes
btrfs subvolume create /mnt/btrfs/@
btrfs subvolume create /mnt/btrfs/@home
btrfs subvolume create /mnt/btrfs/@snapshots

# Mount subvolumes independently
mount -o subvol=@ /dev/datavg/data /
mount -o subvol=@home /dev/datavg/data /home

The conventional @ naming for the root subvolume is used by Ubuntu’s installer and by snapper. Adopting it from the start keeps your layout compatible with standard tooling.

  Btrfs Volume: /dev/datavg/data
  +---------------------------------------------+
  |  [top-level subvolume]                      |
  |    |                                        |
  |    +-- @                (root FS)           |
  |    |     |                                  |
  |    |     +-- snapshot: @-2026-05-01  (RO)   |
  |    |     +-- snapshot: @-2026-05-15  (RO)   |
  |    |     +-- snapshot: @-2026-05-30  (RO)   |
  |    |                                        |
  |    +-- @home             (home dir)         |
  |    |     |                                  |
  |    |     +-- snapshot: @home-2026-05-30 (RO)|
  |    |                                        |
  |    +-- @snapshots        (snapshot store)   |
  |    |                                        |
  |    +-- @docker           (container storage)|
  +---------------------------------------------+

Snapshots

1
2
3
4
5
# Read-write snapshot
btrfs subvolume snapshot /mnt/btrfs/@ /mnt/btrfs/@snapshots/@-$(date +%F)

# Read-only snapshot (preferred for backups)
btrfs subvolume snapshot -r /mnt/btrfs/@ /mnt/btrfs/@snapshots/@-$(date +%F)

Snapshots are created instantly regardless of the subvolume’s size. They share all existing blocks with the origin — a snapshot of a 500 GB subvolume consumes no additional space at creation, and grows only as either the origin or the snapshot accumulates divergent writes.

btrfs send / receive

The killer feature for backup replication:

1
2
3
4
5
6
7
# Initial full send
btrfs send /mnt/btrfs/@snapshots/@-2026-05-01 | btrfs receive /backup/btrfs/

# Incremental send (only the delta between two snapshots)
btrfs send -p /mnt/btrfs/@snapshots/@-2026-05-01 \
              /mnt/btrfs/@snapshots/@-2026-05-02 \
  | btrfs receive /backup/btrfs/

Incremental sends are efficient — they transmit only the blocks that changed between the parent and child snapshot. This is the Btrfs equivalent of ZFS send/receive, and it makes replication of large datasets practical even over slow links.

Compression

1
mount -o compress=zstd:3 /dev/datavg/data /mnt/data

Zstd at level 3 is a good default — fast, good ratio. Check actual compression ratios with:

1
btrfs fi usage /mnt/data

The output shows uncompressed data size vs. on-disk size. For source code, logs, and configuration files, expect 30–50% space savings. For already-compressed content (JPEGs, MP4s, zip files), expect near-zero savings and do not waste CPU cycles on it. The compress-force option forces compression attempts even on blocks that previously compressed poorly — avoid it.

Scrub

1
2
btrfs scrub start /mnt/data
btrfs scrub status /mnt/data

Scrub reads every block on the volume and verifies it against the stored checksum. It detects and (on RAID profiles with redundancy) corrects silent data corruption. Schedule scrubs monthly via systemd timer. This is functionality ext4 and XFS simply do not have at the data level.

RAID in Btrfs — what is safe and what is not

Btrfs RAID profiles are set per data type (data, metadata, system):

  • RAID 1 (two copies of each block, can span two+ devices): production-safe, well-tested.
  • RAID 10 (striped mirrors): production-safe.
  • RAID 5 / RAID 6: do not use for data you care about. As of Linux kernel 6.x and current development in 2025–2026, the RAID 5/6 write hole remains an open issue. A partial-stripe write followed by a power failure can leave parity inconsistent with data, and subsequent scrubs will blindly repair data to match the wrong parity rather than detecting the corruption. The Btrfs kernel documentation and upstream developers themselves have historically warned strongly against RAID 5/6 for production data. This situation has not been fully resolved. The kernel added stronger warnings when creating RAID 5/6 arrays precisely because users kept ignoring the existing documentation.

If you need software RAID 5/6 under Btrfs, use mdadm for the RAID layer and run Btrfs in single profile on top of the md device. You lose Btrfs’s self-healing scrub for the RAID layer, but the RAID itself is managed by a more mature implementation.

Historical stability and current reality

Btrfs had genuine data loss bugs through roughly 2017–2018, particularly around balance operations, RAID, and specific edge cases with COW metadata. The kernel 5.x series resolved most of the serious issues for common configurations. As of kernel 6.x, Btrfs RAID 1 on a single disk or multi-disk is stable for everyday use. The caveats that remain: RAID 5/6 (as above), very fragmented filesystems (heavy random writes on nearly-full volumes can lead to ENOSPC before df shows 100%), and the need to monitor free space more carefully than with ext4.

If you are on a current kernel (6.1 LTS or newer) and not using RAID 5/6, Btrfs is a legitimate production filesystem for the use cases where its features matter.


ZFS on Linux (OpenZFS)

A full ZFS treatment is beyond this post’s scope — the LunarOps posts on TrueNAS SCALE and homelab NAS cover ZFS architecture, pool design, RAID-Z levels, and ARC tuning in depth. What matters here is the Linux-specific context and the ZFS vs. Btrfs decision.

OpenZFS on Linux

ZFS is not in the mainline Linux kernel. The OpenZFS project maintains the Linux port, distributed as a DKMS module. The reason it is not in-tree is a license conflict: OpenZFS is licensed under the CDDL, which is not compatible with the GPL that covers the kernel. This is not a technical problem — it is a legal one — but it has practical consequences: ZFS is not in the default kernel builds from most distributions and must be installed separately.

Ubuntu ships ZFS support as a first-class option. On Ubuntu 24.04 LTS, zfsutils-linux and the OpenZFS 2.2.x series are available directly from the main repository:

1
2
apt install zfsutils-linux
modprobe zfs

Ubuntu’s installer also supports native ZFS root with optional encryption. On RHEL/AlmaLinux, ZFS is available through the ZFS on Linux repository (zfsonlinux.org) but is not in the official repositories and requires DKMS with kernel headers.

The kernel module must be rebuilt when the kernel updates. DKMS handles this automatically, but it introduces a brief window after a kernel upgrade before the new module is compiled — plan reboots accordingly in production.

Why ZFS over Btrfs for production

ZFS’s most important advantage is maturity. It has been running in production on Solaris and FreeBSD in high-stakes environments (financial services, large-scale NAS appliances, OpenStack storage nodes) for over fifteen years. TrueNAS, Proxmox Backup Server, and the storage backends of many commercial NAS vendors use it. The RAID-Z implementation does not have the write-hole problem that plagues Btrfs RAID 5/6. The ARC (Adaptive Replacement Cache) is an effective read cache that can be sized to available RAM and dramatically accelerates repeated access patterns. ZFS send/receive for incremental snapshot replication is mature and widely used.

For a production file server, NAS, or any workload where data integrity is the primary concern and you are willing to manage the DKMS module and rebuild workflow, ZFS is the stronger choice. For a desktop Linux system or a developer workstation where the subvolume UX and snapper integration are attractive, Btrfs is entirely reasonable.


SSD alignment and TRIM

Why alignment matters

SSDs and modern hard drives with 4Kn (4096-byte native sectors) require that filesystem block boundaries align with physical sector boundaries. A misaligned filesystem that straddles two physical sectors on an SSD forces the drive to read two physical sectors, modify one logical sector, and write both back — a read-modify-write amplification penalty on every write.

Modern partitioning tools (fdisk in interactive mode since util-linux 2.17, parted, gdisk) default to 1 MB alignment for all partitions. 1 MB is a multiple of every common physical sector size (512B, 4096B) and of typical SSD erase block sizes. If you created your partitions with a modern tool, alignment is almost certainly correct.

Verify:

1
2
3
4
5
6
# Physical and logical sector sizes
lsblk -o NAME,PHY-SEC,LOG-SEC /dev/sda

# Partition alignment check
cat /sys/block/sda/queue/physical_block_size
parted /dev/sda align-check optimal 1

For LVM over correctly aligned partitions or whole disks, alignment propagates correctly — the PE size default of 4 MB is well-aligned to any reasonable physical sector size.

TRIM and discard

TRIM (the ATA command) and DISCARD (the NVMe/SCSI equivalent) tell the SSD controller which logical block addresses are no longer in use. Without this information, the SSD’s garbage collection algorithm must read pages before erasing them to determine if they contain live data, which degrades write performance over time as the drive fills.

Two approaches to issuing TRIM:

Continuous discard (discard mount option in /etc/fstab):

/dev/datavg/data  /mnt/data  ext4  defaults,discard  0 2

Every unlink or fallocate --punch-hole immediately sends a DISCARD to the SSD. The overhead is small but measurable on write-heavy workloads, since every delete generates additional I/O commands. Generally not recommended for HDDs (no effect) or for high-frequency delete workloads.

Periodic TRIM (fstrim):

1
2
# Manual one-shot TRIM of all mounted TRIM-capable filesystems
fstrim -av

The fstrim.timer systemd unit ships with util-linux and runs fstrim -av weekly. On Ubuntu 22.04+, Debian 12 (Bookworm) fresh installs, and most modern distributions, this timer is enabled by default. Verify:

1
systemctl status fstrim.timer

Note: systems upgraded from earlier Debian versions may not have the timer enabled even if it ships enabled on fresh installs — check explicitly. Enable it if it is not running:

1
systemctl enable --now fstrim.timer

Periodic TRIM via fstrim.timer is the recommended approach for most environments. It is low-overhead, runs during a quiet period, and does not add per-operation latency.

LVM discard passthrough

If you are running filesystems on LVM, TRIM commands from the filesystem must be passed through the DM (device mapper) layer to reach the underlying block device. This is not enabled by default. Edit /etc/lvm/lvm.conf:

activation {
    issue_discards = 1
}

Without this, fstrim on a filesystem mounted on an LV sends DISCARD to the logical volume but it is silently dropped before reaching the physical disk. With thin provisioning, discard passthrough also allows the thin pool to reclaim blocks when files are deleted, which is essential for controlling thin pool growth.


Filesystem comparison

Feature ext4 XFS Btrfs ZFS
Data checksums No No (metadata only in v5) Yes Yes
Metadata checksums No Yes (v5) Yes Yes
Native snapshots No No Yes Yes
Transparent compression No No Yes (lzo/zlib/zstd) Yes (lz4/zstd/gzip)
Native RAID No No Yes (0/1/10; avoid 5/6) Yes (RAID-Z 1/2/3)
Online grow Yes Yes Yes Yes
Online shrink Yes No Yes No
Send/receive replication No No Yes Yes
Deduplication No No Limited (offline) Yes (inline)
Max volume size 1 EB 8 EB 16 EB 256 ZB
Max file size 16 TB 8 EB 16 EB 16 EB
Repair tool e2fsck xfs_repair btrfs check zpool scrub/zfs scrub
Shrink support Yes No Yes No
Kernel module In-tree In-tree In-tree DKMS (out-of-tree)
RHEL default No Yes No No
Ubuntu default (root) Yes No Optional Optional
Fedora default (desktop) No No Yes No
Production maturity Very high Very high High (excl. RAID 5/6) Very high (OpenZFS)
Best for General purpose, boot, compatibility Large FS, high-throughput, RHEL Desktop, snapshots, COW workloads NAS, integrity-critical, server

Decision guide

Boot partition (/boot, /boot/efi): ext4 always. Bootloaders have limited filesystem driver support, and you do not want complexity on the partition that the system must read before the kernel is even running.

RHEL, AlmaLinux, Rocky Linux root and data volumes: XFS. It is the supported default, the one Red Hat’s support team knows deeply, and it performs excellently on the large storage workloads these platforms are often running. There is no good reason to fight the default here.

Debian/Ubuntu server, general-purpose data volumes: ext4 for simplicity and maximum tool compatibility; XFS if the volume is large and the workload is sequential-I/O-heavy; Btrfs if you specifically want checksums or subvolume/snapshot functionality without ZFS.

Desktop Linux with rollback capability: Btrfs with snapper is the most ergonomic option. Fedora and openSUSE ship this integration out of the box. The ability to take a pre-upgrade snapshot, upgrade, and roll back if something breaks is genuinely useful and Btrfs provides it at essentially no cost.

Container storage (Docker, Podman, containerd): Btrfs with the btrfs storage driver on a dedicated subvolume, or ext4/XFS with overlay2. The Btrfs reflink support makes layer sharing efficient; the subvolume model integrates well with the container image layer concept.

NAS, TrueNAS, file server where data integrity is paramount: ZFS. RAID-Z is reliable in a way Btrfs RAID 5/6 is not yet. The ARC is effective. ZFS send/receive for replication is battle-tested at scale. The DKMS module requirement is a real operational cost, but it is manageable.

HPC scratch or high-throughput sequential workloads on large volumes: XFS on LVM. Straightforward, fast, well-understood.

Any workload requiring silent corruption detection on a budget server without ECC RAM: Btrfs or ZFS. This is the most under-appreciated use case for checksummed filesystems. A server with faulty RAM or a marginal HBA can silently corrupt data that ext4 and XFS will store and serve back without complaint. Checksums detect this at the filesystem layer.


Putting it together: a practical storage stack

A typical production server might look like this:

  • Two NVMe drives for OS and databases, running in LVM with ext4 on a modest LV for the root filesystem, XFS on a larger LV for database tablespaces. LVM provides the flexibility to extend these without reinstallation.
  • Four HDDs for bulk storage, provisioned as an LVM VG with thin provisioning for multiple workloads sharing the same physical pool. Periodic pvmove to cycle aging disks out without downtime.
  • Btrfs for a developer-facing code repository server, taking hourly read-only snapshots with btrfs subvolume snapshot -r and replicating incrementally to a backup server with btrfs send | ssh backup btrfs receive.
  • ZFS for a separate NAS that holds long-term archives, configured with RAID-Z2 for redundancy, weekly scrubs, and ZFS send/receive replication to an off-site target.

LVM and the filesystems are not competing technologies. LVM operates below the filesystem and provides the block-level flexibility that makes everything above it more manageable. The filesystem choice above LVM (or above a ZFS pool, which has its own volume management) depends on the workload’s specific requirements: compatibility, throughput profile, need for checksums, need for snapshots, and operational context.

Understanding the full stack — from physical extent through volume group through logical volume through filesystem — is what separates storage administration from storage archaeology. With the tools and concepts in this post, you can design storage layouts that you can grow, adapt, and maintain without surprises.

Comments