GraphLMS

ОС
Начать

ОС · 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/каналы.

Проверь себя· RAID и надёжность

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, логической порчи или шифровальщика — это всё честно скопируется/перезапишется. Бэкап нужен отдельно.
Flash и SSDФайлы, каталоги, дескрипторы