Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Несомненно, вопрос, вынесенный в заголовок статьи, не нов, поднимался не раз и по нему достигнут консенсус «не особо нужна, и даже может быть вредна».
Однако недавнее обсуждение в комментариях заставило меня ещё раз задуматься.
Со временем любой SSD всё равно сильно фрагментируется (внутри, в FTL)… Свежезаписанный SSD при линейном чтении даст высокую скорость, а уже поработавший — гораздо ниже, потому что линейными оно будет только для вас.
Да, обычно такое не должно происходить: или мы пишем «понемногу» в мелкие файлы/небольшие блоки метаинформации ФС (скорость линейного чтения которых нас не особо волнует), либо же мы мы пишем «помногу» в большие файлы и всё будет хорошо. Бывает и дозапись мелкими блоками в большие файлы — логи, например, однако они относительно короткоживущие и особой проблемы я тут не вижу.
Но легко представился вполне реальный сценарий, при котором всё-таки внутренняя фрагментация SSD может проявиться: файл базы данных, в который идёт достаточно активная случайная запись. Со временем он (оставаясь нефрагментированным на уровне операционной системы) окажется физически очень даже фрагментированным, что может существенно снизить скорость seq scan, резервного копирования и т.п.
Для проверки я написал скрипт и провёл тесты.
Спойлер: проблема присутствует (существенно влияет на производительность) только на одной из попавшихся под руки моделей (и та позиционируется производителем не как datacenter, а как десктопная/ноутбучная).
Если в двух словах, SSD устроен очень непросто. В NAND flash можно писать (точнее стирать) только большими блоками. А операционная система видит SSD как набор 512-байтовых (или 4096-байтовых) секторов, каждый из которых может быть адресован независимо.
Чтобы как-то это совместить, придумана такая вещь, как FTL (flash translation layer): данные во flash-памяти лежат не последовательно, а (очень условно) в том порядке, как они были записаны, что-то вроде log-структурированных файловых систем.
Такие структуры очень хорошо обрабатывают случайную запись, превращая её в последовательную, но, увы, ничто не бывает бесплатно — в результате зачастую последовательное чтение превращается в случайное.
Алгоритмы, по которым работают FTL, закрыты, однако, насколько мы можем судить, у разных производителей они могут кардинально различаться. Соответственно, кардинально может различаться и поведение накопителей под нагрузкой.
Именно это мы и будет исследовать.
Идея скрипта: создаём файл на несколько гигабайт, заполненный случайными данными, замеряем скорость последовательного чтения.
Далее используя случайный доступ переписываем часть тестового файла и снова измеряем скорость линейного чтения. Если наши подозрения верны, то теперь чтение из файла будет идти медленнее.
После каждой записи делаем по три операции чтения с задержкой между ними на случай, если какой-то накопитель в фоне производит дефрагментацию и потом скорость чтения улучшится.
Не раз встречал обзоры, в которых запускают чтение с нового накопителя, получают какие-то фантастические цифры и, ничтоже сумняшеся, публикуют их. Через какое-то время тест повторяют уже на не столь девственном диске, и вдруг оказывается, что время доступа выросло, а скорость, соответственно, упала.
Дело в поддержке TRIM: контроллер внутри SSD может «знать», что в конкретном блоке нет полезных данных, информация об этом в хранится в FTL. И при запросе на чтение из такого блока он не обращается к медленной NAND flash, а сразу возвращает нули. На новом накопителе все блоки помечены как неиспользуемые, соответственно, в тестах на чтение он готов ставить рекорды. Только нас же интересует с какой скоростью SSD умеет отдавать не нули, а данные.
Кроме этого, некоторые накопители умеют сжимать данные, и на хорошо сжимаемых тестовых данных могут показывать не совсем те результаты, которые будут в реальной жизни.
Поэтому, перед тестированием стоит заполнять SSD несжимаемыми данными (в linux хорошим источником может служить /dev/urandom
).
тестовый файл создаётся в текущем каталоге.
тестировал только под linux c dash, coreutils и fio из debian buster, с другими дистрибутивами навряд ли будут проблемы, а вот под freebsd и другие операционные системы скорее всего скрипт придётся «допиливать».
echo preparing...
dd if=/dev/urandom of=testfile bs=1M count=4096 status=none
sync
for A in 1 2 3; do
sleep 10
dd if=testfile of=/dev/null bs=1M iflag=direct
done
for A in 50 200 800 4000; do
echo fio: write ${A}M...
fio --name=test1 --filename=testfile --bs=4k --iodepth=1 --numjobs=1 --rw=randwrite --io_size=${A}M --randrepeat=0 --direct=1 --size=4096M > /dev/null
sync
for B in 1 2 3; do
echo sleep ${B}0
sleep ${B}0
dd if=testfile of=/dev/null bs=1M iflag=direct
done
done
echo sleep 3600
sleep 3600
dd if=testfile of=/dev/null bs=1M iflag=direct
Обнаружилось, что NVMe-накопители intel у меня сейчас только на серверах с windows; пришлось с помощью гугла, stackexchange и какой-то матери слепить вариант и под винду
Из внешних зависимостей только fio
; путь к exe-файлу и временному файлу указывается в первых строчках скрипта.
$testfile = "c:\temp\testfile"
$fio = "c:\temp\fio-3.18-x64\fio"
echo "preparing..."
$filestream = New-Object System.IO.FileStream($testfile, "Create")
$binarywriter = New-Object System.IO.BinaryWriter($filestream)
$out = new-object byte[] 1048576
For ($i=1; $i -le 4096; $i++) {
(new-object Random).NextBytes($out);
$binarywriter.write($out)
}
$binarywriter.Close()
For ($i=1; $i -le 3; $i++) {
sleep 10
$time = Measure-Command {
Invoke-Expression "$fio --name=test1 --filename=$testfile --bs=1M --iodepth=1 --numjobs=1 --rw=read --direct=1 --size=4096M" *>$null
}
$seconds = $time.Minutes*60+$time.Seconds+$time.Milliseconds/1000
echo "read in $seconds"
}
foreach ($A in 50,200,800,4000) {
echo "fio: write ${A}M..."
Invoke-Expression "$fio --name=test1 --filename=$testfile --bs=4k --iodepth=1 --numjobs=1 --rw=randwrite --io_size=${A}M --randrepeat=0 --direct=1 --size=4096M" *>$null
For ($i=10; $i -le 30; $i+=10) {
echo "sleep $i"
sleep $i
$time = Measure-Command {
Invoke-Expression "$fio --name=test1 --filename=$testfile --bs=1M --iodepth=1 --numjobs=1 --rw=read --direct=1 --size=4096M" *>$null
}
$seconds = $time.Minutes*60+$time.Seconds+$time.Milliseconds/1000
echo "read in $seconds"
}
}
rm $testfile
Получил следующие результаты:
- фоновой дефрагментации в тестируемых моделях не обнаружено: скорость чтения не повышается через некоторое время после записи, в том числе длительный «отстой» (час и даже более суток) ничего не меняет, поэтому в таблице ниже привожу просто лучший результат из трёх запусков;
- под windows почему-то время чтения менее стабильно и оказалось выше ожидаемого (впрочем, возможно, дело в том, что эти сервера оказались более нагружены);
- продолжение записи сверх указанного в скрипте (перезапись файла более одного раза) не влияет на производительность.
Время чтения (в секундах) файла размером 4Гб для разных дисков:
Диск | Первое чтение после последовательного заполнения файла | После случайной записи 50Мб | +200Мб | +800Мб | +4000Мб |
---|---|---|---|---|---|
intel S3510 SSDSC2BB480G6 | 10.7 | 10.7 | 10.8 | 10.8 | 10.8 |
toshiba XG5 KXG50ZNV512G | 1.9 | 2.9 | 3.7 | 4.8 | 6.8 |
samsung PM963 MZQLW960HMJP | 2.8 | 3.2 | 3.5 | 3.7 | 4.2 |
samsung PM983 MZQLB960HAJR | 3.3 | 3.6 | 3.4 | 3.4 | 3.4 |
samsung PM981 MZVLB1T0HALR | 1.8 | 1.8 | 2.1 | 2.5 | 3.5 |
samsung PM1725b MZPLL1T6HAJQ | 1.8 | 1.9 | 2.0 | 2.3 | 2.9 |
micron 5200 eco | 9.3 | 9.8 | 10.4 | 12.2 | 10.7 |
samsung PM883 MZ7LH1T9HMLT | 7.9 | 7.9 | 8.1 | 8.1 | 8.0 |
intel P3520 (win) | 5.8 | 5.9 | 6.0 | 6.1 | 5.8 |
intel P4500 (win) | 4.2 | 4.2 | 4.3 | 4.4 | 4.3 |
Жирным отмечены DC модели (остальные — десктопные/ноутбучные); где SATA, а где NVMe, думаю, видно без пояснений.
Мы видим, что по мере случайной записи в файл у самсунга PM981 скорость чтения падала и в итоге упала вдвое (но осталась, правда, достаточно неплохой), а у единственной тошибы в таблице — вовсе в 3.5 раза, фактически сравнявшись с таковой у SATA устройств.
С другой стороны, у большинства устройств случайная запись или вовсе не повлияла на производительность, или повлияла незначительно.
Моя интерпретация этих результатов: скорость линейного чтения у SSD действительно может деградировать со временем, однако деградация, вызванная внутренней фрагментацией, не носит совсем уж фатального характера на большинстве дисков (на дисках intel, например, она вовсе незаметна; на дисках samsung если и заметна, всё равно скорость чтения остаётся вполне приемлемой).
Остаётся открытым вопрос деградирует ли скорость чтения со временем по другим причинам (например, из-за износа NAND flash).
Могу сказать про тошибу XG5: разницы в поведении между диском, на который по SMART было записано >>150Тб, и новым диском я не заметил — или 300-400 перезаписей недостаточно, чтобы износ flash стал заметен, или он вовсе не влияет на производительность SSD.
По поводу падения производительности после случайной записи: у меня как раз на такой тошибе хранится достаточно нагруженная БД mysql размером около 100Гб. Действительно, в полном соответствии с изложенными выше теорией и измерениями, скорость чтения «боевых» таблиц mysql оказалась достаточно низкой (около 600Мб/с), скорость же чтения других крупных файлов с той же файловой системы гораздо выше (>2Гб/с).
Если хочется побороть, то можно воспользоваться одним из первых методов дефрагментации: делаем бэкап, удаляем файлы, восстанавливаем из бэкапа. Недостаток этого метода в том, что он достаточно долгий и подразумевает downtime (а через некоторое время данные во флеш-памяти снова окажутся фрагментированными и всё придётся повторять сначала). Так что проще или смириться, или выбирать диски, которые не подвержены этой проблеме.
Придумал относительно быстрый способ избавиться от внутренней (и только от внутренней) фрагментации SSD:
sync
fsfreeze -f /mountpoint
dd if=/dev/nvme0n1p2 of=/dev/nvme0n1p2 bs=512M iflag=direct oflag=direct status=progress
fsfreeze -u /mountpoint
Его можно запустить на «живой» системе без размонтирования ФС и остановки сервисов. Он тоже может привести к некоторому простою из-за замораживания ФС, но при желании можно разбить его на несколько итераций, чтобы уменьшить время, на которое замораживается ввод-вывод. Есть ещё одно «но»: я не уверен на 100%, что все SSD правильно обрабатывают ситуацию «пишем нули в область, для которой до этого делали TRIM» (то есть с точки зрения накопителя области ФС, на которые ранее делали TRIM, могут теперь считаться не свободными, а занятыми данными).
В целом, рекомендация «забить смириться или выбирать диски» остаётся в силе.
Резюме: дефрагментация может быть полезна для некоторых SSD, однако не совсем такая (совсем не такая?) как для HDD. Нам важно не только то, что файл расположен в непрерывной цепочке секторов, но и то, что запись в эти секторы шла последовательно.
P.S. был бы благодарен, если бы читатели запустили скрипт у себя и привели цифры для своих SSD, так как моя выборка накопителей достаточно однобокая.