Persistence · 13 мин
Контент v0.12.0Flash и SSD
Другое железо, другие правила
У жёсткого диска главная беда — механика: seek и вращение стоят миллисекунды, поэтому случайный доступ в сотни раз медленнее последовательного. SSD (solid state drive, твердотельный накопитель) состоит только из микросхем флеш-памяти и контроллера. Головки нет, вращения нет, и прочитать любую страницу можно примерно за одно и то же время: десятки микросекунд вместо десяти миллисекунд.
Если бы на этом всё заканчивалось, глава была бы на один абзац. Но у флеш-памяти свои странные правила записи, и из-за них SSD внутри устроен сложнее HDD. Эти правила объясняют, почему SSD тормозит, когда почти заполнен, почему он изнашивается и почему дешёвый SSD под базой данных может оказаться медленнее, чем ожидалось.
Страницы, блоки и стирание
Память в SSD — это NAND flash. Её единицы:
- страница (page), обычно 4–16 КБ — единица чтения и записи;
- блок (block, erase block), сотни страниц, то есть несколько мегабайт — единица стирания.
И три правила:
- Читать можно любую страницу в любом порядке.
- Писать можно только в чистую (стёртую) страницу. Записать поверх старых данных нельзя.
- Стереть можно только целый блок, отдельную страницу — нельзя.
Порядок времён (для распространённой TLC-памяти, по паспортам производителей):
чтение страницы ~50–100 мкс
запись страницы ~0,5–1 мс
стирание блока ~3–5 мсТак что у страницы три состояния: чистая (готова к записи), записанная (валидные данные) и невалидная (данные устарели, но место занято до стирания всего блока). Мелкая запись и крупное стирание — это несоответствие масштабов, из которого растут все остальные сложности.
Нельзя перезаписать на месте
На HDD изменить 4 КБ в середине файла просто: головка встаёт над сектором и пишет поверх. На флеше, чтобы изменить одну страницу в заполненном блоке, наивно пришлось бы:
1. прочитать все 256 страниц блока в память
2. изменить одну
3. стереть блок ~3–5 мс
4. записать обратно 256 страниц
→ ради 4 КБ переписали мегабайтОтношение «сколько байт физически записано во флеш» к «сколько байт попросил записать хост» называется write amplification (усиление записи, WA). У наивной схемы выше оно равно 256. Чем WA больше, тем медленнее запись и тем быстрее изнашивается память.
Поэтому SSD так не делает. Новые данные пишутся в другую, чистую страницу, а старая просто помечается невалидной. Кто следит, где теперь лежат данные, — следующий раздел.
FTL: слой, который всё прячет
Операционная система работает с SSD как с обычным блочным устройством: «прочитай логический блок 12345», «запиши в логический блок 12345». Этот адрес называется LBA (logical block address). Чтобы файловым системам не нужно было знать про страницы, блоки и стирание, в контроллере SSD работает прошивка FTL (flash translation layer, слой трансляции флеша). Она ведёт таблицу «логический адрес → физическая страница» и сама решает, куда класть данные.
ОС: «запиши LBA 100»
│
▼
┌────────────────────────────────┐
│ FTL (прошивка контроллера SSD) │
│ LBA 100 → блок 7, страница 5 │
│ LBA 101 → блок 2, страница 0 │
│ + сборка мусора │
│ + выравнивание износа │
└────────────────────────────────┘
│
▼
NAND: блоки и страницыЗапись в FTL устроена как журнал: новые данные кладутся в следующую чистую страницу открытого блока, а в таблице меняется указатель.
Записали X в LBA 100: LBA 100 → p5 p5 [X валидна]
Перезаписали X' в LBA 100: LBA 100 → p6 p5 [X невалидна]
p6 [X' валидна]Запись всегда идёт в чистое место и всегда последовательно внутри блока — быстро. Таблица отображения занимает около 1 байта на килобайт ёмкости (примерно 1 ГБ на 1 ТБ диска), поэтому у хороших SSD есть своя DRAM, где эта таблица живёт. Дешёвые модели без DRAM держат её часть в памяти хоста или кэшируют кусками, и на случайных нагрузках это заметно.
Та же идея «никогда не писать на место, писать в конец лога, потом убирать мусор» встретится ещё дважды: в лог-структурированных ФС и в LSM-деревьях баз данных.
Сборка мусора и откуда берётся усиление записи
Чистые страницы когда-нибудь кончаются. Тогда работает сборка мусора (garbage collection, GC) внутри FTL: выбрать блок с большим числом невалидных страниц, скопировать из него ещё валидные страницы в другой блок, стереть освободившийся блок.
Посчитаем на примере. Блок из 128 страниц, в нём 96 валидных и 32 невалидных. Чтобы получить 32 чистые страницы под новые данные, GC переписывает 96 валидных страниц и стирает блок.
хост хочет записать: 32 страницы
флеш реально пишет: 96 (перенос валидных) + 32 (новые данные) = 128 страниц
write amplification: 128 / 32 = 4Если бы в блоке была всего одна валидная страница, WA было бы около 1. Поэтому GC выбирает блоки «погрязнее», и поэтому так важно, сколько на диске свободного места: чем полнее SSD, тем больше валидных страниц в каждом блоке, тем больше копирования ради каждой записи. Отсюда два практических следствия.
- Запись на почти заполненном SSD медленнее. Под длительной записью накопитель, заполненный на 95%, может писать в разы медленнее, чем пустой.
- Over-provisioning (резерв ёмкости). Производитель ставит флеш больше объявленного объёма (диск на 960 ГБ физически содержит 1024 ГБ и больше) и прячет разницу от ОС. Этот запас всегда свободен, GC есть где развернуться. У серверных моделей резерв больше, отсюда «странные» объёмы вроде 800 ГБ или 3,84 ТБ. Можно увеличить резерв и самому: оставить часть диска неразмеченной.
Износ и выравнивание
Каждая ячейка выдерживает ограниченное число циклов «стереть-записать» (P/E cycles). Чем больше бит хранит ячейка, тем дешевле гигабайт и тем меньше ресурс:
| Тип ячейки | Бит на ячейку | Порядок ресурса, циклов P/E |
|---|---|---|
| SLC | 1 | ~100 000 |
| MLC | 2 | ~3 000–10 000 |
| TLC | 3 | ~1 000–3 000 |
| QLC | 4 | ~1 000 и меньше |
Если бы часто меняемые данные всегда попадали в одни и те же физические блоки, эти блоки умерли бы быстро, а остальные остались бы новыми. Wear leveling (выравнивание износа) размазывает запись по всем блокам: FTL выбирает для записи менее изношенные блоки и время от времени переносит «холодные» данные, которые годами не меняются, чтобы их малоизношенные блоки тоже поработали.
В паспорте SSD ресурс указывают как TBW (сколько терабайт можно записать за
гарантийный срок) или DWPD (сколько полных объёмов диска в день можно
перезаписывать). Например, диск 3,84 ТБ с 1 DWPD на 5 лет выдержит около 7 ПБ
записи. Текущий износ на Linux видно через nvme smart-log /dev/nvme0: поле
percentage_used показывает, какая доля ресурса израсходована. Write
amplification тратит этот ресурс напрямую: при WA = 4 диск изнашивается вчетверо
быстрее, чем выглядит по записи приложения.
TRIM: сказать диску, что данные больше не нужны
Когда вы удаляете файл, файловая система помечает его блоки свободными в своих структурах, но сам SSD об этом не знает. Для FTL эти страницы по-прежнему валидны, и GC будет честно их копировать, тратя время и ресурс.
Команда TRIM (в NVMe она называется Deallocate) сообщает диску: «логические блоки с такими-то адресами больше не содержат нужных данных». FTL помечает соответствующие страницы невалидными, и GC их не копирует.
В Linux TRIM отправляют двумя способами: опция монтирования discard (сразу
при удалении) или периодически утилитой fstrim (во многих дистрибутивах
включён fstrim.timer, раз в неделю). Периодический вариант обычно
предпочтительнее: немедленный discard на некоторых дисках сам по себе тормозит
удаление.
Интерфейс: SATA против NVMe
SSD сначала подключали так же, как HDD, — через SATA. Протокол SATA (AHCI) рассчитан на механический диск: одна очередь команд глубиной 32 и потолок около 550 МБ/с. Для флеша это узкое горлышко.
NVMe — протокол, сделанный под флеш. Диск подключён прямо к PCIe, очередей может быть до 65 535, в каждой до 65 536 команд. Linux заводит по паре очередей (отправки и завершения) на каждое ядро процессора, поэтому ядра не дерутся за общую очередь. Работает это через механизм из главы про устройства: драйвер кладёт команду в очередь в памяти, звонит в doorbell, диск забирает команду и отвечает через DMA и прерывание.
Зачем столько очередей? Внутри SSD десятки микросхем флеша, каждая с несколькими независимыми плоскостями. Одна команда занимает одну из них, остальные простаивают. Максимальную скорость диск показывает, только когда ему дают много команд одновременно — говорят, при большой глубине очереди (queue depth, QD).
NVMe-диск (PCIe 4.0, TLC), чтение блоками по 4 КБ, порядок величин:
QD = 1: задержка ~80 мкс → ~12 000 IOPS → ~50 МБ/с
QD = 32+: задержка растёт, но ~500 000–1 000 000 IOPS → 2–4 ГБ/с
последовательное чтение крупными блоками: ~5–7 ГБ/сОтсюда важное уточнение к частой фразе «у SSD случайное чтение такое же быстрое, как последовательное». По задержке одной операции — почти да: нет механики, и адрес не важен. По пропускной способности — только если запросов много одновременно. Сервис, который читает с SSD строго по одному блоку и ждёт ответа перед следующим, получит десятки мегабайт в секунду, а не гигабайты. В Go это означает, что параллельные чтения из нескольких горутин (или большие блоки) ускоряют работу с диском, а последовательный цикл по мелким случайным чтениям — нет.
Планировщик ввода-вывода ОС для NVMe обычно none: переупорядочивать запросы
ради экономии seek незачем, а лишняя сортировка добавляет задержку.
Почему запись ведёт себя рывками
Соберём всё, что влияет на запись.
- Кэш записи. У многих потребительских SSD часть TLC/QLC-памяти работает в быстром режиме SLC как буфер. Пока он не заполнен, запись идёт на гигабайтах в секунду; после десятков гигабайт подряд скорость падает в несколько раз.
- Сборка мусора. Когда свободных блоков мало, GC работает прямо во время записи, и задержка отдельных операций прыгает.
- Заполненность. Чем меньше свободного места, тем больше копирования на каждую запись.
- Размер и выравнивание. Мелкая случайная запись тяжелее крупной последовательной: больше работы таблице отображения и GC.
Для базы данных важен ещё один пункт: как диск отрабатывает команду «сбрось кэш
на флеш», которую ОС отправляет при fsync. У серверных SSD есть защита от
потери питания (power loss protection, PLP) — конденсаторы, которых хватает,
чтобы дописать содержимое DRAM-кэша во флеш. Такой диск может подтвердить
fsync сразу из кэша, за десятки микросекунд. Потребительский диск без PLP
обязан реально записать данные во флеш, и fsync стоит миллисекунды. Разница в
скорости коммитов базы — на порядок. Подробности в главе про
fsync.
HDD и SSD рядом
| Параметр | HDD | SSD (NVMe) |
|---|---|---|
| Устройство | пластины и головка | флеш-память и контроллер |
| Задержка случайного чтения | ~10 мс | ~50–100 мкс |
| Случайных IOPS | 100–200 | сотни тысяч при большой глубине очереди |
| Последовательное чтение | 150–250 МБ/с | единицы ГБ/с |
| Перезапись на месте | да | нет, только через FTL и GC |
| Износ | механический | ограниченное число циклов записи |
| Что ухудшает скорость | случайный доступ | заполненность, мелкая запись, нет параллелизма |
| Планировщик в Linux | mq-deadline, bfq | none |
| Цена за ГБ | ниже в несколько раз | выше |
Какая операция во flash-памяти возможна только на уровне целого блока, а не отдельной страницы?
Что такое write amplification?
Что делает FTL (Flash Translation Layer)?
Зачем нужен wear leveling?
Что сообщает SSD команда TRIM?
Почему у SSD случайное чтение почти такое же быстрое, как последовательное?
Частые ошибки
- Повторять, что у SSD случайное чтение «как последовательное», не уточняя про глубину очереди.
- Заполнять SSD под базу данных до 95%: сборка мусора резко замедлит запись и ускорит износ.
- Ставить потребительский SSD без защиты от потери питания под базу с частыми
fsync. - Забывать про TRIM (
fstrim.timer, опцияdiscard). - Читать с NVMe мелкими блоками строго по одному запросу и удивляться десяткам мегабайт в секунду.
Проверьте себя
- Почему SSD не перезаписывает страницу на месте?
- Зачем производитель прячет часть флеш-памяти от ОС?
- Почему серверный SSD с PLP подтверждает
fsyncбыстрее потребительского? - SSD заполнен так, что в среднем в блоке, который выбирает сборщик мусора, 80% страниц валидны. Чему примерно равна write amplification? Что станет с ней, если удалить половину данных, но не делать TRIM?
Показать ответы
Ответы. (1) Писать можно только в стёртую страницу, а стирать — только целый блок. (2) Это резерв (over-provisioning): у сборки мусора всегда есть свободное место, меньше копирования и ниже write amplification. (3) Конденсаторы позволяют дописать кэш во флеш без внешнего питания, поэтому сброс кэша можно подтвердить сразу. (4) Чтобы освободить 20% блока, нужно переписать 80%: на каждую страницу новых данных пишется 1 / 0,2 = 5 страниц, WA ≈ 5. Без TRIM диск не знает об удалении, считает данные валидными, и WA не изменится.
Что спрашивают на собесе
- Почему во флеше нельзя перезаписать страницу? Писать можно только в стёртую страницу, а стирать — только блок целиком.
- Что такое write amplification и как её уменьшают? Отношение физически записанного к запрошенному. Растёт от сборки мусора на заполненном диске. Уменьшают TRIM, резерв ёмкости (over-provisioning) и крупной последовательной записью.
- Что делает FTL? Отображает логические адреса на физические страницы, пишет как журнал, собирает мусор, выравнивает износ.
- Зачем wear leveling? Ячейки выдерживают ограниченное число циклов; запись размазывают, чтобы блоки изнашивались равномерно.
- Что делает TRIM? Сообщает SSD, какие логические блоки свободны, чтобы GC не копировал мёртвые данные.
- Правда ли, что у SSD случайное чтение как последовательное? По задержке почти да. По пропускной — только при большой глубине очереди: NVMe раскрывается на десятках параллельных запросов.
- Почему серверный SSD быстрее на fsync? PLP позволяет подтверждать сброс из DRAM-кэша, не дожидаясь записи во флеш.