I wanted to actually understand RAID instead of memorizing a comparison table. So I launched an Ubuntu 24.04 EC2 instance, attached a few spare EBS volumes, and built every common RAID level by hand with mdadm: RAID 0, 1, 5, 6, and 10. For each one I created the array, wrote a file, deliberately failed a disk to watch what happens, then rebuilt it.
This article is the full walkthrough, in plain language, and it has three parts:
- The concepts — what RAID and an "array" actually are, and the storage vocabulary around them.
- The hands-on build — every level with the real commands and real output, including the warnings you'll hit.
- Production reality — the errors you face on live systems and exactly how to handle each one.
Because this runs entirely on AWS, I've kept EC2 and EBS specifics inline where they matter. If you've ever been confused about why RAID 5 and RAID 10 give different usable space, or frozen at the word "degraded" in a 2 AM alert, this is for you.
1. The concepts
What is RAID, in one minute?
RAID stands for Redundant Array of Independent Disks. You combine several physical disks into one logical disk. Depending on how you combine them, you trade between three things:
- Speed — spread data across disks so reads and writes happen in parallel.
- Redundancy — keep extra copies or parity so you survive a disk dying.
- Capacity — how much usable space you actually get.
You cannot maximize all three at once. Each RAID "level" is a different compromise. That is the entire concept.
The Linux tool for software RAID is mdadm (multiple-device admin). It builds a virtual device like /dev/md0 out of your real disks.
What is an "array"?
You will see the word array on nearly every line below, so let's define it clearly.
An array is the single logical disk that RAID creates out of your group of physical disks. "Array" just means "an organized collection" — here, a collection of disks organized to behave as one.
The flow:
- You have 4 separate physical disks:
nvme1n1,nvme2n1,nvme3n1,nvme4n1. - You tell
mdadmto combine them. -
mdadmhands you back one new device:/dev/md0.
That /dev/md0 is the array. Your OS treats it like a normal single disk — you put a filesystem on it, mount it, read and write files — with no idea there are really 4 disks underneath. mdadm quietly handles spreading data (and copies or parity) across the members.
Vocabulary for the rest of the article:
-
Member / component disk — one real physical disk in the group (
nvme1n1, etc.). -
Array — the combined virtual disk (
/dev/md0) those members form. - RAID level — the rule for how data is spread across the members (stripe, mirror, parity).
So "create the array" means "fuse these physical disks into one /dev/md0," and "the array is degraded" means "the combined disk still works, but one member is missing."
Do I need to partition the disks first (fdisk)?
Short answer: no, not for this. You will notice there is no fdisk anywhere below — the arrays are built directly on whole raw disks.
A partition divides one physical disk into separate logical sections, each acting like its own disk. Tools like fdisk, parted, or gdisk write a small partition table at the start of the disk recording where each section begins and ends. You partition when you need:
- separate areas for different purposes (OS vs data vs swap) on one disk,
- boot-specific partitions (the EFI System Partition,
/boot/efi), - or multiple filesystems / operating systems on one disk.
But a filesystem or a RAID array can live directly on a whole raw disk — /dev/nvme1n1 and /dev/nvme1n1p1 (its first partition) are both just block devices. On EC2, each EBS volume is already a clean, single-purpose disk, so using whole disks is simplest and common. (On physical servers, some admins still partition first so a slightly-smaller replacement drive from a different vendor still fits the array.)
The three layers, kept straight:
-
fdisk= divide a disk into sections (optional). -
mkfs= put a filesystem on a section, a whole disk, or a RAID array. -
mdadm= combine disks into a RAID array.
A word on filesystems: ext4 vs xfs
A RAID array is just a block device — it needs a filesystem before it can hold files. The two common choices:
| ext4 | xfs | |
|---|---|---|
| Best at | General purpose, small-to-medium files | Large files, high parallelism, big filesystems |
| Can grow? | Yes | Yes (online only) |
| Can shrink? | Yes | No — grow only |
| Default on | Debian, Ubuntu | RHEL, Fedora, Amazon Linux 2 |
Grow means enlarging a filesystem to use more space (common and safe — e.g. after expanding an EBS volume you still run resize2fs or xfs_growfs to make the new space usable). Shrink means reducing it to free space (rarer, riskier, and xfs cannot do it at all).
⚠️
mkfsalways erases. Reformatting a device — even to switch from ext4 to xfs — destroys everything on it. There is no in-place ext4-to-xfs conversion. To change filesystem type without losing data: copy the data off,mkfsthe new type, then copy it back. In this lab, reformatting between RAID levels was fine only because the test files were disposable.
I used ext4 throughout. For most workloads either is fine; pick xfs for very large files and heavy concurrency, ext4 for everything else or when you might need to shrink.
2. The hands-on build
The setup
On EC2 I used:
- 1 × 30 GiB root volume — the OS. Never touched.
- 4 × 10 GiB EBS volumes — the RAID practice disks.
Modern EC2 instances present disks as NVMe devices. Here is lsblk:
nvme0n1 30G disk <- ROOT. Has /, /boot. NEVER touch.
├─nvme0n1p1 29G part /
├─nvme0n1p15 106M part /boot/efi
└─nvme0n1p16 913M part /boot
nvme1n1 10G disk <- RAID disk
nvme2n1 10G disk <- RAID disk
nvme3n1 10G disk <- RAID disk
nvme4n1 10G disk <- RAID disk
The one rule: the root disk is the one with partitions mounted at /. Every RAID command below names only nvme1n1–nvme4n1. If you ever see nvme0n1 in a format or RAID command, stop.
Install the tool and learn two status commands:
sudo apt update && sudo apt install -y mdadm
-
cat /proc/mdstat— quick live status of all arrays. -
sudo mdadm --detail /dev/md0— full report on one array.
How to read the status symbols (this is 90% of understanding mdadm):
-
[UU],[UUUU]— eachUis a disk that is Up. An_is down or missing. -
[2/2],[4/3]— (disks expected / disks present). -
(F)beside a disk — it is marked Failed. -
resync— syncing a freshly created array. -
recovery— rebuilding a disk you re-added after a failure. -
State : clean= healthy;State : degraded= running but missing a disk.
RAID 0 — Striping (speed, zero safety)
RAID 0 splits data across disks in chunks — a write goes to disk 1, then disk 2, then disk 1, so both work at once. Fast. But no redundancy: lose either disk and everything is gone.
Capacity: full sum of all disks. 2 × 10 GiB = ~20 GiB.
sudo mdadm --create --verbose /dev/md0 --level=0 --raid-devices=2 /dev/nvme1n1 /dev/nvme2n1
cat /proc/mdstat
md0 : active raid0 nvme2n1[1] nvme1n1[0]
20953088 blocks super 1.2 512k chunks
512k chunks is the stripe size (how much data lands on one disk before moving to the next). 20953088 blocks ≈ 20 GiB — full capacity, no redundancy overhead.
Add a filesystem and mount it:
sudo mkfs.ext4 /dev/md0
sudo mkdir -p /mnt/raid0
sudo mount /dev/md0 /mnt/raid0
echo "RAID 0 test" | sudo tee /mnt/raid0/test.txt
There is no failure drill for RAID 0 — if a disk dies, the array is simply gone. That is the point.
Teardown (frees the disks for the next level):
sudo umount /mnt/raid0
sudo mdadm --stop /dev/md0
sudo mdadm --zero-superblock /dev/nvme1n1 /dev/nvme2n1
The superblock is metadata mdadm writes on each disk saying "I belong to array X." --zero-superblock wipes it so the disk looks fresh. Skip this and mdadm may re-assemble a ghost array later.
RAID 1 — Mirroring (safety, half the space)
RAID 1 writes identical data to both disks — twins. One dies, the other has a complete copy.
Capacity: one disk's worth. 2 × 10 GiB = ~10 GiB. The 50% mirror tax.
sudo mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/nvme1n1 /dev/nvme2n1
First scary-but-harmless message:
mdadm: Note: this array has metadata at the start and
may not be suitable as a boot device. ...
Continue creating array? y
It is only saying this array cannot boot an OS (its metadata sits at the disk start). We use it for data, so type y.
cat /proc/mdstat
md0 : active raid1 nvme2n1[1] nvme1n1[0]
10476544 blocks super 1.2 [2/2] [UU]
[==>..................] resync = 11.8% (1241088/10476544) ...
That progress bar is a resync — mdadm copying disk 1 onto disk 2 so they start identical. The array is usable immediately; you do not wait. [UU] = both up. 10476544 ≈ 10 GiB — half of RAID 0.
sudo mkfs.ext4 /dev/md0
sudo mkdir -p /mnt/raid1 && sudo mount /dev/md0 /mnt/raid1
echo "RAID 1 test" | sudo tee /mnt/raid1/test.txt
Warning you'll see:
mkfs.ext4says/dev/md0 contains a ext4 file system ... Proceed anyway? (y,N). That is leftover from the RAID 0 filesystem on these reused disks. Typeyto overwrite.
The failure drill:
sudo mdadm /dev/md0 --fail /dev/nvme2n1
cat /proc/mdstat
md0 : active raid1 nvme2n1[1](F) nvme1n1[0]
10476544 blocks super 1.2 [2/1] [U_]
(F) failed, [2/1] one of two present, [U_] one up and one down → degraded. But:
cat /mnt/raid1/test.txt # RAID 1 test <- still works
A disk "died" and you lost nothing. Rebuild it:
sudo mdadm /dev/md0 --remove /dev/nvme2n1
sudo mdadm --zero-superblock /dev/nvme2n1
sudo mdadm /dev/md0 --add /dev/nvme2n1
cat /proc/mdstat # now shows: recovery = X%
recovery = re-mirroring onto the fresh disk. Back to [UU] when done. Then tear down.
RAID 5 — Striping + distributed parity (the popular one)
RAID 5 stripes like RAID 0 (fast) and computes parity (a checksum) spread across all disks. Lose one disk and mdadm recalculates its data from parity plus the survivors. There is no full copy — it is math.
Capacity: you lose exactly one disk's worth to parity, regardless of disk count. 3 × 10 GiB = ~20 GiB. Cheap redundancy (1/N overhead). Minimum 3 disks.
sudo mdadm --create --verbose /dev/md0 --level=5 --raid-devices=3 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
cat /proc/mdstat
md0 : active raid5 nvme3n1[3] nvme2n1[1] nvme1n1[0]
20953088 blocks ... level 5, 512k chunk, algorithm 2 [3/2] [UU_]
[=>...................] recovery = 8.4% ...
Looks broken but isn't. [3/2] [UU_] plus "recovery" on a brand-new array (and mdadm --detail showing spare rebuilding) is a deliberate optimization: mdadm builds the last disk via recovery instead of syncing all three from scratch. It ends at [3/3] [UUU]. Don't panic.
~20 GiB from three disks → one disk's worth went to parity.
sudo mkfs.ext4 /dev/md0
sudo mkdir -p /mnt/raid5 && sudo mount /dev/md0 /mnt/raid5
echo "RAID 5 test" | sudo tee /mnt/raid5/test.txt
Failure drill:
sudo mdadm /dev/md0 --fail /dev/nvme3n1
cat /mnt/raid5/test.txt # RAID 5 test <- reconstructed from parity, live
There is no copy of the dead disk anywhere — every read rebuilds the missing bytes from parity plus the survivors on the fly.
Hard limit: RAID 5 survives exactly ONE failure. A second disk down = total loss. That limitation is why RAID 6 exists. Rebuild with the same remove → zero → add pattern, then tear down all three disks.
RAID 6 — Double parity (survive TWO failures)
RAID 6 is RAID 5 with a second, independent parity block. Any two disks can die at once and you lose nothing. Critical for large arrays, where a second disk often fails during the stressful rebuild of the first.
Capacity: lose two disks' worth. 4 × 10 GiB = ~20 GiB. Minimum 4 disks.
sudo mdadm --create --verbose /dev/md0 --level=6 --raid-devices=4 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
# then mkfs.ext4, mount at /mnt/raid6, write test.txt
The headline drill — fail two at once:
sudo mdadm /dev/md0 --fail /dev/nvme3n1 --fail /dev/nvme4n1
cat /mnt/raid6/test.txt # RAID 6 test <- still works!
cat /proc/mdstat
md0 : active raid6 nvme4n1[3](F) nvme3n1[2](F) nvme2n1[1] nvme1n1[0]
... [4/2] [UU__]
Two disks dead at once, data survives. RAID 5 would be destroyed by this. Rebuild both:
sudo mdadm /dev/md0 --remove /dev/nvme3n1
sudo mdadm /dev/md0 --remove /dev/nvme4n1
sudo mdadm --zero-superblock /dev/nvme3n1 /dev/nvme4n1
sudo mdadm /dev/md0 --add /dev/nvme3n1
sudo mdadm /dev/md0 --add /dev/nvme4n1
RAID 10 — Mirror + Stripe (speed and safety)
RAID 10 is "nested": it makes mirror pairs, then stripes across them. You get RAID 1's safety and RAID 0's speed, with fast rebuilds (copy from the mirror partner — no parity math). The database workhorse.
Capacity: half to mirroring. 4 × 10 GiB = ~20 GiB.
sudo mdadm --create --verbose /dev/md0 --level=10 --raid-devices=4 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
cat /proc/mdstat
md0 : active raid10 nvme4n1[3] nvme3n1[2] nvme2n1[1] nvme1n1[0]
20953088 blocks ... 512K chunks 2 near-copies [4/4] [UUUU]
2 near-copies = every block lives on 2 disks (the mirror part).
The crucial detail — which disks are partners?
sudo mdadm --detail /dev/md0
0 ... active sync set-A /dev/nvme1n1
1 ... active sync set-B /dev/nvme2n1
2 ... active sync set-A /dev/nvme3n1
3 ... active sync set-B /dev/nvme4n1
The set-A / set-B labels are the mirror groups:
-
set-A:
nvme1n1⇄nvme3n1 -
set-B:
nvme2n1⇄nvme4n1
Notice the pairing is not 1+2 / 3+4 as you might guess. Always read the set- labels. This controls survival:
- Lose 1 disk → always fine.
- Lose 2 in different sets → fine.
- Lose 2 in the same set → array dead.
RAID 6 survives any 2; RAID 10's two-disk survival is conditional — but it rebuilds much faster.
Failure drill:
sudo mdadm /dev/md0 --fail /dev/nvme3n1
cat /proc/mdstat # [4/3] [UU_U]
cat /mnt/raid10/test.txt # still works — partner nvme1n1 serves it
sudo mdadm /dev/md0 --remove /dev/nvme3n1
sudo mdadm --zero-superblock /dev/nvme3n1
sudo mdadm /dev/md0 --add /dev/nvme3n1
Rebuild speed is noticeably faster (~159 MB/s in my run) — a straight copy from the partner, no parity to compute.
3. Production reality — what actually goes wrong, and how to handle it
The lab is clean. Production is not. Here are the failures you will genuinely face on live systems, roughly in order of how often they bite.
1. A disk fails for real (the everyday case)
What you see: an email from mdadm --monitor, or cat /proc/mdstat shows [U_UU] and State : degraded. The array still serves data.
How to handle:
- Don't panic — degraded means working but no safety margin. Reads and writes continue.
- Identify the dead disk and its serial so you pull the right one from the chassis:
sudo mdadm --detail /dev/md0
sudo smartctl -a /dev/sdX | grep -i serial
- Fail + remove it (if not already failed), swap the physical disk, then add the new one:
sudo mdadm /dev/md0 --fail /dev/sdX --remove /dev/sdX
# physically replace the drive, then:
sudo mdadm /dev/md0 --add /dev/sdY
watch cat /proc/mdstat # watch recovery climb to [UUUU]
- Pull the correct disk. Yanking a healthy disk from a degraded RAID 5 destroys the array. Match by serial number every time.
Set up alerting so you even know a disk died:
sudo nano /etc/mdadm/mdadm.conf # add: MAILADDR you@example.com
sudo mdadm --monitor --scan --daemon
Without monitoring, a disk can sit failed for weeks until a second one dies and takes everything. This is the number one cause of "RAID didn't save us" stories.
2. The rebuild kills a second disk (the RAID 5 killer)
What happens: a disk fails, you add a replacement, and during the long, intense full-disk rebuild another aging disk hits a bad sector and drops out. On RAID 5 that is total array loss — the classic reason RAID 5 is discouraged for large modern drives.
How to prevent:
- Use RAID 6 (or RAID 10) for anything large or important — a second failure during rebuild is survivable.
- Run periodic scrubs to find bad sectors before a rebuild forces you to read them:
echo check | sudo tee /sys/block/md0/md/sync_action
cat /sys/block/md0/md/mismatch_cnt # 0 is good
- Keep backups. RAID is not a backup — it protects against disk failure, not deletion, corruption, ransomware, or a controller frying every disk at once.
3. "Array won't assemble after reboot"
What you see on boot: the array is missing, you're dropped to an emergency shell, or /mnt/data is empty.
Root cause: the array config was never persisted, so the OS does not know to assemble it. (In this lab we skipped that on purpose, which is exactly why nothing survived a reboot.)
The fix — do this for any array you want to keep:
# 1. Save the array definition
sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
# 2. Rebuild the initramfs so the array assembles early at boot
sudo update-initramfs -u # Debian/Ubuntu
# (RHEL/CentOS: sudo dracut -f)
# 3. Mount by UUID in /etc/fstab (never by /dev/md0 — numbers can change)
sudo blkid /dev/md0 # grab the UUID
# add to /etc/fstab:
# UUID=xxxx /mnt/data ext4 defaults,nofail 0 2
The nofail option matters: without it, a missing array can hang the entire boot. With it, the box boots and you fix the array live.
If it is already not assembling:
sudo mdadm --assemble --scan # try auto-assemble
sudo mdadm --assemble /dev/md0 /dev/sd[a-d] # or name the members
sudo mdadm --examine /dev/sdX # inspect each disk's superblock
4. The device name changed (/dev/md0 became /dev/md127)
What you see: the array shows up as /dev/md127 instead of /dev/md0, and fstab entries pointing at /dev/md0 fail.
Why: when mdadm cannot match the array to a config entry, it auto-numbers from 127 downward. Almost always means mdadm.conf is missing or stale.
Fix: regenerate the config (step 3 above) with the correct ARRAY line, run update-initramfs -u, and always mount by UUID, not device name.
5. Split brain after a crash (Events counter mismatch)
What you see: --assemble refuses because the disks disagree about who has the newest data. mdadm --examine /dev/sdX shows different Events counts per disk.
How to handle:
- Assemble using the disks with the highest, matching Events count:
sudo mdadm --examine /dev/sd[a-d] | grep -E 'Events|/dev'
sudo mdadm --assemble /dev/md0 /dev/sda /dev/sdb /dev/sdc # the good ones
- As a last resort,
--assemble --forcelets a slightly-behind disk rejoin. Use it only when you understand the risk — it can resurrect stale data. Take addimage of each disk first if the data matters.
6. Silent data corruption / bit rot
What you see: files are subtly wrong, or a scrub reports a non-zero mismatch_cnt.
How to handle: mdadm RAID does not checksum data, so it cannot detect silent corruption on its own — it only knows a disk is present or gone. Scrubs catch mismatches between data and parity but can't always say which side is right. For true bit-rot protection, use a checksumming filesystem (ZFS or Btrfs) on top of, or instead of, mdadm.
7. Rebuild is crawling and tanking performance
What you see: a rebuild says finish=600min and your app is slow because disk I/O is saturated.
How to handle — tune the rebuild rate:
cat /proc/sys/dev/raid/speed_limit_min # current limits (KB/s)
cat /proc/sys/dev/raid/speed_limit_max
echo 200000 | sudo tee /proc/sys/dev/raid/speed_limit_min # speed up in a window
echo 10000 | sudo tee /proc/sys/dev/raid/speed_limit_min # throttle for production
Trade-off: faster rebuild means less time at risk but heavier I/O load. On a busy box, throttle during peak and open it up at night.
8. Out of space on a "full capacity" array
What you see: writes fail with No space left on device sooner than expected.
Reminder: RAID 0/5/6 report combined capacity, but filesystem metadata, ext4's ~5% reserved blocks, and parity all eat into it. Plan for ~90–95% usable and alert well before 100% — a full filesystem can corrupt in-flight writes.
The lab warnings (harmless — don't confuse them with the above)
| Message | Meaning | Action |
|---|---|---|
may not be suitable as a boot device |
Can't boot an OS from this array; fine for data | Type y
|
contains a ext4 file system ... Proceed anyway? |
Leftover FS from reusing a disk | Type y
|
[3/2] [UU_] + spare rebuilding right after creating RAID 5 |
Normal creation optimization | Wait for [UUU]
|
No such file or directory on a test file |
You're looking at an unmounted mountpoint | Remount |
Cleanup (and stop the AWS bill)
sudo umount /mnt/raidXX
sudo mdadm --stop /dev/md0
sudo mdadm --zero-superblock /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1
cat /proc/mdstat # should say: unused devices: <none>
We never wrote /etc/fstab or mdadm.conf, so nothing persists across reboot — a clean slate.
On AWS: EBS volumes bill while they exist, attached or not. Detach the 4 extra volumes, delete them, and terminate the instance if you are done.
The one-page summary
| RAID | Min disks | Usable (4×10 GiB) | Survives | Overhead | Rebuild | Best for |
|---|---|---|---|---|---|---|
| 0 | 2 | 20 GiB | nothing | 0% | n/a (data lost) | scratch, max speed |
| 1 | 2 | 10 GiB | any 1 | 50% | copy from mirror | OS/boot, small critical data |
| 5 | 3 | 20 GiB (3 disks) | any 1 | ~33% | parity math (slow) | balanced general storage |
| 6 | 4 | 20 GiB | any 2 | 50% | parity math (slowest) | large arrays, long rebuilds |
| 10 | 4 | 20 GiB | 1 always; 2 if different sets | 50% | copy from partner (fast) | databases, write-heavy |
Mental model:
- Stripe (0) = speed, no safety.
- Mirror (1) = safety, half capacity.
- Parity (5/6) = safety at lower capacity cost; 6 adds a second parity for two-failure tolerance.
- 10 = mirror + stripe; speed and safety, 50% capacity, conditional two-disk survival.
Three production truths worth remembering:
- RAID is not a backup. It survives disk failure, not deletion, corruption, or human error.
- A degraded array with no monitoring is a time bomb — set up
mdadm --monitoron day one. - Persist the config (
mdadm.conf+update-initramfs+ fstab by UUID), or your array vanishes on the next reboot.
Wrapping up
Building each level by hand — and failing disks on purpose — made RAID click in a way no diagram ever did. But the real lesson is in Part 3: the lab always rebuilds cleanly, while production throws you degraded arrays at 2 AM, rebuilds that saturate I/O, and boxes that won't boot. Knowing the handful of commands above turns those from emergencies into routine.
AWS makes this experiment cheap: a small EC2 instance plus a few tiny EBS volumes for an hour costs almost nothing, and you tear it all down when you're done. Spin one up and try it yourself — then go set up monitoring on whatever RAID you already run.
Top comments (0)