Почему мой NVMe медленнее SSD?

В данной статье мы рассмотрим некоторые нюансы подсистемы ввода-вывода и их влияние на производительность.

Пару недель назад я столкнулся с вопросом, почему NVMe на одном сервере медленнее, чем SATA на другом. Посмотрел в характеристики серверов и понял, что это был вопрос с подвохом: NVMe был из пользовательского сегмента, а SSD — из серверного.

Очевидно, что сравнивать продукты из разных сегментов в разном окружении некорректно, но это не является исчерпывающим техническим ответом. Изучим основы, проведем эксперименты и дадим ответ на поставленный вопрос.

Что такое fsync и где он используется?

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

Существует ряд задач, в которых необходимо быть уверенным, что изменения в файле записаны на накопитель, а не лежат в промежуточном буфере. Эту уверенность можно получить при использовании POSIX-совместимого системного вызова fsync. Вызов fsync инициирует принудительную запись из буфера на накопитель.

Продемонстрируем влияние буферов искусственным примером в виде короткой программы на языке C.

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>


int main(void) {
    /* Открываем файл answer.txt на запись, если его нет -- создаём */
    int fd = open("answer.txt", O_WRONLY | O_CREAT);
    /* Записываем первый набор данных */
    write(fd, "Answer to the Ultimate Question of Life, The Universe, and Everything: ", 71);
    /* Делаем вид, что проводим вычисления в течение 10 секунд */
    sleep(10);
    /* Записываем результат вычислений */
    write(fd, "42\n", 3); 


    return 0;
}

Комментарии хорошо объясняют последовательность действий в программе. Текст «ответ на главный вопрос жизни, Вселенной и всего такого» будет буферизирован операционной системой, и если перезагрузить сервер нажатием на кнопку Reset во время «вычислений», то файл окажется пуст. В нашем примере потеря текста не является проблемой, поэтому fsync не нужен. Базы данных такого оптимизма не разделяют.

Базы данных — это сложные программы, которые одновременно работают с множеством файлов, поэтому хотят быть уверенными, что записываемые ими данные будут сохранены на накопителе, так как от этого зависит консистентность данных внутри БД. Базы данных спроектированы записывать все завершенные транзакции и быть готовыми к отключению питания в любой момент. Такое поведение обязывает использовать fsync постоянно в больших количествах.

На что влияет частое использование fsync?

При обычном вводе-выводе операционная система старается оптимизировать общение с дисками, так как в иерархии памяти внешние накопители самые медленные. Поэтому операционная система старается за одно обращение к накопителю записать как можно больше данных.

Продемонстрируем влияние использования fsync на конкретном примере. В качестве испытуемых у нас следующие твердотельные накопители:

  • Intel® DC SSD S4500 480 GB, подключен по SATA 3.2, 6 Гбит/с;
  • Samsung 970 EVO Plus 500GB, подключен по PCIe 3.0 x4, ~31 Гбит/с.

Тесты проводятся на Intel® Xeon® W-2255 под управлением ОС Ubuntu 20.04. Для тестирования дисков используется sysbench 1.0.18. На дисках создан один раздел, отформатированный как ext4. Подготовка к тесту заключается в создании файлов объемом в 100 ГБ:

sysbench --test=fileio --file-total-size=100G prepare

Запуск тестов:

# Без fsync
sysbench --num-threads=16 --test=fileio --file-test-mode=rndrw --file-fsync-freq=0 run
# С fsync после каждой записи
sysbench --num-threads=16 --test=fileio --file-test-mode=rndrw --file-fsync-freq=1 run
Тест Intel® S4500 Samsung 970 EVO+
Чтение без fsync, МиБ/с 5734.89 9028.86
Запись без fsync, МиБ/с 3823.26 6019.24
Чтение с fsync, МиБ/с 37.76 3.27
Запись с fsync, МиБ/с 25.17 2.18

Нетрудно заметить, что NVMe из клиентского сегмента уверенно лидирует, когда операционная система сама решает, как работать с дисками, и проигрывает, когда используется fsync. Отсюда возникает два вопроса:

  1. Почему в тесте без fsync скорость чтения превышает физическую пропускную способность канала?
  2. Почему SSD из серверного сегмента лучше обрабатывает большое количество запросов fsync?

Ответ на первый вопрос прост: sysbench генерирует файлы, заполненные нулями. Таким образом, тест проводился над 100 гигабайтами нулей. Так как данные весьма однообразны и предсказуемы, в ход вступают различные оптимизации ОС, и они значительно ускоряют выполнение.

Если ставить под сомнение все результаты sysbench, то можно воспользоваться fio.

# Без fsync
fio --name=test1 --blocksize=16k --rw=randrw --iodepth=16 --runtime=60 --rwmixread=60 --fsync=0 --filename=/dev/sdb


# С fsync после каждой записи
fio --name=test1 --blocksize=16k --rw=randrw --iodepth=16 --runtime=60 --rwmixread=60 --fsync=1 --filename=/dev/sdb
Тест Intel® S4500 Samsung 970 EVO+
Чтение без fsync, МиБ/с 45.5 178
Запись без fsync, МиБ/с 30.4 119
Чтение с fsync, МиБ/с 32.6 20.9
Запись с fsync, МиБ/с 21.7 13.9

Тенденция к просадке производительности у NVMe при использовании fsync хорошо заметна. Можно переходить к ответу на второй вопрос.

Оптимизация или блеф?

Ранее мы говорили, что данные хранятся в буфере, но не уточняли в каком именно, так как это было не принципиально. Мы и сейчас не будем углубляться в тонкости операционных систем и выделим два общих вида буферов:

  • программный;
  • аппаратный.

Под программным буфером подразумеваются буферы, которые есть в операционной системе, а под аппаратным — энергозависимая память контроллера диска. Системный вызов fsync посылает накопителю команду записать данные из его буфера в основное хранилище, но никак не может проконтролировать корректность выполнения команды.

Так как SSD показывает лучшие результаты, то можно сделать два предположения:

  • диск спроектирован под нагрузку подобного плана;
  • диск «блефует» и игнорирует команду.

Нечестное поведение накопителя можно заметить, если провести тест с исчезновением питания. Проверить это можно скриптом diskchecker.pl, который был создан в 2005 году.

Данный скрипт требует две физические машины — «сервер» и «клиент». Клиент записывает на тестируемый диск небольшой объем данных, вызывает fsync и отправляет серверу информацию о том, что было записано.

# Запускается на сервере
./diskchecker.pl -l [port]


# Запускается на клиенте
./diskchecker.pl -s <server[:port]> create <file> <size_in_MB>

После запуска скрипта необходимо обесточить «клиента» и не возвращать питание в течение нескольких минут. Важно именно отключить тестируемого от электричества, а не просто выполнить жесткое выключение. По прошествии некоторого времени сервер можно подключать и загружать в ОС. После загрузки ОС необходимо снова запустить diskchecker.pl, но с аргументом verify.

./diskchecker.pl -s <server[:port]> verify <file>

В конце проверки вы увидите количество ошибок. Если их 0, то значит, диск выдержал испытание. Для исключения удачного для диска стечения обстоятельств опыт можно повторить несколько раз.

Наш S4500 не показал ошибок при потере питания, то есть можно утверждать, что он готов к нагрузкам с большим количеством вызовов fsync.

Заключение

При выборе дисков или целых готовых конфигураций следует помнить о специфике задач, которые необходимо решить. На первый взгляд кажется очевидным, что NVMe, то есть SSD с PCIe-интерфейсом, быстрее «классического» SATA SSD. Однако, как мы поняли сегодня, в специфических условиях и с определенными задачами это может быть не так.