Один диск — это ограниченная скорость (HDD даёт 100–200
случайных операций в секунду) и единая точка отказа. Производители указывают
для дисков наработку на отказ (MTBF) в 1–2,5 млн часов, что соответствует
годовой частоте отказов меньше 1%. Реальная статистика больших парков
(например, ежегодные отчёты Backblaze) — порядка 1–2% в год. На ноутбуке это
«однажды не повезёт». В стойке из 500
дисков это пять-десять отказов в год, то есть отказ диска — плановое событие, а
не авария.
RAID (redundant array of independent disks, избыточный массив независимых
дисков) собирает несколько физических дисков в одно логическое устройство так,
чтобы получить скорость, устойчивость к отказу или и то и другое. Файловая
система (см. реализацию ФС) видит один большой
диск и не знает, что внутри их несколько.
┌─────────────────────────────────────┐ ФС │ видит один диск │ └───────────────────┬─────────────────┘ │ ┌─────────┴─────────┐ │ слой RAID │ режет, копирует, считает чётность └─┬──────┬──────┬───┬┘ ▼ ▼ ▼ ▼ [disk0][disk1][disk2][disk3]
Слой RAID бывает аппаратным (контроллер с батарейкой или конденсатором для
кэша) и программным: mdadm в Linux, RAID-Z в ZFS, LVM.
Каждый уровень RAID оценивают по трём вопросам:
полезная ёмкость — сколько места доступно под данные;
сколько отказов переживёт массив без потери данных;
скорость чтения и записи по сравнению с одним диском.
Дальше N — число дисков, S — ёмкость одного диска.
Striping (чередование, «нарезка полосами»): данные режутся на куски (обычно
64–512 КБ, это размер страйп-юнита) и раскладываются по дискам по кругу.
RAID 0, 4 диска: disk0 disk1 disk2 disk3 полоса0 A B C D полоса1 E F G H полоса2 I J K L
Чтение A..D идёт со всех четырёх дисков одновременно, случайные запросы
расходятся по разным дискам. Скорость растёт примерно в N раз.
Избыточности нет никакой. Умер любой диск — пропала каждая четвёртая полоса
каждого файла, и восстановить её не из чего. Хуже того, вероятность потерять
массив в N раз выше, чем один диск: умереть может любой из четырёх.
Полезная ёмкость: N·S.
Переживает отказов: 0.
Скорость: ~N× на чтение и запись.
Где уместен: временные данные, кэши, которые легко пересобрать, scratch-диски
для вычислений.
Зеркало надёжно, но тратит половину ёмкости. RAID 5 обходится одним «лишним»
блоком на полосу. Это блок чётности (parity), который считают побитовым XOR
(«исключающее ИЛИ») всех блоков данных полосы.
У XOR есть свойство, на котором всё держится: x ^ x = 0 и x ^ 0 = x. Поэтому
если P = A ^ B ^ C, то A ^ C ^ P = A ^ C ^ A ^ B ^ C = B. Любой один
потерянный блок восстанавливается XOR-ом всех остальных.
A = 1011 B = 0110 C = 1100 P = A ^ B ^ C = 0001 потеряли B: A ^ C ^ P = 1011 ^ 1100 ^ 0001 = 0110 = B
То же самое на Go, с байтами вместо битов:
package mainimport "fmt"// parity считает XOR всех блоков страйпа.func parity(blocks ...[]byte) []byte { p := make([]byte, len(blocks[0])) for _, b := range blocks { for i := range b { p[i] ^= b[i] } } return p}func main() { // Страйп из трёх блоков данных по 4 байта (в жизни — по 64 КБ и больше). d0 := []byte("GoOS") d1 := []byte("RAID") d2 := []byte("5!!!") p := parity(d0, d1, d2) fmt.Printf("parity: % x\n", p) // Диск с d1 умер. Восстанавливаем его блок из уцелевших и чётности. restored := parity(d0, d2, p) fmt.Printf("восстановлено: %q\n", restored) // Запись в один блок: новая чётность по дельте, без чтения остальных дисков. newD0 := []byte("gOos") newP := parity(p, d0, newD0) // P' = P xor старое xor новое fmt.Println("дельта совпадает с пересчётом:", string(newP) == string(parity(newD0, d1, d2)))}
parity: 20 0f 27 36восстановлено: "RAID"дельта совпадает с пересчётом: true
Чётность в RAID 5 не лежит на одном выделенном диске, а переходит с диска на
диск от полосы к полосе. Иначе каждая запись обновляла бы чётность на одном и том
же диске, и он стал бы узким местом (так был устроен RAID 4).
RAID 5, 4 диска (3 блока данных + 1 чётность в каждой полосе): disk0 disk1 disk2 disk3 полоса0 A0 A1 A2 Pa полоса1 B0 B1 Pb B2 полоса2 C0 Pc C1 C2 полоса3 Pd D0 D1 D2
Если умер, скажем, disk2, массив продолжает работать в деградированном
режиме: каждое чтение блока с disk2 вычисляется на лету XOR-ом трёх других
дисков. После замены диска контроллер проходит все полосы и заполняет новый
диск: A2 = A0 ^ A1 ^ Pa, затем Pb = B0 ^ B1 ^ B2 и так далее.
Полезная ёмкость: (N − 1)·S.
Переживает отказов: 1.
Чтение: последовательное ~(N − 1)× (блоки чётности пропускаются), случайное
до N× (чётность разбросана, работают все диски).
Изменить один блок в RAID 5 просто так нельзя: чётность полосы зависит от всех
её блоков. Перечитывать всю полосу не нужно — как показал пример выше, новую
чётность можно получить по дельте:
хотим заменить A0 на A0' 1. прочитать старый A0 2. прочитать старую Pa 3. Pa' = Pa ^ A0 ^ A0' 4. записать A0' 5. записать Pa' одна логическая запись = 2 чтения + 2 записи = 4 операции с дисками
Это называют штрафом на мелкую запись (small write penalty). Для базы данных
с потоком случайных записей RAID 5 на HDD работает примерно вчетверо медленнее,
чем те же диски в RAID 0. Крупная запись целой полосы штрафа не платит: все
блоки новые, чётность считается из них, ничего читать не надо.
Шаги 4 и 5 — две независимые записи на разные диски. Если между ними пропадёт
питание, данные в полосе уже новые, а чётность ещё старая. Пока все диски живы,
этого никто не заметит. Но если потом умрёт диск, его блоки восстановят по
неверной чётности, и получится мусор. Это дыра записи (write hole).
Лечат её так же, как файловые системы лечат свою несогласованность после сбоя
(см. журналирование): аппаратный контроллер держит
незавершённые записи в кэше с батарейкой, mdadm умеет вести журнал записей
на отдельном устройстве, а RAID-Z в ZFS никогда не перезаписывает полосу на
месте — пишет новую целиком и атомарно переключает указатель (copy-on-write).
RAID 6 добавляет вторую, независимую контрольную сумму (её считают не простым
XOR, а кодом Рида — Соломона). Массив переживает отказ любых двух дисков.
Полезная ёмкость: (N − 2)·S.
Переживает отказов: 2.
Мелкая запись ещё дороже: 6 операций (прочитать блок и две чётности, записать
все три).
Зачем вторая чётность, если диски отказывают редко? Из-за времени
восстановления. Диск на 16 ТБ, переписываемый со скоростью 200 МБ/с, заполняется
за 16·10¹² / 200·10⁶ = 80 000 секунд, то есть больше 22 часов, а под рабочей
нагрузкой — несколько суток. Всё это время RAID 5 работает без запаса: второй
отказ означает потерю массива. А нагрузка rebuild (полное чтение всех
оставшихся дисков) как раз повышает шанс, что откажет ещё один диск из той же
партии, или что на нём всплывёт нечитаемый сектор. Производители пишут для
потребительских дисков порядка одной неисправимой ошибки чтения на 10¹⁴ бит
(это около 12,5 ТБ), для серверных — на 10¹⁵. При rebuild массива из
нескольких 16-терабайтных дисков читаются десятки терабайт. Поэтому для
больших HDD RAID 5 сегодня считается рискованным, и берут RAID 6 или RAID 10.
RAID 10 (он же 1+0): диски объединяют в зеркальные пары, а поверх пар
делают чередование.
RAID 10, 4 диска = 2 зеркала: зеркало M0 зеркало M1 disk0 disk1 disk2 disk3 A A B B C C D D
Скорость чередования, надёжность зеркала, никакой чётности и никакого штрафа
на мелкую запись. Rebuild простой: скопировать один диск с его пары.
Полезная ёмкость: N·S / 2.
Переживает отказов: гарантированно 1. Два — если они в разных зеркалах
(disk0 и disk2), а если умерли оба диска одной пары (disk0 и disk1), данные
потеряны.
Скорость: чтение ~N×, запись ~N/2× (каждый блок пишется дважды).
Это частый выбор под базы данных со случайной записью.
RAID защищает от одной беды: физической смерти диска. От всего остального он не
спасает. rm -rf или DROP TABLE мгновенно применятся ко всем копиям. Баг в
приложении, который записал мусор, запишет его на все диски. Шифровальщик
зашифрует весь массив. Пожар в стойке уничтожит все диски сразу. Для этого
нужны бэкапы: отдельные копии во времени, в другом месте, желательно такие,
которые нельзя удалить с того же сервера.
В облаке диск виртуальной машины (AWS EBS, GCP Persistent Disk) — это не
физический диск, а сетевое блочное хранилище, которое провайдер сам
реплицирует на несколько серверов внутри зоны доступности. Смерть физического
носителя скрыта от вас.
свой сервер: [сервер] ── SATA/NVMe ── [физический диск] умер → нужен RAID облако: [VM] ── сеть ── [том EBS] ── репликация внутри зоны
Поэтому:
RAID 1/5/6 поверх облачных томов обычно не нужен: избыточность уже есть
внутри сервиса, вы заплатите за неё дважды и потеряете в скорости записи.
RAID 0 из нескольких томов иногда делают ради IOPS и пропускной
способности, если один том упирается в лимит. Сегодня чаще проще взять один
том с нужными параметрами.
Облачный том тоже может отказать. AWS указывает для gp2/gp3 годовую
частоту отказов 0,1–0,2% (для io2 — на порядок ниже). Это в разы лучше
физического диска, но не ноль, и от удаления тома или порчи данных репликация
не спасает. Нужны снапшоты и бэкапы.
Локальные NVMe-диски инстанса (instance store, local SSD) быстрые, но не
реплицируются и пропадают при остановке машины. Здесь RAID снова имеет смысл,
но чаще данные защищают репликацией на уровне приложения.
От потери целой зоны или региона RAID не спасает в принципе. Для этого
реплицируют на уровне базы или приложения: реплики PostgreSQL в другой зоне,
кворумная запись в etcd или Cassandra. RAID защищает от смерти диска,
репликация — от смерти сервера, стойки, зоны.
Проверь себя· RAID и надёжность
5 дисков по 2 ТБ собрали в RAID 5. Сколько ТБ полезной ёмкости?
Какой уровень RAID не переживает ни одного отказа диска?
Что такое write penalty в RAID 5 при изменении одного блока?
Почему поверх облачного диска (EBS/PD) обычно не делают RAID 1/5?
Выберите верные утверждения про parity и восстановление. (несколько)
8 дисков по 4 ТБ в RAID 10. Полезная ёмкость в ТБ?
Почему мелкая запись в RAID 5 стоит четыре операции, а запись целой полосы — нет?
Почему для RAID 5 на дисках по 16 ТБ главный риск — время восстановления?
Шесть дисков по 8 ТБ. Посчитайте полезную ёмкость и число переживаемых отказов для RAID 5, RAID 6 и RAID 10. Какой выбрать под PostgreSQL с интенсивной записью?
Показать ответы
Ответы. (1) x ^ x = 0, поэтому XOR всех оставшихся блоков полосы и чётности даёт пропавший блок. (2) Для мелкой записи нужны старые данные и старая чётность (2 чтения) и запись новых данных и чётности (2 записи); при записи всей полосы чётность считается из новых блоков, читать нечего. (3) Rebuild идёт сутки и дольше, всё это время запаса нет, а полное чтение оставшихся дисков повышает шанс второго отказа или нечитаемого сектора. (4) RAID 5: 40 ТБ, 1 отказ. RAID 6: 32 ТБ, 2. RAID 10: 24 ТБ, гарантированно 1, до 3 при отказах в разных парах. Под интенсивную запись — RAID 10: нет штрафа на мелкую запись и быстрый rebuild.
RAID 5 или RAID 10 под базу? RAID 10: нет штрафа на мелкую запись (2
операции вместо 4), быстрый и простой rebuild. RAID 5 экономит ёмкость, но
медленно пишет мелкими блоками и рискован при rebuild больших дисков.
Как XOR восстанавливает данные? P = A ^ B ^ C, потерянный B = A ^ C ^ P,
потому что x ^ x = 0.
Что такое write hole? Сбой между записью данных и записью чётности
оставляет полосу несогласованной. Лечат журналом, кэшем с батарейкой,
copy-on-write (RAID-Z).