GraphLMS

ОС
Начать

ОС · Persistence · 13 мин

Flash и SSD

Почему SSD — это совсем другое железо

В прошлой главе про HDD мы разбирали диск с крутящимися пластинами и механической головкой. Там главная боль — seek (подвести головку) и rotation (дождаться нужного сектора). Поэтому случайный доступ у HDD в десятки раз медленнее последовательного.

SSD (Solid State Drive — «твердотельный накопитель») устроен иначе: никаких движущихся частей, только микросхемы памяти. Память называется NAND flash (флеш-память). Раз нет головки и нет вращения — нет и seek. Любая ячейка доступна почти за одинаковое время.

Бытовая аналогия. HDD — это виниловая пластинка с иглой: чтобы заиграть нужную песню, иглу надо физически подвести к дорожке. SSD — это плейлист в телефоне: любой трек открывается мгновенно, неважно, первый он или сотый.

Казалось бы — заменили механику на электронику, стало быстро, и всё. Но у флеша есть три неочевидных особенности, которые меняют вообще всё поведение накопителя. Из-за них SSD внутри гораздо хитрее, чем HDD. Разберём их.

        HDD                              SSD
   ┌───────────────┐              ┌───────────────────┐
   │   ((( o )))    │              │ [chip][chip][chip] │
   │  пластина+игла │              │ [chip][chip][chip] │
   │  крутится 7200 │              │   нет движущихся   │
   │   об/мин       │              │     частей         │
   └───────────────┘              └───────────────────┘
   seek + rotation                 любая ячейка ~ равно
   случайн. << послед.             случайн. ≈ послед. (чтение)

Слева HDD с механикой и неравномерным доступом, справа SSD из микросхем, где любое место читается примерно за одно и то же время.

Три правила флеша: страницы, блоки и стирание

Вот главное, что нужно понять про NAND flash. Внутри есть две единицы размера:

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

А теперь три правила, которые ломают привычную картину «диск — это массив ячеек, пиши куда хочешь»:

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

То есть у каждой страницы три возможных состояния: СТЁРТАЯ (чистая, готова принять запись), ЗАПИСАННАЯ (в ней лежат данные) и НЕВАЛИДНАЯ (данные устарели, но физически ещё занимают место — стереть-то можно только весь блок).

БЛОК (единица стирания) = много страниц
┌─────────────────────────────────────────────┐
│ page0  page1  page2  page3  ...  page127      │ ← единица чтения/записи
└─────────────────────────────────────────────┘
 
Чтение:  page2 ──────────────► быстро, любой порядок
Запись:  page2 ──► только если page2 СТЁРТАЯ
Стирание: ───► нельзя стереть page2 одну
              стирается ВЕСЬ блок целиком

Здесь видно несоответствие масштабов: пишем мелко (страница), а стираем крупно (блок). Именно из этого вырастают все проблемы дальше.

Главная засада: нельзя перезаписать страницу

В HDD перезапись тривиальна: навёл головку, наложил новые данные поверх старых. Во флеше так нельзя. Чтобы записать в уже занятую страницу, её сперва надо стереть. А стереть одну страницу нельзя — только весь блок из сотни страниц.

Представьте: в блоке лежат данные 128 страниц, а вы хотите изменить всего одну. Наивный путь был бы кошмаром:

Хочу изменить page2 в блоке, где остальные 127 страниц заняты полезным:
 
наивно:
  1. прочитать все 128 страниц в память (буфер)
  2. в буфере поменять page2
  3. стереть ВЕСЬ блок
  4. записать обратно все 128 страниц
 
→ ради 4 КБ изменений мы перелопатили несколько МБ!

Вот это и есть write amplification (усиление записи) — когда чтобы записать чуть-чуть логических данных, накопителю приходится физически перетасовать гораздо больше. Считается как «сколько байт реально записано на флеш ÷ сколько байт просил записать хост». Чем выше — тем хуже: и медленнее, и быстрее изнашивается флеш.

Аналогия: блокнот, написанный ручкой без ластика. Стереть можно только целую страницу целиком. Хотите поправить одно слово в середине — придётся переписать всю страницу заново.

На практике никто не делает наивный «read-modify-erase-write» на каждую запись. Вместо этого SSD пишет новые данные в свободное место, а старую копию просто помечает невалидной — и разгребает мусор потом, пачкой. Кто этим занимается — дальше.

FTL: слой, который всё это прячет

Если бы операционная система знала про страницы, блоки и стирание, драйверы файловых систем (см. файлы и директории) пришлось бы переписывать под флеш. Этого не случилось — потому что внутри SSD есть FTL (Flash Translation Layer), «слой трансляции флеша».

FTL — это маленький контроллер с прошивкой внутри SSD. Снаружи он прикидывается обычным блочным диском: «дай мне логический блок №12345 — я запишу/прочитаю». А внутри он сам решает, на какую физическую страницу флеша это положить, и ведёт таблицу соответствия логический адрес → физический адрес (mapping table).

   ОС думает, что пишет в "сектор 12345"


        ┌───────────────────────┐
        │         FTL           │  (прошивка контроллера SSD)
        │  таблица отображения: │
        │   LBA 12345 → phys p17 │
        │   LBA 12346 → phys p3  │
        │   ...                  │
        │  + сборка мусора       │
        │  + wear leveling       │
        └───────────────────────┘


     реальные блоки/страницы NAND flash

ОС видит ровный массив секторов; FTL под капотом раскидывает их по физическим страницам и держит карту соответствия.

Главный приём FTL — log-structured запись (запись «журналом»): новые данные всегда кладутся в очередную свободную стёртую страницу, по порядку, а в таблице обновляется указатель. Старая физическая страница помечается невалидной. Так мы избегаем дорогого «стереть блок ради одной страницы» на каждую запись.

Запись данных X в LBA 100, потом перезапись X' в тот же LBA 100:
 
шаг 1: пишем X в чистую page5,  таблица: LBA100 → page5
шаг 2: НЕ стираем page5! пишем X' в чистую page6
        таблица: LBA100 → page6   (page5 → НЕВАЛИДНА)
 
   page5 [X  невалид]   page6 [X' валид]   page7 [чисто] ...

Запись всегда идёт в чистое место — быстро. А невалидные страницы копятся, и их надо когда-то освобождать.

Сборка мусора и wear leveling

Свободные стёртые страницы кончаются — их надо возвращать в оборот. Этим занимается garbage collection (сборка мусора) внутри FTL.

GC находит блок, где много невалидных страниц, переносит из него ещё живые (валидные) страницы в другое место, а затем стирает весь блок целиком — и тот снова готов к записи.

Блок A почти мусорный (V = валид, X = невалид, . = пусто):
   [V][X][X][V][X][X][X][V]   ← 3 валидных, 5 невалидных
 
GC:
   1. скопировать 3 валидные в другой блок  ← вот это и есть лишняя
   2. стереть блок A целиком → [.][.][.][.][.][.][.][.]    физическая запись
                                                            (write amplification!)

Заметьте: шаг копирования валидных страниц — это та самая лишняя физическая запись, источник write amplification. Чем активнее перезаписывают SSD и чем он полнее, тем больше приходится копировать.

Вторая забота FTL — wear leveling (выравнивание износа). Беда флеша в том, что каждую ячейку можно стереть-перезаписать ограниченное число раз (от нескольких тысяч до десятков тысяч циклов, зависит от типа). После исчерпания ячейка перестаёт надёжно держать заряд.

Если бы FTL всегда писал в одни и те же физические блоки (например, туда, куда маппится часто меняющийся файл), эти блоки умерли бы быстро, а остальные остались бы как новенькие. Wear leveling специально размазывает запись равномерно по всем блокам — даже «холодные» данные, которые лежат и не меняются, периодически переносит, чтобы освободить мало изношенные блоки под горячую запись.

без wear leveling:          с wear leveling:
блок0 ████████ (умер)        блок0 ███
блок1 █                      блок1 ███
блок2 █                      блок2 ███   ← износ равномерный,
блок3 █                      блок3 ███     ресурс всего SSD больше

Слева горячий блок изнашивается в одиночку и выходит из строя; справа нагрузка размазана, и накопитель живёт дольше.

TRIM: дай SSD знать, что данные больше не нужны

Есть тонкая проблема на стыке файловой системы и FTL. Когда вы удаляете файл, файловая система (см. файлы и директории) просто помечает его блоки свободными в своих структурах — но самому SSD об этом не сообщает. С точки зрения FTL те страницы всё ещё «валидные данные», и при сборке мусора он будет честно их копировать. Зря.

Команда TRIM решает это: ОС явно говорит SSD «логические блоки с такими-то адресами больше не содержат нужных данных». Теперь FTL может пометить соответствующие физические страницы невалидными и не копировать их при GC.

Без TRIM:                        С TRIM:
ОС удалила файл                  ОС удалила файл → шлёт TRIM(LBA...)
SSD не знает → считает валидным  SSD помечает страницы невалидными
GC копирует мусор (лишняя запись) GC пропускает их → меньше write amplif.

Итог TRIM: меньше write amplification, меньше износ, выше скорость записи на заполненном диске.

Почему чтение быстрое, а запись — сложная

Соберём всё вместе. Случайное чтение на SSD почти такое же быстрое, как последовательное: головки нет, любую страницу достаём напрямую. Это огромный выигрыш над HDD, где случайное чтение упиралось в seek.

А вот запись сложнее, чем кажется снаружи. Из-за «стирать только блоками» SSD не перезаписывает на месте, а ведёт лог, копит невалидные страницы и периодически запускает сборку мусора со стиранием блоков. Поэтому:

  • запись «рывками» (то быстро в кэш/свободные страницы, то проседает, когда включился GC);
  • на почти полном SSD запись медленнее (мало свободных блоков, GC копирует больше);
  • мелкая случайная запись хуже крупной последовательной (больше работы FTL).

HDD против SSD: сводная таблица

Параметр HDD SSD
Принцип пластины + головка NAND flash, микросхемы
Движущиеся части да нет
Случайное чтение медленно (seek+rotation) быстро, ≈ последовательного
Последовательное чтение хорошо очень хорошо
Перезапись на месте да, просто нет: нужно стирать блок
Единица стирания сектор перезаписывается целый блок (сотни страниц)
Особый эффект convoy/seek write amplification
Износ механический, со временем ограничено циклов на ячейку
Спец-механизмы планировщик I/O FTL, GC, wear leveling, TRIM
Цена за ГБ ниже выше
Латентность миллисекунды микросекунды

Таблица показывает: SSD выигрывает по скорости и латентности, но платит сложностью внутри (FTL) и ограниченным ресурсом перезаписи.

Проверь себя· Flash и SSD

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

Что такое write amplification?

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

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

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

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

Что спрашивают на собесе

  • Почему во флеше нельзя просто перезаписать страницу и при чём тут стирание целого блока.
  • Что такое write amplification, откуда оно берётся и как TRIM и запас свободного места его уменьшают.
  • Зачем нужен FTL и какую таблицу он ведёт; что такое log-structured запись внутри SSD.
  • Что делает wear leveling и почему без него SSD умрёт раньше времени.
  • Почему у SSD случайное чтение почти равно последовательному, а запись ведёт себя «рывками» и проседает на заполненном диске.
  • Что именно делает TRIM и на каком стыке (файловая система ↔ контроллер) он работает.
Жёсткие диски и планирование запросовRAID и надёжность хранения