ОС · Persistence · 14 мин
RAID и надёжность хранения
Зачем объединять диски
Один диск — это компромисс. Он либо быстрый, либо ёмкий, либо надёжный, но редко всё сразу. А ещё он однажды умрёт: у любого диска есть MTBF (mean time between failures — среднее время до отказа), и для парка из сотен дисков «однажды» наступает каждую неделю.
RAID (Redundant Array of Independent Disks — избыточный массив независимых дисков) — это способ собрать несколько физических дисков в одно логическое устройство так, чтобы получить либо скорость, либо защиту от отказа, либо и то и другое. Снаружи операционная система и файловая система (см. /os/book/fs-implementation) видят один большой диск и не догадываются, что внутри их несколько.
Бытовая аналогия. Представь, что ты переписываешь важную книгу от руки:
- можно писать быстро, раздав главы нескольким переписчикам (но если один потеряет свою пачку — книга испорчена);
- можно сделать две одинаковые копии (надёжно, но бумаги уходит вдвое больше);
- можно вести контрольную тетрадь, по которой при потере одной главы её удастся восстановить (компромисс).
Это ровно RAID 0, RAID 1 и RAID 5. Разберём каждый.
┌─────────────────────────────────────┐
ОС / ФС │ видит ОДИН диск ёмкостью N байт │
└───────────────────┬─────────────────┘
│ (логический том)
┌─────────┴─────────┐
│ RAID-контроллер │ режет/копирует/считает parity
└─┬──────┬──────┬───┬┘
▼ ▼ ▼ ▼
[disk0][disk1][disk2][disk3] физические дискиЧто нарисовано: RAID — это прослойка между файловой системой и физическими
дисками. Вся «магия» (нарезка, копирование, контрольные суммы) живёт в
контроллере — железном или программном (mdadm в Linux, ZFS, LVM).
Три оси оценки: ёмкость, надёжность, скорость
Любой уровень RAID сравниваем по трём числам:
- Usable capacity (полезная ёмкость) — сколько байт реально доступно из суммарного объёма дисков. Остальное «съела» избыточность.
- Сколько отказов переживаем — сколько дисков может умереть, прежде чем мы потеряем данные.
- Скорость чтения/записи — во сколько раз быстрее (или медленнее) одного диска.
Дальше — про каждый уровень с этими тремя числами.
RAID 0 — striping (быстро, но хрупко)
Striping (страйпинг, «нарезка полосами») режет данные на блоки (страйпы) и раскладывает их по дискам по очереди. Чтение и запись идут параллельно на всех дисках сразу — отсюда скорость.
RAID 0 на 4 дисках. Блоки A,B,C,D,E,F,G,H... ложатся по кругу:
disk0 disk1 disk2 disk3
┌────┐ ┌────┐ ┌────┐ ┌────┐
страйп0 │ A │ │ B │ │ C │ │ D │
страйп1 │ E │ │ F │ │ G │ │ H │
страйп2 │ I │ │ J │ │ K │ │ L │
└────┘ └────┘ └────┘ └────┘
Чтение файла A..D = 4 диска работают ОДНОВРЕМЕННО → ~4x скорость.Что нарисовано: соседние блоки данных лежат на разных дисках, поэтому большой последовательный запрос обслуживают сразу все шпиндели/каналы.
Цена скорости — нулевая надёжность. Нет ни копии, ни контрольной суммы. Смерть любого диска уносит половину каждого файла → массив мёртв целиком.
disk1 умер ✗:
disk0 disk1 disk2 disk3
┌────┐ ┌╳╳╳╳┐ ┌────┐ ┌────┐
│ A │ │ ?? │ │ C │ │ D │ ← блок B потерян,
│ E │ │ ?? │ │ G │ │ H │ значит весь массив потерян
└────┘ └────┘ └────┘ └────┘- Usable capacity: 100% (N дисков × объём).
- Переживаем отказов: 0.
- Скорость: ~N× чтение и запись.
Где применяют: кэши, временные данные, видеомонтаж — там, где скорость важнее сохранности, а потеря не катастрофа.
RAID 1 — mirroring (надёжно, но дорого)
Mirroring (зеркалирование) хранит полную копию данных на втором диске. Пишем одно и то же в оба места; читать можно с любого (а значит, чтение можно распараллелить и ускорить).
RAID 1 (зеркало из 2 дисков): каждый блок продублирован
disk0 disk1
┌──────┐ ┌──────┐
│ A │ ═════ │ A │ копия
│ B │ ═════ │ B │
│ C │ ═════ │ C │
└──────┘ └──────┘
disk0 умер ✗ → читаем всё с disk1, данные целы:
┌╳╳╳╳╳╳┐ ┌──────┐
│ ?? │ │ A │ ← полный доступ
└──────┘ └──────┘Что нарисовано: данные существуют в двух экземплярах. Потеря одного диска не теряет ничего — второй продолжает обслуживать запросы, а после замены диска массив ресинкается (копирует всё обратно).
- Usable capacity: 50% (половина денег уходит на копию).
- Переживаем отказов: 1 (а при отказе обоих — данные потеряны).
- Скорость: чтение ~2×, запись ~1× (пишем в оба, ждём самый медленный).
RAID 5 — parity (золотая середина)
RAID 1 надёжен, но платить 50% ёмкости за зеркало дорого, когда дисков много. RAID 5 решает это через parity (чётность) — контрольный блок, вычисляемый операцией XOR по блокам данных одного страйпа.
XOR (исключающее ИЛИ) обладает волшебным свойством: если знаешь результат и все слагаемые кроме одного — пропавшее восстанавливается тем же XOR.
P = A XOR B XOR C
Потеряли B? → B = A XOR C XOR P (тот же XOR возвращает данные)
Маленький пример по битам:
A = 1011
B = 0110
C = 1100
P = A^B^C = 0001 ← храним P
Потеряли B: A^C^P = 1011^1100^0001 = 0110 = B ✓Что нарисовано: один лишний блок parity на страйп позволяет восстановить любой один потерянный блок данных этого страйпа. Не нужна целая копия — нужен один блок на группу.
В RAID 5 parity распределена (не лежит на одном выделенном диске, а размазана по кругу) — чтобы parity-диск не стал узким местом на запись.
RAID 5 на 4 дисках (3 данных + 1 parity на страйп, parity «гуляет»):
disk0 disk1 disk2 disk3
┌────┐ ┌────┐ ┌────┐ ┌─────┐
страйп0 │ A0 │ │ A1 │ │ A2 │ │ Pa │ Pa = A0^A1^A2
страйп1 │ B0 │ │ B1 │ │ Pb │ │ B2 │ Pb = B0^B1^B2
страйп2 │ C0 │ │ Pc │ │ C1 │ │ C2 │ Pc = C0^C1^C2
страйп3 │ Pd │ │ D0 │ │ D1 │ │ D2 │ Pd = D0^D1^D2
└────┘ └────┘ └────┘ └─────┘Восстановление после отказа диска:
disk2 умер ✗. Берём страйп0: знаем A0, A1, Pa → A2 = A0^A1^Pa.
Так перебираем КАЖДЫЙ страйп и заполняем новый диск.
disk0 disk1 [disk2 new] disk3
┌────┐ ┌────┐ ┌────────┐ ┌─────┐
страйп0 │ A0 │ │ A1 │ │A0^A1^Pa│ │ Pa │ → A2 восстановлен
└────┘ └────┘ └────────┘ └─────┘Что нарисовано: пока массив «деградировал» (один диск мёртв), каждое чтение пропавшего блока вычисляется на лету по остальным дискам и parity. После замены диска контроллер прогоняет все страйпы и наполняет новый диск — это rebuild.
- Usable capacity: (N−1)/N (один диск «уходит» под parity).
- Переживаем отказов: 1.
- Скорость чтения: ~(N−1)× (читаем данные со всех, кроме parity).
- Скорость записи: проблемная — см. ниже.
Дорогая запись: read-modify-write и write hole
Изменить один блок в RAID 5 нельзя «просто записав» его: parity страйпа зависит от всех блоков. Контроллер делает read-modify-write:
Хотим изменить блок A0 → A0'. Нужно пересчитать Pa.
Умный способ (по дельте), но всё равно 2 чтения + 2 записи:
1. READ старый A0
2. READ старый Pa
3. Pa' = Pa XOR A0 XOR A0' (учли разницу)
4. WRITE A0'
5. WRITE Pa'
Одна логическая запись = 4 физических операции → «small write penalty».Что нарисовано: маленькая запись в RAID 5 в 4 раза тяжелее, чем в RAID 0/1.
Для баз с интенсивным случайным write (OLTP) это больно — особенно поверх HDD.
На SSD (см. /os/book/ssd) penalty меньше по латентности, но
лишние записи ускоряют износ ячеек (wear).
Write hole (дыра записи) — отдельная опасность. Шаги 4 и 5 не атомарны: если питание пропадёт между записью данных и записью parity, страйп окажется несогласованным (parity больше не соответствует данным). Тогда при будущем отказе диска восстановление выдаст мусор. Лечат это журналом/NVRAM в контроллере или дизайном ФС (ZFS RAID-Z пишет полный страйп целиком, copy-on-write — перекликается с журналированием из /os/book/fs-implementation).
RAID 10 — зеркало + страйп (быстро и надёжно)
RAID 10 (он же 1+0) — сначала диски парами в зеркала (RAID 1), потом эти зеркала объединяют страйпингом (RAID 0). Получаем скорость страйпа и надёжность зеркала, без parity и без write-penalty.
RAID 10 на 4 дисках = 2 зеркала, по ним страйп:
зеркало M0 зеркало M1
disk0 disk1 disk2 disk3
┌────┐ ┌────┐ ┌────┐ ┌────┐
│ A │ │ A │ │ B │ │ B │ страйп: A→M0, B→M1
│ C │ │ C │ │ D │ │ D │
└────┘ └────┘ └────┘ └────┘
Переживёт даже 2 отказа — ЕСЛИ это разные зеркала (disk0 и disk2).
Но disk0+disk1 (одно зеркало) сразу = потеря данных.Что нарисовано: данные страйпуются между зеркалами, а внутри зеркала дублируются. Отказ одного диска не страшен; двух — переживаем, только если они из разных зеркал. Цена — те же 50% ёмкости, что у RAID 1.
Сводная таблица уровней
| Уровень | Метод | Usable capacity | Переживаем отказов | Чтение | Запись | Когда брать |
|---|---|---|---|---|---|---|
| RAID 0 | striping | N (100%) | 0 | ~N× | ~N× | кэш, временные данные |
| RAID 1 | mirroring | N/2 (50%) | 1 | ~2× | ~1× | загрузочные/критичные пары |
| RAID 5 | distributed parity | (N−1)/N | 1 | ~(N−1)× | медленно (RMW) | архивы, чтение-heavy |
| RAID 6 | двойная parity | (N−2)/N | 2 | ~(N−2)× | ещё медленнее | большие массивы HDD |
| RAID 10 | mirror+stripe | N/2 (50%) | 1 (до 2*) | ~N× | ~N/2× | БД с random write |
* RAID 10 переживает 2 отказа только если они в разных зеркалах.
RAID 6 добавлен для полноты: это RAID 5 с двумя независимыми parity-блоками, переживает смерть двух дисков. Нужен потому, что при rebuild большого массива на HDD второй диск часто умирает прямо во время восстановления (нагрузка + время).
Считаем на пальцах
4 диска по 4 ТБ. Сколько полезно и сколько отказов держим?
RAID 0: usable = 4 × 4 = 16 ТБ, отказов 0
RAID 1: (зеркало пары) usable = 4 ТБ, отказов 1 (часто 2 диска, не 4)
RAID 5: usable = (4−1) × 4 = 12 ТБ, отказов 1
RAID 6: usable = (4−2) × 4 = 8 ТБ, отказов 2
RAID 10: usable = 4×4 / 2 = 8 ТБ, отказов 1 (до 2, если разные зеркала)Формулы: usable(0)=N·S, usable(1)=S (или N·S/2 при многих парах), usable(5)=(N−1)·S, usable(6)=(N−2)·S, usable(10)=N·S/2.
RAID в облаке: чаще НЕ нужен
Важный для собеса вывод. В облаке диск (AWS EBS, GCP PD) — это уже не один физический диск, а сетевое блочное устройство, реплицированное внутри зоны несколькими копиями. Облако само переживает смерть физического носителя прозрачно для тебя.
On-prem: [твой сервер] ── SATA ── [физический диск] ← может умереть → нужен RAID
Облако: [твоя VM] ── сеть ── [EBS том] ── репликация ── [n копий в AZ]
▲ отказ железа скрыт провайдеромЧто нарисовано: то, ради чего нужен RAID 1/5 on-prem (пережить смерть носителя), в облаке уже сделано на уровне сервиса хранилища.
Поэтому:
- RAID 1/5/6 поверх облачного диска — обычно избыточно (двойная репликация, лишние деньги и запись).
- RAID 0 поверх нескольких облачных дисков — иногда оправдан ради IOPS/полосы (но проще взять один том побольше/побыстрее).
- Защита от потери всей зоны/региона — это уже не RAID, а репликация на уровне приложения/БД: реплики Postgres/Mongo в разных AZ, гео-репликация объектного хранилища, кворумные записи. RAID защищает от смерти диска; репликация приложения — от смерти узла, стойки, зоны.
Связь с конкурентностью: запись в кворум реплик и ожидание ack — это та же
работа с примитивами синхронизации и контекстами, что в Go-курсе
(/book/context, /book/patterns): fan-out
записи в N реплик и ожидание большинства через WaitGroup/каналы.
5 дисков по 2 ТБ собрали в RAID 5. Сколько ТБ полезной ёмкости?
Какой уровень RAID не переживает ни одного отказа диска?
Что такое write penalty в RAID 5 при изменении одного блока?
Почему поверх облачного диска (EBS/PD) обычно не делают RAID 1/5?
Выбери верные утверждения про parity и восстановление. (несколько)
8 дисков по 4 ТБ в RAID 10. Полезная ёмкость в ТБ?
Что спрашивают на собесе
- Чем RAID 5 отличается от RAID 10 и когда что брать? RAID 5 экономит ёмкость ((N−1)/N), но имеет write penalty (read-modify-write, 4 операции на запись) — хорош для чтения/архивов. RAID 10 жертвует 50% ёмкости, но даёт быструю запись и простой rebuild — для БД с случайной записью.
- Что такое write hole и как с ним борются? Несогласованность данных и parity при сбое питания между их записями. Лечат журналом/NVRAM контроллера или copy-on-write ФС (ZFS RAID-Z), пишущими полный страйп атомарно.
- Почему один диск из массива «съедается» в RAID 5? Эквивалент одного диска уходит под parity (распределённую). Объясни XOR-восстановление: B = A XOR C XOR P.
- Сколько отказов переживает каждый уровень и посчитай usable capacity для N дисков по S ТБ — типовая задачка с числами.
- Нужен ли RAID в облаке? Обычно нет: облачный том уже реплицирован внутри зоны. От потери зоны/региона спасает не RAID, а репликация на уровне БД/приложения.
- RAID — это бэкап? Нет. RAID защищает от отказа железа, но не от
rm -rf, логической порчи или шифровальщика — это всё честно скопируется/перезапишется. Бэкап нужен отдельно.