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
| Feature | ext4 | xfs | btrfs | zfs |
|---|---|---|---|---|
| Max file size | 16TB | 8EB | 16EB | 16EB |
| Max volume | 1EB | 8EB | 16EB | 256ZB |
| Journaling | Yes | Yes | Yes (CoW) | Yes (CoW/ZIL) |
| Snapshots | No | No | Yes | Yes |
| Compression | No | No | Yes | Yes |
| Deduplication | No | No | Yes (experimental) | Yes |
| Checksums | No | No | Yes | Yes |
| Subvolumes | No | No | Yes | Yes |
| RAID | No | No | Yes | Yes (native) |
| Online grow | Yes | Yes | Yes | Yes |
| Online shrink | No | No | Yes | No |
| CoW | Partial | No | Yes | Yes |
| Developer | Linux community | SGI → Red Hat | Oracle | OpenZFS |
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
| Scenario | Recommended FS | Reason |
|---|---|---|
| General server (root partition) | ext4 | Mature and stable |
| Database (MySQL/PG) | xfs | Good large file performance |
| Large file storage (video/logs) | xfs | High throughput |
| Container storage | ext4 / xfs | OverlayFS compatible |
| NAS / file server | zfs | Snapshots + checksums + compression |
| Backup archive | btrfs | Compression + snapshots |
| Virtualization host | zfs / xfs | Snapshots / high performance |
| Database backup snapshots | btrfs / zfs | Snapshot capability |
| Temporary data | ext4 (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
| Parameter | Description | Performance Impact | Safety |
|---|---|---|---|
noatime | Do not update file access time | Reduces I/O | No impact |
nodiratime | Do not update directory access time | Reduces I/O | No impact |
relatime | Update atime only if older than mtime (default) | Compromise | No impact |
data=ordered | Write data before journal (default) | Medium | High |
data=writeback | Journal only metadata, data may be out of order | High | Low |
data=journal | Journal both data and metadata | Low | Highest |
barrier=1 | Enable write barriers (default) | Slightly lower | High |
barrier=0 | Disable write barriers | High | Low (data loss on power failure) |
Database scenario: MySQL/PostgreSQL recommends
data=ordered,barrier=1,noatime.data=writebackis 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
| Parameter | Description |
|---|---|
largeio | Use large I/O block sizes |
inode64 | Allow inode allocation above 32GB |
allocsize | Preallocation size (suitable for large files) |
swalloc | Switch to stripe-aligned allocation when file exceeds stripe size |
nobarrier | Disable 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
| Parameter | Description |
|---|---|
compress=zstd:3 | zstd compression, level 3 (range 1-15, default 3) |
space_cache=v2 | Use v2 space cache (better performance) |
autodefrag | Automatic defragmentation |
ssd | SSD optimization |
discard=async | Asynchronous 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
| Scheduler | Description | Use Case |
|---|---|---|
| none | No scheduling, direct dispatch | NVMe SSD (default) |
| mq-deadline | Multi-queue deadline | SSD / general |
| bfq | Fair bandwidth allocation | Desktop / interactive |
| kyber | Adaptive scheduling | NVMe 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
| Scenario | Recommended Mode | Reason |
|---|---|---|
| Database | ordered | Ensures data is written before metadata |
| Log storage | writeback | Performance priority, loss is tolerable |
| /tmp | writeback | Temporary data needs no protection |
| Root partition | ordered | Balance performance and safety |
| Critical data | journal | Highest 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:
- Flush dirty pages to disk
- Flush journal to disk
- 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
| Filesystem | fsync Latency (4K write) | Notes |
|---|---|---|
| ext4 (ordered) | ~1-3ms | Waits for journal commit |
| ext4 (writeback) | ~0.5-1ms | Less metadata journaling |
| xfs | ~0.5-2ms | Efficient journal |
| btrfs | ~2-5ms | CoW + checksum |
| zfs (default) | ~1-3ms | ZIL write |
| zfs (SLOG) | ~0.1-0.5ms | SLOG 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:
lsandreaddirslow 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
| Method | Advantages | Disadvantages | Recommended |
|---|---|---|---|
| discard (continuous) | Real-time release | Adds latency to delete operations | Not recommended |
| fstrim.timer (periodic) | No real-time overhead | Brief delay in release | Recommended |
On NVMe SSDs, continuous TRIM overhead is low; using
discardis 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:
- ext4 for general use: Stable and reliable, the safest default. First choice for root partitions and small servers.
- xfs for large files and high I/O: First choice for databases, video storage, virtualization images. Default filesystem for RHEL family.
- btrfs for snapshots and compression: Backups, log archives, development environments. But RAID5/6 is not recommended for production.
- zfs for enterprise storage: NAS and backup servers with high data integrity requirements. But high memory needs and license controversy.
noatimemount option is a universal optimization: Should be enabled in nearly all scenarios.- I/O scheduler varies by storage medium: NVMe uses none, SSD uses mq-deadline, HDD uses mq-deadline or bfq.
- fsync performance is critical for databases: ext4 uses ordered mode, zfs adds SLOG device.
- SSDs must configure TRIM: Prefer periodic TRIM via fstrim.timer.
- 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.