Jump to content

RAID storage performance tuning

From Pulsed Media Wiki


On a striped array, sequential throughput is set almost entirely by three things working together: the RAID chunk size, the array's stripe cache, and the filesystem's stripe alignment. Get all three right and a wide array delivers close to the sum of its disks' sequential bandwidth. Get any one wrong — most often a chunk that is too small, or an ext4 layout that does not match the chunk — and most of that bandwidth stays on the floor while the CPU burns cycles on read-modify-write.

This page covers the settings; for the RAID levels themselves see RAID and Basic usage of Software Raid (MDADM).

Why chunk size dominates

The chunk size is how much contiguous data md writes to one disk before moving to the next. It sets the width of a full stripe: on an N-disk array a stripe is chunk × (number of data disks).

A chunk that is too small fragments every large sequential write into many small per-disk operations, so command and seek overhead dominates and no disk reaches its streaming transfer rate. A separate hazard on parity arrays (RAID 5/6) is read-modify-write: any write that does not cover a whole stripe forces md to read the old data and parity back, recompute parity, and write it. You avoid that by getting writes to land as full stripes — which is what a sensibly sized chunk plus a large stripe cache (below) makes possible, letting big sequential I/O batch into whole-stripe writes. That is the case where a parity array runs at close to full speed. (The opposite extreme has its own cost: an over-large chunk makes small or random writes more likely to touch only part of a stripe, so pick the chunk for the workload — large for streaming big files, smaller where small random I/O dominates.)

For storage-heavy, large-file workloads (media, backups, seedbox data) a 2 MB (2048 KiB) chunk is a strong default:

mdadm --create /dev/md0 --level=5 --raid-devices=6 --chunk=2048 /dev/sd[a-f]

The chunk is fixed at array-create time. Changing it later means rebuilding the array, so it is worth getting right up front.

Stripe cache

The md stripe cache holds stripes in memory while parity is computed, which lets the array batch writes into whole stripes instead of doing read-modify-write per operation. On a parity array under sequential load, raising it to the maximum is one of the cheapest wins available:

echo 32768 > /sys/block/md0/md/stripe_cache_size

This value resets to a small default on every reboot, so persist it (for example from /etc/rc.local or a systemd unit). Its only cost is a bounded amount of RAM per array.

ext4 alignment must match the chunk

A filesystem that does not know the array geometry will scatter its writes across stripe boundaries and defeat everything above. ext4 takes two parameters, both derived from the RAID chunk and the filesystem block size (4 KiB on modern disks):

  • stride = chunk ÷ filesystem block size. For a 2048 KiB chunk and 4 KiB blocks: 2048 ÷ 4 = 512.
  • stripe-width = stride × number of data disks. For a 6-disk RAID 5 (5 data disks): 512 × 5 = 2560.
mkfs.ext4 -b 4096 -E stride=512,stripe-width=2560 /dev/md0

Recompute both whenever the chunk or the disk count changes — a stride computed for one chunk size is wrong for another, and a wrong stride silently costs throughput.

Mount options

For a large-file data array, mount with:

noatime,commit=60

noatime stops a metadata write on every read, and a longer commit interval batches journal flushes. Where per-user quota is required, journaled quota (jqfmt=vfsv1 with the user/group quota files) is preferred over the plain in-inode quota for a shared array. Quota mount options alone do not enable quota — the quota accounting files still have to be created and turned on after the filesystem is mounted.

The rule of thumb

Throughput on a striped array is a product, not a sum: chunk size × stripe geometry × filesystem alignment. All three have to agree. A large chunk with a mis-aligned filesystem is as slow as a small chunk; a perfectly aligned filesystem on a tiny chunk still can't feed the disks. Set the chunk for your workload, max the stripe cache and persist it, align ext4 to the chunk, and verify the result with a real sequential benchmark rather than trusting the defaults.

In Pulsed Media hosting

Pulsed Media runs large parity arrays across its seedbox and storage-box fleet, where sequential throughput on big files is what customers actually feel. The tuning above — a wide chunk, a maxed and persisted stripe cache, and ext4 aligned to the chunk — is the standard applied to those arrays, and it is the difference between an array that reads at the pace of its disks and one that reads at the pace of one.

See also