Overview

The filesystem is the bridge between the operating system and storage devices, directly impacting data reliability, I/O performance, and operational complexity. Linux supports multiple filesystems, each with its own design trade-offs. This article compares the four mainstream filesystems — ext4, xfs, btrfs, and zfs — and dives into mount parameter optimization, I/O scheduler selection, journal modes, fsync performance, and other core topics, with production-grade selection recommendations and tuning strategies.

Filesystem Comparison

Core Feature Overview

Featureext4xfsbtrfszfs
Max file size16TB8EB16EB16EB
Max volume1EB8EB16EB256ZB
JournalingYesYesYes (CoW)Yes (CoW/ZIL)
SnapshotsNoNoYesYes
CompressionNoNoYesYes
DeduplicationNoNoYes (experimental)Yes
ChecksumsNoNoYesYes
SubvolumesNoNoYesYes
RAIDNoNoYesYes (native)
Online growYesYesYesYes
Online shrinkNoNoYesNo
CoWPartialNoYesYes
DeveloperLinux communitySGI → Red HatOracleOpenZFS

ext4: The Stable, Reliable Traditional Choice

ext4 is an improved version of ext3, merged into the mainline kernel in 2008. It is the default filesystem for Ubuntu, Debian, and other distributions.

Advantages:

  • Extremely high stability and maturity
  • Broad community support
  • File-level journaling (configurable modes)
  • Delayed allocation
  • Multiblock allocator
  • Fast fsck (via unallocated block tables)

Disadvantages:

  • No snapshots, no compression, no checksums
  • Slower deletion of large files
  • No online shrinking
# Create ext4 filesystem
$ mkfs.ext4 -L data /dev/sdb1

# Common options
$ mkfs.ext4 -b 4096 -O ^has_journal /dev/sdb1  # Disable journal (not recommended for production)

xfs: High-Performance King for Large Files

xfs was developed by SGI and later merged into the mainline by Red Hat. It excels at handling large files and high-concurrency I/O, and is the default filesystem for RHEL/CentOS.

Advantages:

  • Excellent large file performance
  • High-concurrency I/O throughput
  • Simple online growth
  • Allocation Group (AG) design supporting parallel allocation
  • B+ tree index for space allocation

Disadvantages:

  • No snapshots
  • Large file deletion can cause brief I/O stalls
  • Cannot shrink the filesystem
# Create xfs filesystem
$ mkfs.xfs -L data /dev/sdb1

# Specify AG count (affects parallel performance)
$ mkfs.xfs -d agcount=16 /dev/sdb1

btrfs: Next-Generation CoW Filesystem

btrfs (B-tree FS) was developed by Oracle and introduces modern filesystem features: snapshots, compression, checksums, subvolumes, and RAID.

Advantages:

  • Transparent compression (zlib/lzo/zstd)
  • Snapshots (created in seconds)
  • Subvolumes (similar to LVM)
  • Data/metadata checksums
  • Online deduplication (experimental)
  • Transparent RAID (supports RAID1/RAID10)

Disadvantages:

  • RAID5/6 still unstable
  • Performance not as good as ext4/xfs (CoW write amplification)
  • Random write performance affected by fragmentation
# Create btrfs filesystem
$ mkfs.btrfs -L data /dev/sdb1

# Mount with zstd compression
$ mount -o compress=zstd /dev/sdb1 /data

# Create subvolume
$ btrfs subvolume create /data/snapshots

# Create snapshot
$ btrfs subvolume snapshot /data /data/snapshots/$(date +%Y%m%d)

zfs: Enterprise-Grade Unified Storage

zfs integrates filesystem, volume manager, and RAID into a unified system, providing end-to-end data integrity guarantees.

Advantages:

  • End-to-end checksums (self-healing)
  • Native RAID-Z (superior to hardware RAID)
  • Transparent compression (lz4 default)
  • Snapshots and clones
  • ARC/L2ARC secondary cache
  • ZIL/SLOG synchronous write acceleration

Disadvantages:

  • CDDL license incompatible with GPL (requires ZFS on Linux module)
  • High memory requirement (recommend 1GB ARC per 1TB storage)
  • Cannot directly shrink a pool
# Create zpool (RAID1 mirror)
$ zpool create -f tank mirror /dev/sdb /dev/sdc

# Enable lz4 compression
$ zfs set compression=lz4 tank

# Create dataset
$ zfs create tank/data

# Snapshot
$ zfs snapshot tank/data@backup_20260710

Selection Decision Matrix

ScenarioRecommended FSReason
General server (root partition)ext4Mature and stable
Database (MySQL/PG)xfsGood large file performance
Large file storage (video/logs)xfsHigh throughput
Container storageext4 / xfsOverlayFS compatible
NAS / file serverzfsSnapshots + checksums + compression
Backup archivebtrfsCompression + snapshots
Virtualization hostzfs / xfsSnapshots / high performance
Database backup snapshotsbtrfs / zfsSnapshot capability
Temporary dataext4 (nobarrier)Performance priority

Mount Parameter Optimization

ext4 Mount Options

# Production recommended
$ mount -o defaults,noatime,nodiratime,data=ordered,barrier=1,errors=remount-ro /dev/sdb1 /data

# Performance priority (not recommended for databases)
$ mount -o noatime,nodiratime,data=writeback,barrier=0 /dev/sdb1 /data

# /etc/fstab
/dev/sdb1  /data  ext4  noatime,nodiratime,data=ordered,barrier=1  0 2
ParameterDescriptionPerformance ImpactSafety
noatimeDo not update file access timeReduces I/ONo impact
nodiratimeDo not update directory access timeReduces I/ONo impact
relatimeUpdate atime only if older than mtime (default)CompromiseNo impact
data=orderedWrite data before journal (default)MediumHigh
data=writebackJournal only metadata, data may be out of orderHighLow
data=journalJournal both data and metadataLowHighest
barrier=1Enable write barriers (default)Slightly lowerHigh
barrier=0Disable write barriersHighLow (data loss on power failure)

Database scenario: MySQL/PostgreSQL recommends data=ordered,barrier=1,noatime. data=writeback is faster but may cause database corruption on power failure.

xfs Mount Options

# Production recommended
$ mount -o noatime,nodiratime,largeio,inode64,swalloc /dev/sdb1 /data

# Large file storage optimization
$ mount -o noatime,nodiratime,largeio,inode64,allocsize=512m /dev/sdb1 /data

# /etc/fstab
/dev/sdb1  /data  xfs  noatime,nodiratime,inode64,allocsize=512m  0 2
ParameterDescription
largeioUse large I/O block sizes
inode64Allow inode allocation above 32GB
allocsizePreallocation size (suitable for large files)
swallocSwitch to stripe-aligned allocation when file exceeds stripe size
nobarrierDisable write barriers (not recommended)

btrfs Mount Options

# General recommendation
$ mount -o noatime,compress=zstd:3,space_cache=v2,autodefrag /dev/sdb1 /data

# SSD optimization
$ mount -o noatime,compress=zstd:3,ssd,discard=async,space_cache=v2 /dev/sdb1 /data
ParameterDescription
compress=zstd:3zstd compression, level 3 (range 1-15, default 3)
space_cache=v2Use v2 space cache (better performance)
autodefragAutomatic defragmentation
ssdSSD optimization
discard=asyncAsynchronous TRIM

atime Performance Impact

# Test atime enabled vs disabled
$ mount /dev/sdb1 /mnt/test -o defaults
$ time find /mnt/test -type f -exec cat {} \; > /dev/null
# Each file read triggers a metadata write (atime update)

$ mount /dev/sdb1 /mnt/test -o remount,noatime
$ time find /mnt/test -type f -exec cat {} \; > /dev/null
# 20-50% I/O reduction

I/O Scheduler Selection

Scheduler Types

SchedulerDescriptionUse Case
noneNo scheduling, direct dispatchNVMe SSD (default)
mq-deadlineMulti-queue deadlineSSD / general
bfqFair bandwidth allocationDesktop / interactive
kyberAdaptive schedulingNVMe SSD
# View available schedulers
$ cat /sys/block/sdb/queue/scheduler
[mq-deadline] kyber bfq none

# Switch scheduler
$ echo none > /sys/block/sdb/queue/scheduler

Scheduler Selection Recommendations

# NVMe SSD: none (no scheduling, reduce overhead)
$ echo none > /sys/block/nvme0n1/queue/scheduler

# SATA SSD: mq-deadline (prevent request starvation)
$ echo mq-deadline > /sys/block/sda/queue/scheduler

# HDD: mq-deadline or bfq
$ echo mq-deadline > /sys/block/sdb/queue/scheduler

# Container host (mixed workload): bfq (fair allocation)
$ echo bfq > /sys/block/sdb/queue/scheduler

I/O Scheduler Parameters

# mq-deadline parameters
$ ls /sys/block/sdb/queue/iosched/
fifo_batch  read_expire  write_expire  writes_starved  front_merges

# read_expire: read request expiration (ms), default 500
# write_expire: write request expiration (ms), default 5000
# writes_starved: read priority count (process N reads before 1 write), default 2
# fifo_batch: batch size, default 16

Queue Depth

# View queue depth
$ cat /sys/block/sdb/queue/nr_requests
256

# Increase queue depth (high-concurrency scenarios)
$ echo 512 > /sys/block/sdb/queue/nr_requests

# View/set device queue depth
$ cat /sys/block/sdb/device/queue_depth
64

Journal Modes

ext4 Three Journal Modes

data=ordered (default):
  1. Write data to filesystem
  2. Wait for data to hit disk
  3. Write metadata to journal
  4. Journal commit

data=writeback:
  1. Write metadata to journal
  2. Journal commit
  3. Data written asynchronously (may be out of order)
  → Fastest, but may lose "committed" data on power failure

data=journal:
  1. Write data to journal
  2. Write metadata to journal
  3. Journal commit
  4. Data written from journal to filesystem
  → Slowest, but highest data safety

Journal Selection by Scenario

ScenarioRecommended ModeReason
DatabaseorderedEnsures data is written before metadata
Log storagewritebackPerformance priority, loss is tolerable
/tmpwritebackTemporary data needs no protection
Root partitionorderedBalance performance and safety
Critical datajournalHighest safety (~50% performance penalty)
# Switch journal mode (requires remount)
$ mount -o remount,data=writeback /data

# Note: data=writeback must be enabled at mkfs time
$ tune2fs -O has_journal -E journal_default /dev/sdb1
$ tune2fs -o journal_data_writeback /dev/sdb1

xfs Journal Parameters

# Separate journal device (performance improvement)
$ mkfs.xfs -l logdev=/dev/sdc1,size=1024m /dev/sdb1
$ mount -o logdev=/dev/sdc1 /dev/sdb1 /data

# Journal size recommendations
# Minimum: 32MB or 0.5% of filesystem (whichever is larger)
# Recommended: 1GB journal per 1TB data

fsync Performance

The Cost of fsync

fsync() forces file data and metadata to be flushed to disk — a critical operation for database WAL (Write-Ahead Log). Each fsync triggers:

  1. Flush dirty pages to disk
  2. Flush journal to disk
  3. Wait for disk acknowledgment
# Test fsync performance
$ dd if=/dev/zero of=/data/test.tmp bs=4k count=1000 conv=fdatasync
# conv=fdatasync measures fdatasync latency

# Test fsync latency with fio
$ fio --name=fsync_test --filename=/data/test.tmp --rw=write --bs=4k \
      --fsync=1 --size=1G --runtime=60 --time_based

fsync Performance Across Filesystems

Filesystemfsync Latency (4K write)Notes
ext4 (ordered)~1-3msWaits for journal commit
ext4 (writeback)~0.5-1msLess metadata journaling
xfs~0.5-2msEfficient journal
btrfs~2-5msCoW + checksum
zfs (default)~1-3msZIL write
zfs (SLOG)~0.1-0.5msSLOG device acceleration

Reducing fsync Overhead

# ext4: use writeback mode (sacrifice consistency)
$ mount -o data=writeback /dev/sdb1 /data

# xfs: use external journal device
$ mount -o logdev=/dev/ssd/log /dev/sdb1 /data

# zfs: add SLOG device
$ zpool add tank log /dev/nvme0n1p1

# Application layer: batch writes with single fsync
# Databases typically already have this optimization (group commit)

Large Directory Optimization

Problems with Too Many Directory Entries

When a directory contains more than 100,000 files:

  • ls and readdir slow down
  • File creation/deletion slows down
  • ext4’s htree index may degrade
# View directory entry count
$ ls -1 /data/large_dir | wc -l

# ext4 directory index status
$ dumpe2fs /dev/sdb1 | grep "Directory hash"

ext4 Directory Index Optimization

# Enable dir_index (enabled by default)
$ tune2fs -O dir_index /dev/sdb1

# Rebuild index for existing directories
$ e2fsck -D /dev/sdb1

Directory Sharding Strategy

# Hash-based sharding (common in CDN cache, image storage)
# /data/ab/cd/ef/abcdef123456.jpg

# Date-based directories
# /data/2026/07/10/logfile.txt

# Business prefix
# /data/orders/2026/07/order_12345.json

xfs Large Directory Optimization

# Increase inode size at mkfs (default 256 bytes)
$ mkfs.xfs -i size=512 /dev/sdb1
# Larger inodes support more extended attributes, reducing inline data overflow

# Specify allocsize at mount
$ mount -o allocsize=64m /dev/sdb1 /data

btrfs Large Directory Optimization

# btrfs uses B-tree index, large directory performance is better than ext4
# But sharding is still recommended to reduce snapshot overhead

# Enable directory metadata readahead
$ mount -o readdirsize=64k /dev/sdb1 /data

SSD/TRIM Optimization

Purpose of TRIM

SSDs need to notify the controller to erase blocks after file deletion (otherwise writes require erase first, causing write amplification). The TRIM command serves this purpose.

TRIM Configuration Methods

# Method 1: Continuous TRIM (real-time, sends TRIM on every delete)
# Add discard mount option
$ mount -o discard /dev/sdb1 /data

# Method 2: Periodic TRIM (recommended)
$ systemctl enable --now fstrim.timer
$ systemctl status fstrim.timer
# Runs fstrim weekly by default

# Manual TRIM
$ fstrim -v /data
# /data: 1234567890 bytes trimmed

# Check device TRIM support
$ lsblk -D
NAME  DISC-GRAN DISC-MAX DISC-ZERO
sdb   512B      2G       0  # TRIM supported

Continuous vs Periodic TRIM

MethodAdvantagesDisadvantagesRecommended
discard (continuous)Real-time releaseAdds latency to delete operationsNot recommended
fstrim.timer (periodic)No real-time overheadBrief delay in releaseRecommended

On NVMe SSDs, continuous TRIM overhead is low; using discard is viable. For SATA SSDs, periodic TRIM is recommended.

Other SSD Optimizations

# I/O scheduler: none or mq-deadline
$ echo none > /sys/block/nvme0n1/queue/scheduler

# Disable readahead (SSD random read is fast, no readahead needed)
$ echo 0 > /sys/block/nvme0n1/queue/read_ahead_kb

# Block size alignment
$ cat /sys/block/nvme0n1/queue/physical_block_size
4096

# Check if SSD is NVMe
$ lsblk -d -o NAME,ROTA,TRAN
NAME ROTA TRAN
sda     0 sata
nvme0n1 0 nvme

Real-World Cases

Case 1: MySQL Database Filesystem Selection

Environment: MySQL 8.0, 2TB data, NVMe SSD

Requirements: High fsync performance, large file support, online growth

Solution: xfs + noatime + mq-deadline

# 1. Create xfs
$ mkfs.xfs -f -L mysql_data -d agcount=32 /dev/nvme0n1p2

# 2. Mount
$ mount -o noatime,nodiratime,inode64,largeio /dev/nvme0n1p2 /var/lib/mysql

# 3. Scheduler
$ echo none > /sys/block/nvme0n1/queue/scheduler

# 4. Preallocate space
$ fallocate -l 2T /var/lib/mysql/ibdata1

Case 2: Log Storage Compression Optimization

Environment: 10TB log data, HDD

Requirements: Reduce disk usage, retain fast query capability

Solution: btrfs + zstd compression

# 1. Create btrfs
$ mkfs.btrfs -L logs -d single /dev/sdb

# 2. Mount with compression
$ mount -o compress=zstd:9,noatime,space_cache=v2 /dev/sdb /var/log/archive

# 3. Check compression results
$ btrfs filesystem df /var/log/archive
$ btrfs filesystem usage /var/log/archive
# Compression ratio typically 3:1 to 5:1

Case 3: zfs Self-Healing Data Protection

Environment: Backup server, 4 × 8TB HDDs

Requirements: Data integrity protection, automatic snapshots

Solution: zfs RAID-Z1 + lz4 compression + automatic snapshots

# 1. Create RAID-Z1 pool (tolerates 1 disk failure)
$ zpool create -f tank raidz /dev/sd{b,c,d,e}

# 2. Enable compression
$ zfs set compression=lz4 tank
$ zfs set atime=off tank

# 3. Create dataset
$ zfs create tank/backups

# 4. Set snapshot retention policy
$ zfs set com.sun:auto-snapshot=true tank/backups
$ zfs set com.sun:auto-snapshot:daily=true tank/backups

# 5. Check data integrity
$ zpool scrub tank
$ zpool status tank

Case 4: Container Image Layer Storage Optimization

Environment: Docker host, OverlayFS + ext4

Problem: Poor container I/O performance, massive small file read/write

Solution:

# 1. Use ext4 as underlying filesystem
$ mkfs.ext4 -L docker /dev/nvme0n1p3
$ mount -o noatime,nodiratime,data=ordered /dev/nvme0n1p3 /var/lib/docker

# 2. Docker overlay2 storage driver configuration
# /etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "data-root": "/var/lib/docker"
}

# 3. Scheduler: none (NVMe)
$ echo none > /sys/block/nvme0n1/queue/scheduler

# 4. Increase queue depth
$ echo 1024 > /sys/block/nvme0n1/queue/nr_requests

Filesystem Check and Repair

ext4

# Check filesystem (read-only)
$ e2fsck -n /dev/sdb1

# Auto-repair
$ e2fsck -p /dev/sdb1

# Force check (interactive)
$ e2fsck -f /dev/sdb1

# Force check and attempt recovery
$ e2fsck -fy /dev/sdb1

# View filesystem information
$ dumpe2fs -h /dev/sdb1

xfs

# Check (read-only)
$ xfs_db -c "check" /dev/sdb1

# Repair
$ xfs_repair /dev/sdb1

# Force repair (may cause data loss)
$ xfs_repair -L /dev/sdb1  # Clear log, dangerous!

# View filesystem information
$ xfs_info /dev/sdb1

btrfs

# Check
$ btrfs device stats /data
$ btrfs filesystem show /data

# Scan and repair
$ btrfs scrub start /data
$ btrfs scrub status /data

# Severe corruption
$ btrfs check --repair /dev/sdb1  # Use with caution

Summary

Filesystem selection and optimization is the foundation of storage performance. Key takeaways:

  1. ext4 for general use: Stable and reliable, the safest default. First choice for root partitions and small servers.
  2. xfs for large files and high I/O: First choice for databases, video storage, virtualization images. Default filesystem for RHEL family.
  3. btrfs for snapshots and compression: Backups, log archives, development environments. But RAID5/6 is not recommended for production.
  4. zfs for enterprise storage: NAS and backup servers with high data integrity requirements. But high memory needs and license controversy.
  5. noatime mount option is a universal optimization: Should be enabled in nearly all scenarios.
  6. I/O scheduler varies by storage medium: NVMe uses none, SSD uses mq-deadline, HDD uses mq-deadline or bfq.
  7. fsync performance is critical for databases: ext4 uses ordered mode, zfs adds SLOG device.
  8. SSDs must configure TRIM: Prefer periodic TRIM via fstrim.timer.
  9. Large directories must be sharded: Directories with more than 100,000 files need hash or date-based bucketing.

Ultimate principle of filesystem tuning: reliability over performance. Any optimization that may cause data loss (like barrier=0, data=writeback) must be used only after thorough risk assessment.