Persistence · 13 мин

Контент v0.12.0

Flash и SSD

Другое железо, другие правила

У жёсткого диска главная беда — механика: seek и вращение стоят миллисекунды, поэтому случайный доступ в сотни раз медленнее последовательного. SSD (solid state drive, твердотельный накопитель) состоит только из микросхем флеш-памяти и контроллера. Головки нет, вращения нет, и прочитать любую страницу можно примерно за одно и то же время: десятки микросекунд вместо десяти миллисекунд.

Если бы на этом всё заканчивалось, глава была бы на один абзац. Но у флеш-памяти свои странные правила записи, и из-за них SSD внутри устроен сложнее HDD. Эти правила объясняют, почему SSD тормозит, когда почти заполнен, почему он изнашивается и почему дешёвый SSD под базой данных может оказаться медленнее, чем ожидалось.

Страницы, блоки и стирание

Память в SSD — это NAND flash. Её единицы:

  • страница (page), обычно 4–16 КБ — единица чтения и записи;
  • блок (block, erase block), сотни страниц, то есть несколько мегабайт — единица стирания.

И три правила:

  1. Читать можно любую страницу в любом порядке.
  2. Писать можно только в чистую (стёртую) страницу. Записать поверх старых данных нельзя.
  3. Стереть можно только целый блок, отдельную страницу — нельзя.

Порядок времён (для распространённой 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 и SSD

Какая операция во flash-памяти возможна только на уровне целого блока, а не отдельной страницы?

Что такое write amplification?

Что делает FTL (Flash Translation Layer)?

Зачем нужен wear leveling?

Что сообщает SSD команда TRIM?

Почему у SSD случайное чтение почти такое же быстрое, как последовательное?

Частые ошибки

  • Повторять, что у SSD случайное чтение «как последовательное», не уточняя про глубину очереди.
  • Заполнять SSD под базу данных до 95%: сборка мусора резко замедлит запись и ускорит износ.
  • Ставить потребительский SSD без защиты от потери питания под базу с частыми fsync.
  • Забывать про TRIM (fstrim.timer, опция discard).
  • Читать с NVMe мелкими блоками строго по одному запросу и удивляться десяткам мегабайт в секунду.

Проверьте себя

  1. Почему SSD не перезаписывает страницу на месте?
  2. Зачем производитель прячет часть флеш-памяти от ОС?
  3. Почему серверный SSD с PLP подтверждает fsync быстрее потребительского?
  4. 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-кэша, не дожидаясь записи во флеш.

Продолжить