Skip to content
Menu

Linux Storage10 min read

ext4 & XFS

The two mainstream Linux filesystems — internals and trade-offs

Why These Two

On a modern Linux system, the default root filesystem is almost always ext4 (Debian, Ubuntu) or XFS (RHEL, Fedora Server, Oracle Linux, Rocky, Alma). Both are mature, journaled, extent-based, and production-proven on PB-scale deployments. They differ in design philosophy — and in a few operational properties that will bite you if you pick wrong.

ext4: Evolutionary Line

  1. ext2

    1993 — no journal, fast but fragile after crash

  2. ext3

    2001 — added journaling (JBD), otherwise same on-disk layout

  3. ext4

    2008 — extents, 48-bit block nrs, dir_index, delayed allocation, checksums

ext4 preserves forward and backward compatibility with ext2/ext3 for features not in use — you can mount an ext3 volume as ext4 and vice versa. This lineage is part of its appeal: battle-tested code with incremental, well-understood evolution.

ext4 On-Disk Layout

ext4 divides the block device into block groups, typically 128 MiB each. Each group holds everything needed to allocate files near their metadata — a locality strategy inherited from BSD's FFS.

Anatomy of a Block Group

RegionPurpose
Superblock (copy)Filesystem-wide metadata. Not in every group — only in groups 0, 1, and powers of 3, 5, 7 (sparse_super).
Group descriptor tablePoints to this group's bitmaps and inode table. Duplicated wherever the superblock is.
Block bitmapOne bit per data block: free or in use.
Inode bitmapOne bit per inode slot: free or in use.
Inode tableArray of fixed-size inode structures (default 256 B each).
Data blocksThe rest — where file contents live.

Note

Why backup superblocks matter: if block 0 gets corrupted, you can recover by pointing e2fsck at a backup: e2fsck -b 32768 /dev/sdX. mke2fs -n prints the locations without actually creating the FS.

ext4 Features Worth Knowing

FeatureWhat it does
extentsReplaces ext3's triple-indirect block tree with (offset, length, physical_block) triplets. Lower metadata overhead, faster for large files.
dir_indexHashed B-tree for large directories. Without it, directory lookup is linear — miserable past ~10k entries.
flex_bg"Flexible block groups": consolidates bitmaps and inode tables from several adjacent groups into one place, reducing seeks.
huge_fileUses i_blocks in cluster units instead of 512 B sectors — allows files > 2 TiB.
uninit_bgMarks unused block groups so fsck can skip them — drastically faster checks on large empty filesystems.
inline_dataSmall files (< ~60 B) stored entirely inside the inode's block-pointer area. No data block needed at all.
64bit48-bit block numbers, enabling filesystems larger than 16 TiB (up to 1 EiB theoretical).
metadata_csumCRC32c checksums on superblock, group descriptors, bitmaps, inode table, extent tree, directory blocks. Detects silent corruption.
encryptPer-directory encryption (fscrypt). Same key API used by Android and Chrome OS.

ext4 Tuning

console
console

# # Create a filesystem tuned for a specific workload
# mkfs.ext4 -T largefile4   /dev/sdb1      # 1 inode per 4 MiB — huge media library
# mkfs.ext4 -T news          /dev/sdc1     # 1 inode per 4 KiB — Maildir/news spool
# mkfs.ext4 -T largefile     /dev/sdd1     # 1 inode per 1 MiB — sweet spot for bulk
#
# # Explicit inode count override
# mkfs.ext4 -N 5000000 /dev/sde1            # force 5M inodes regardless of size
#
# # RAID stripe alignment (stride = chunk/block, stripe-width = stride * data disks)
# mkfs.ext4 -E stride=16,stripe-width=64 /dev/md0
  

Reserved Blocks

By default, ext4 reserves 5% of the filesystem for root. This prevents unprivileged users from filling the disk and starving systemd-journald, syslog, or anything else that must keep writing. On a 10 TiB data volume, this is 500 GiB wasted — adjust with:

console
console

# tune2fs -m 1 /dev/sda1     # drop reserve to 1%
# tune2fs -m 0 /dev/sdb1     # no reserve (fine for data-only volumes)
  

Tip

Rule of thumb: keep the 5% reserve on the root FS. Drop it to 0 on pure-data mounts (backups, media, object stores, container image layers).

ext4 Command Line

CommandUse
mkfs.ext4Create a new ext4 filesystem.
tune2fsChange parameters on an existing FS: reserve %, mount count, label, UUID, feature flags.
resize2fsGrow (online) or shrink (offline) the filesystem. Grow is routine; shrink requires unmount and is slower.
dumpe2fsPrint superblock and group info — fantastic for forensics.
debugfsInteractive low-level repair/inspection shell. Undelete files, inspect inodes by number, dump blocks.
e2fsck / fsck.ext4Check and repair.
console
console

# dumpe2fs -h /dev/sda1 | head -30
Filesystem volume name:   root
Last mounted on:          /
Filesystem UUID:          3f9a1b6e-...
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index
                          filetype needs_recovery extent 64bit flex_bg
                          metadata_csum_seed sparse_super large_file
                          huge_file uninit_bg dir_nlink extra_isize
                          encrypt
Inode count:              6553600
Block count:              26214400
Block size:               4096
Reserved block count:     1310720
...
  

XFS: Scalable From Day One

XFS was designed by Silicon Graphics in 1993 for IRIX — specifically to serve TB-sized video-editing workloads on multi-CPU workstations. Ported to Linux in 2001, merged upstream in 2002. Its entire design is oriented around scalability and concurrency: B+trees everywhere, parallel allocation groups, dynamic inode allocation.

RHEL made XFS the default root filesystem in RHEL 7 (2014), reflecting a quarter-century of production use.

XFS Design Principles

Allocation Groups (AGs)

An XFS filesystem is carved into a small number (usually 4–32) of allocation groups. Each AG is effectively a mini-filesystem with its own free-space and inode indexes. Because allocations within different AGs don't contend for the same locks, XFS scales to many concurrent writers far better than ext4.

B+ Trees Everywhere

  • Free-space by block number — locate free runs adjacent to existing data.
  • Free-space by length — find a run of exactly N free blocks.
  • Inode B+tree — track which inode slots are allocated (inodes themselves are assigned dynamically, no pre-allocation).
  • Directory B+tree — O(log n) lookups in directories with millions of entries.
  • Extent B+tree inside the inode — maps logical offsets to physical extents for large files.

Dynamic Inode Allocation

XFS does not pre-allocate inodes at mkfs time. New inodes are carved out of free space on demand. Practical consequences:

  • You can't run out of inodes while having free space — a common ext4 failure mode.
  • Deleted inode space isn't automatically reclaimed to the pool of data blocks until the chunk is empty (this is usually invisible in practice).

Metadata-Only Journal

XFS journals metadata only. File data is never journaled. Ordering is enforced by writing data blocks before the metadata that references them, similar to ext4's data=ordered. There is no data=journal equivalent — if you need that level of durability, put the workload on a FS that supports it, or use application-level fsync.

XFS Features

FeatureWhat it does
Delayed allocationWrites stay in memory; physical blocks aren't chosen until flush. Lets the allocator see the full size of the write and place it contiguously. Excellent for reducing fragmentation.
Online defragxfs_fsr (filesystem reorganizer) runs while mounted and writable. Walks files, rewrites fragmented ones into contiguous extents.
ReflinksCopy-on-write copies within a file or between files: cp --reflink=always a b shares extents until one side writes. Used heavily by container storage drivers and backup tools (borg, restic).
QuotasUser, group, and project quotas. Project quotas set limits on arbitrary directory trees — the mechanism Kubernetes uses for enforced per-volume quotas on XFS PVs.
Realtime subvolumeSeparate data region with no metadata — extents allocated in fixed-size contiguous chunks. Deterministic I/O latency for video/audio workloads. Rare in practice.
CRC32c metadata checksumsOn by default since mkfs.xfs 3.2.3 (2014). Every metadata block is verified on read. Silent corruption produces an explicit error, not mysterious misbehavior.
Scalability ceiling500 TiB per filesystem on 64-bit systems is routine; 8 EiB theoretical. Directories with tens of millions of entries remain fast.

XFS Tuning

console
console

# # Default mkfs is already sane. Tune only when needed.
# mkfs.xfs /dev/sda1
#
# # RAID alignment — sunit in 512B sectors, swidth = sunit * data disks
# mkfs.xfs -d sunit=128,swidth=512 /dev/md0
#
# # More allocation groups for a high-concurrency workload
# mkfs.xfs -d agcount=32 /dev/sdb1
#
# # Larger log buffer — reduces log commit frequency under heavy metadata load
# mount -o logbsize=256k /dev/sda1 /data
#
# # Inspect an existing XFS
# xfs_info /data
meta-data=/dev/sda1   isize=512    agcount=4, agsize=6553600 blks
         =              sectsz=4096  attr=2, projid32bit=1
         =              crc=1        finobt=1, sparse=1, rmapbt=0
data     =              bsize=4096   blocks=26214400, imaxpct=25
         =              sunit=0      swidth=0 blks
naming   =version 2     bsize=4096   ascii-ci=0, ftype=1
log      =internal log  bsize=4096   blocks=12800, version=2
         =              sectsz=4096  sunit=1 blks, lazy-count=1
realtime =none          extsz=4096   blocks=0, rtextents=0
  

XFS Utilities Cheat Sheet

  • mkfs.xfs — create
  • xfs_info — like dumpe2fs -h, but for XFS
  • xfs_admin — change label / UUID
  • xfs_growfs — online grow (no offline option)
  • xfs_fsr — online defragmenter
  • xfs_repair — offline checker/repair (XFS has no fsck.xfs that does real work; the mount-time log replay handles most cases)
  • xfs_db — low-level debugger, equivalent to debugfs
  • xfs_quota — manage user/group/project quotas

Growing and Shrinking

ext4

Grow: online, with resize2fs /dev/sda1 after the underlying block device/LV has been enlarged.

Shrink: offline only — unmount, e2fsck -f, resize2fs <dev> <size>, then shrink the block device. Slow but supported.

XFS

Grow: online, with xfs_growfs /mount/point.

Shrink: NOT SUPPORTED. Ever. There is no tool. The only way to "shrink" an XFS volume is xfs_dump/xfsrestore to a smaller one, or rsync + reformat.

Warning

The XFS shrink gotcha: If you provision an XFS volume too large — say, a 10 TiB LV on a thin-provisioned pool — you're committed. There's no going back without downtime and data migration. Always err on the side of smaller XFS volumes and grow later. On LVM, plan capacity carefully; on cloud (EBS, GCE PD), remember that volume shrink isn't supported at the block layer anyway, so this is often moot.

ext4 vs XFS — Side by Side

Propertyext4XFS
Max filesystem size (practical)~50 TiB recommendedPB scale, tested to 500+ TiB
Max file size16 TiB8 EiB
InodesPre-allocated at mkfs (can run out)Dynamic (cannot run out independently)
JournalingMetadata + optional data journalingMetadata only
AllocationBlock bitmap + extent treeB+ trees (free by block, free by length)
ConcurrencyGood; serialized on group bitmap locksExcellent; independent AGs
Delayed allocationYes (since 2.6.30)Yes (original)
Reflinks (CoW)NoYes
Project quotasExperimental / limitedFirst-class (used by K8s)
Metadata checksumsOptional (default on mkfs ≥ 1.43)On by default
ShrinkOffline, supportedNot supported
Online growYes (resize2fs)Yes (xfs_growfs)
Small-file performanceStrong, especially with inline_dataGood; large inode size helps
Large-file throughputGoodExcellent — the original design target

Choosing Between Them

Pick ext4 when…
  • It's a general-purpose volume < 16 TiB.
  • You may need to shrink later.
  • Workload is small files and you want inline_data.
  • You want the absolute maximum ecosystem maturity on Debian/Ubuntu.
  • You need data=journal for a paranoid durability requirement.
Pick XFS when…
  • The filesystem is > 16 TiB, or will grow large.
  • Many concurrent writers (database storage, mail spools, object-store backends).
  • Workload is large files (video, backups, databases).
  • You need reflinks (container storage, dedup-aware backups).
  • You need project quotas (Kubernetes XFS PVs).
  • You're on RHEL-family and want the distro default.

Tip

Default heuristic: ext4 for the root filesystem and small data volumes, XFS for anything that will get large or concurrent. If you have no opinion, follow your distro's default.

Honorable Mentions

FSStatusNotes
f2fsMainline, production on AndroidFlash-Friendly FS. Log-structured design tailored for NAND erase-block behavior. Solid choice for low-end SSDs, SD cards, embedded devices. Rarely used for general-purpose servers.
ReiserFS / Reiser4DeprecatedInnovative B+tree design in the early 2000s, later abandoned. Removed from mainline Linux in 6.13. Not a choice for new deployments.
bcachefsExperimental (merged in 6.7, 2024)CoW filesystem with built-in tiered caching, compression, snapshots, encryption. Ambitious — aims to be what btrfs was supposed to be. Not yet production-grade. Worth watching.

Note

btrfs and ZFS are covered separately under Advanced Filesystems (storage-08). They're CoW filesystems with volume management, snapshots, and checksumming baked in — a fundamentally different architecture from ext4/XFS.

Takeaways

  • ext4 and XFS are the two production defaults on Linux. Both are journaled, extent-based, and battle-tested.
  • ext4 evolved from ext2/ext3 with backward compatibility; XFS was built scalable from day one.
  • ext4 pre-allocates inodes (risk of ENOSPC with free space); XFS allocates them dynamically.
  • XFS journals metadata only. No data=journal equivalent.
  • XFS cannot shrink. Plan capacity conservatively or accept migration downtime.
  • Use reflinks (cp --reflink) on XFS for fast CoW copies — huge win for backups and container layers.
  • Default heuristic: ext4 for small/shrinkable volumes, XFS for large/concurrent/large-file workloads.
  • f2fs for flash-only embedded; bcachefs still baking; ReiserFS is history.
Solidnines — solidnines.com