MAATRIX / Блог / Почему пустой SSD быстрее заполненного на 90% и куда девается скорость

Почему пустой SSD быстрее заполненного на 90% и куда девается скорость

MAATRIX

Свежий SSD пишет быстро и предсказуемо, а через полгода эксплуатации на том же диске запись под нагрузкой вдруг начинает «плавать» — задержки растут, throughput на записи проседает, хотя по атрибутам SMART диск в порядке и до сих пор не изношен. Обычно к этому моменту диск заполнен на 85-95%. Совпадение не случайное: чем меньше на SSD реально свободного места, тем меньше у контроллера пространства для манёвра, и часть работы, которую он раньше делал заранее и незаметно, теперь приходится делать прямо в момент вашей записи. Разберёмся, почему так устроено на уровне NAND-флеша и что с этим делать на продакшн-сервере.

Почему SSD не может просто перезаписать блок

Первое, что нужно держать в голове: NAND-флеш физически не умеет перезаписывать данные на месте, как это делает жёсткий диск. Минимальная единица чтения и записи — страница (обычно несколько килобайт), но стереть можно только целый блок, который состоит из десятков и сотен страниц (типичный размер блока — единицы мегабайт). Записать данные можно только в чистую, стёртую страницу. Если страница уже занята — сначала блок целиком нужно стереть, а стирание блока — операция на порядок медленнее записи страницы.

Отсюда следует архитектурное решение, на котором держится вся современная организация SSD: контроллер никогда не перезаписывает данные in-place. Вместо этого он ведёт таблицу трансляции адресов — Flash Translation Layer (FTL), — которая отображает логический адрес блока (LBA), который видит операционная система, на физическое место в NAND. Когда вы «перезаписываете» файл, контроллер на самом деле пишет новую версию данных в свободную страницу где-то ещё, обновляет запись в FTL, а старая страница помечается как невалидная (stale) — данные там ещё физически лежат, но больше никому не нужны.

Со временем таких невалидных страниц становится всё больше, и они разбросаны по разным блокам вперемешку с валидными данными. Просто «дописывать поверх» невалидных страниц нельзя — нужно сначала стереть весь блок, а сделать это можно только когда в нём не осталось нужных данных. Здесь в игру вступает сборка мусора.

Garbage collection: как SSD освобождает место под запись

Garbage collection (GC) — это фоновый процесс контроллера, который берёт частично невалидные блоки, переносит из них оставшиеся валидные страницы в новый чистый блок, а исходный блок целиком стирает и возвращает в пул свободных. По сути это дефрагментация на уровне физической памяти, только вместо перестановки файлов контроллер двигает данные между ячейками флеша.

Здоровая ситуация — когда GC успевает делать эту работу заранее, в фоне, пока диск не загружен вашими операциями ввода-вывода. Контроллер видит, что накопилось достаточно частично-невалидных блоков, выбирает наиболее выгодные для очистки (где валидных страниц меньше всего — их дешевле переносить), и потихоньку освобождает новые чистые блоки про запас. Пока в пуле свободных стёртых блоков есть достаточный запас, входящая запись от хоста идёт сразу в них — быстро, без дополнительных операций.

Проблема начинается, когда этого запаса не остаётся. Если свободных стёртых блоков почти нет, а хост продолжает писать, контроллер вынужден выполнять GC синхронно, прямо на пути записи: прежде чем принять ваши данные, он должен экстренно найти блок для очистки, перенести из него валидные страницы, стереть блок — и только после этого записать то, что просили вы. Каждая ваша операция записи тянет за собой одну или несколько «чужих» операций чтения-переноса-стирания, которые раньше делались фоном, а теперь блокируют именно ваш запрос. Отсюда рост задержек и просадка устойчивого write-throughput — то, что видно на почти заполненном диске под нагрузкой.

Важный нюанс: у GC нет привилегированного доступа к «пустому месту», которое видит операционная система. Она оперирует пулом физически стёртых блоков на уровне NAND, а не свободным местом в файловой системе. И это подводит к следующему пункту.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Write amplification: почему одна ваша запись — это несколько записей на кристалле

Write amplification (WA) — коэффициент, показывающий, во сколько раз объём данных, реально записанных в NAND, превышает объём данных, которые запросил хост. WA = 1 — идеальный случай, когда каждая логическая запись оборачивается ровно одной физической. На практике WA почти всегда больше единицы, и именно принудительный GC — главный источник его роста.

Механизм прямой: каждая операция GC, помимо стирания блока, тратит дополнительную запись на перенос валидных страниц в новое место. Чем меньше в блоке осталось валидных данных на момент очистки — тем меньше приходится переносить, и тем ближе WA к единице. Чем меньше у контроллера выбора блоков для очистки (а выбора становится меньше, когда свободного места мало), тем чаще он вынужден брать блоки, где валидных страниц ещё много, — и тем больше лишней работы на каждую вашу запись.

Здесь подробнее разбирался механизм на примере записи одного байта, которая на диске стоит четыре килобайта — тот же принцип «минимальной единицы записи», из-за которого мелкие операции обходятся дороже, чем кажется по логике приложения. WA усиливает этот эффект: помимо накладных расходов от размера страницы, добавляются накладные расходы от переноса данных при очистке.

Практическое следствие для вас — не только просадка скорости, но и ускоренный износ флеш-памяти: каждая ячейка NAND выдерживает ограниченное число циклов стирания-записи, и рост WA означает, что физический ресурс диска расходуется быстрее, чем показывает счётчик записанных вами данных. Как SSD стареет по этому счётчику, отдельно разбиралось в статье про медленную смерть SSD через wear leveling.

TRIM и почему ОС должна сказать контроллеру, что место освободилось

Есть ещё один источник рассинхронизации между тем, что видит файловая система, и тем, что знает контроллер. Когда вы удаляете файл, операционная система просто помечает соответствующие блоки файловой системы как свободные в своих метаданных — сами данные на диске физически никуда не деваются. Без дополнительного сигнала SSD об этом не узнаёт: с точки зрения контроллера, те страницы NAND по-прежнему заняты валидными (с его точки зрения) данными, и GC не имеет права их трогать.

Команда TRIM (discard) решает именно эту проблему: файловая система сообщает контроллеру, какие LBA больше не используются, и он может пометить соответствующие физические страницы как невалидные заранее, не дожидаясь, пока хост попытается записать поверх них что-то новое. Это напрямую расширяет пул страниц, доступных для эффективной фоновой сборки мусора.

Если TRIM отключён, недоступен (например, диск за программным RAID или виртуализацией, которая его не прокидывает) или монтирование сделано без него, SSD физически не видит удалённые вами данные как свободное место — даже если df показывает гигабайты свободного пространства. Диск в этом случае ведёт себя так, будто он куда более заполнен, чем кажется на уровне файловой системы, и GC работает в худших условиях. Подробнее о механике и типичных граблях — в статье про TRIM и что происходит с SSD, когда о нём забыли; там же разбирается кейс, когда фоновый TRIM подвешивал базу на пятнадцать секунд — обратная сторона того же механизма: если запускать TRIM резко и по всему диску разом, он сам может создать пиковую нагрузку.

Over-provisioning: запас, который производитель придержал заранее

Over-provisioning (OP) — это разница между физической ёмкостью флеш-памяти на кристаллах и той ёмкостью, которую диск заявляет операционной системе. Производитель специально резервирует часть NAND-памяти, недоступную пользователю напрямую, и отдаёт её контроллеру под служебные нужды: пул чистых блоков для GC, замену выходящих из строя ячеек, метаданные FTL.

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

Важно, что over-provisioning можно увеличить и вручную, без замены диска — просто не отдавая файловой системе всю физическую ёмкость. Если создать раздел меньше полного размера диска и оставить хвост неразмеченным (или использовать blkdiscard/fstrim по всему диску перед первым использованием, чтобы контроллер знал, что вся неразмеченная область свободна), этот «лишний» кусок фактически становится дополнительным OP: контроллер видит его как свободные LBA, которые можно использовать под ротацию блоков, даже если операционная система его никогда не тронет.

# Пример: диск на 1 ТБ, но раздел создаём на 900 ГБ,
# оставляя ~10% ёмкости неразмеченными как ручной over-provisioning
parted /dev/nvme0n1 mklabel gpt
parted /dev/nvme0n1 mkpart primary 1MiB 900GB

# Перед этим стоит убедиться, что весь диск считается стёртым
# (актуально для нового диска или после смены назначения)
blkdiscard /dev/nvme0n1

Это не бесплатный трюк — вы физически теряете доступную ёмкость. Но для write-intensive нагрузки (журналы БД, очереди сообщений, кеши с высокой скоростью обновления) обмен 5-10% ёмкости на стабильно более низкую задержку записи часто оправдан.

Что происходит, когда диск заполнен почти под завязку

Соберём картину воедино. У пула стёртых блоков SSD есть три источника: заводской over-provisioning (недоступен ОС, но всегда есть), место, которое ОС считает свободным и ещё не занимала, и место, освобождённое через TRIM после удаления файлов. Когда файловая система заполняется на 90% с лишним, второй источник почти исчерпан, а третий — небольшой, потому что свежеосвобождённые блоки быстро занимаются новыми записями. Контроллеру остаётся полагаться почти исключительно на заводской OP.

Пока этого запаса хватает, поведение диска внешне не отличается от менее заполненного. Но запас конечен, и по мере роста заполнения GC приходится:

  • чаще выбирать для очистки блоки с высокой долей ещё валидных данных (потому что «дешёвых» почти-пустых блоков не осталось), что напрямую повышает write amplification;
  • чаще выполнять очистку синхронно, в момент прихода записи от хоста, а не заранее в фоне — отсюда рост задержек именно тогда, когда приложение ждёт подтверждения записи;
  • держать меньший запас параллельных чистых блоков для распределения нагрузки между каналами и кристаллами NAND, что снижает эффективную параллельность записи.

Итог — деградация записи, которая усиливается нелинейно: разница между 70% и 80% заполнения обычно куда менее заметна, чем между 90% и 97%, потому что именно на последних процентах контроллер теряет свободу манёвра. Насколько именно просядет скорость на конкретном диске — зависит от модели контроллера, объёма заводского OP и характера нагрузки (случайная запись мелкими блоками бьёт по этому эффекту сильнее последовательной — почему так, разбирается в статье про разницу между последовательной и случайной записью на SSD); универсальных цифр здесь давать не стоит, но направление эффекта общее для всех современных SSD.

Практические следствия для продакшн-сервера

Из механики выше вытекает несколько рабочих правил:

  • Не планируйте диск под 100% занятости. Порог зависит от модели диска и профиля нагрузки, но правило простое: чем интенсивнее запись, тем больше запас нужен. Для write-heavy сервисов (СУБД, очереди, логи с высоким приростом) закладывайте буфер уже на этапе выбора ёмкости, а не дожидайтесь алерта на 95%.
  • Следите не только за df -h, но и за атрибутами самого диска, которые показывают состояние с точки зрения контроллера:
# NVMe: доступный запас (available spare) и процент израсходованного ресурса
nvme smart-log /dev/nvme0n1

# SATA/общий случай
smartctl -a /dev/sda | grep -i -E "wear|spare|media"
  • Проверяйте, что TRIM реально работает, а не просто включён в опциях монтирования. Периодический fstrim по расписанию (через fstrim.timer) обычно безопаснее непрерывного online-discard на нагруженных серверах — он даёт контроллеру предсказуемые окна для работы.
  • Для write-intensive томов рассмотрите ручной over-provisioning — не отдавайте файловой системе 100% ёмкости, оставьте 5-10% неразмеченными, как в примере выше.
  • Разделяйте типы нагрузки по дискам. Смешивание редко изменяемых архивных данных с активно перезаписываемыми логами/индексами не даёт GC «дешёвых» блоков для очистки.
  • При выборе диска под БД смотрите на класс накопителя, а не только на цену за гигабайт. Серверные/enterprise NVMe с большим OP ведут себя предсказуемее под длительной записью, чем потребительские модели той же ёмкости — разница в архитектуре очередей разбиралась в материале про NVMe и почему один поток не выжимает диск.

На арендованном сервере часть этой работы снимается за счёт того, что провайдер уже подбирает диски под профиль нагрузки — но мониторинг заполнения и TRIM всё равно остаются на вашей стороне.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Насколько именно упадёт скорость записи при заполнении диска на 90%+?

Точную цифру дать нельзя — она зависит от модели контроллера, объёма заводского OP и характера нагрузки (случайная запись страдает сильнее последовательной). Направление универсально: чем меньше свободных стёртых блоков, тем чаще GC работает синхронно и тем выше задержки записи.

Помогает ли просто удалить файлы, если диск заполнен под завязку?

Да, но только если после удаления сработал TRIM (или вы явно вызвали fstrim) — иначе физические страницы всё ещё считаются занятыми, и освобождение места на уровне файловой системы не даёт SSD дополнительного пула для GC.

Стоит ли вручную резервировать over-provisioning на всех дисках подряд?

Нет смысла для дисков с преимущественно последовательной или редкой записью (архивы, статика, бэкапы) — там штатного заводского OP обычно достаточно. Ручной OP оправдан прежде всего для write-intensive томов: БД, очередей сообщений, журналов с высоким приростом.

Можно ли узнать встроенный over-provisioning конкретного диска из спецификации?

Обычно нет прямого поля «OP: N%» в даташите. Ориентироваться стоит на позиционирование модели (consumer/enterprise, DWPD-рейтинг), а не гадать по косвенным признакам.

RAID или файловая система поверх SSD как-то влияют на этот эффект?

Сам механизм GC и write amplification работает на уровне физического диска и не зависит от того, что поверх него. Но программный RAID и некоторые виртуализованные окружения могут не прокидывать TRIM/discard дальше к диску, и тогда эффект неполной видимости свободного места у контроллера усиливается.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →