Производительность накопителей и СХД
Эффективная работа с системами хранения данных требует взвешенных решений и оптимальных настроек. Одним из ключевых критериев при выборе моделей накопителей и конфигураций является производительность. Чтобы точно оценить производительность конкретной модели или конфигурации в условиях вашего приложения, необходимо уметь самостоятельно проводить тестирование производительности, а не полагаться исключительно на сторонние тесты. Также очень важно уметь правильно интерпретировать результаты тестов, оценивать их корректность и применимость для своих задач. Здесь мы обсудим основные метрики и понятия, относящиеся к производительности блочных СХД и накопителей, познакомимся с инструментами тестирования и запустим наш первый тест.
Основные понятия и метрики
- Ввод/Вывод (Input/Output, I/O) — процесс перемещения данных между хранилищем (СХД или накопителем) и вычислительной средой — сервером или клиентом. Он охватывает как чтение данных с хранилища, так и запись.
- Полоса пропускания (bandwidth, BW) — это теоретический максимум скорости, с которой данные могут передаваться между вычислительной средой и хранилищем. Как правило, полоса пропускания ограничивается пропускной способностью шины, через которую проходят данные. Например, производительность SATA SSD часто ограничивается полосой пропускания шины SATA, составляющей 6 Гбит/с.
- Пропускная способность (throughput) — это объем данных, которое хранилище может обработать за единицу времени. Часто используется взаимозаменяемо с полосой пропускания, но означает реальную скорость, а не теоретический максимум. Обычно является одной из ключевых метрик производительности, особенно в тех случаях, когда необходимо протестировать скорость работы с большими потоками данных — резервное копирование, запись или чтение больших файлов, видеонаблюдение и т.д.
- Размер блока (block size, I/O size) — у этого понятия может быть несколько значений, когда дело касается хранения данных. В контексте тестирования производительности, для нас первостепенным будет размер блока, которым оперирует приложение, работающее с хранилищем. Зачастую приложение может для разных функций использовать разный размер блока. Хороший пример такого приложения — MS SQL Server и все многообразие операций, которые выполняются с разным размером блока ввода-вывода.

- IOPs — количество операций ввода-вывода, которое может обработать хранилище. Еще одна ключевая метрика производительности. Используется в случае, когда хранилище задействовано под работу приложений с транзакционным характером нагрузки. Отношение между тремя основными метриками можно выразить формулой:
Пропускная способность = Размер блока x IOPs
- Задержка (latency, wait time) — в случае тестирования СХД, задержка — это время, которое тратится на выполнение одной команды ввода-вывода со стороны приложения. Это третья ключевая метрика при измерении производительности и зачастую самая заметная.
- Глубина очереди (queue depth) — максимальное количество команд ввода-вывода, которое может быть поставлено в очередь на стороне хранилища. Как правило, более глубокая очередь позволяет достигать более высокой пропускной способности. У разных типов устройств разная глубина очереди — у SATA дисков 32 команды, у SAS — 256, а стандарт NVMe предполагает до 64k очередей глубиной до 64k для накопителей NVMe. У СХД глубина очереди является справочным параметром, который указывается в документации. Важно, чтобы суммарная очередь команд со всех хостов, работающих с портом СХД, не превышала глубину очереди этого порта.
- Перцентиль (percentile) — это статистическая характеристика, описывающая распределение метрик, и обычно применяется для анализа задержек в системах. Например, если по результатам тестирования выяснено, что 99-й перцентиль задержек составляет 2 мс, это означает, что 99% всех измеренных задержек были менее 2 мс, и только 1% задержек превышали это значение. Этот показатель особенно важен для приложений, чувствительных к задержкам, поскольку он позволяет получить представление не только о средней величине задержек, но и о том, как часто система превышает определенные пороговые значения. Таким образом, перцентили дают более полное представление о производительности системы в условиях реальной нагрузки, что позволяет лучше управлять ожиданиями пользователей и оптимизировать работу приложений.
<<<< график перцентилей — bar chart, экспонента или bell curve >>>>
- Случайная нагрузка (random workload) — данные читаются или пишутся в случайные или псевдослучайные адреса на хранилище. Такая нагрузка характерна для нагруженных виртуальных сред, серверов транзакционных баз данных. Наиболее стрессовый сценарий работы практически для любого типа хранилища.
- Последовательная нагрузка (sequential workload) — нагрузка, при которой каждый последующий запрос ввода-вывода начинается с того блока, которым закончился предыдущий, то есть адреса нужных блоков следуют друг за другом. В реальных условиях последовательная нагрузка встречается при работе с большим размером блока. Этот тип нагрузки является более щадящим к ресурсам хранилища и позволяет достигать лучших показателей пропускной способности.
Инструменты для тестирования: от простых к сложным
Самыми базовыми инструментами для создания нагрузки могут быть команды операционной системы для копирования файлов, dd и программы типа CrystalDiskMark. Нагрузка, которую они создают, слишком проста для тестирования СХД или серверных накопителей, но вполне может использоваться для проверки работы накопителей в домашних условиях. Для оценки поведения дисков под нагрузкой под Linux можно использовать iostat -x, а в Windows воспользоваться Resource Manager. Этого достаточно для получения базовой информации — нагрузки на диск на чтение или запись в МБ/с или в количестве операций ввода-вывода, задержках и глубине очереди. Этих параметров зачастую достаточно даже для тестирования серьезных систем, поэтому изучить iostat и Resource Manager полезно в любом случае.
Основная пара инструментов для создания нагрузки в корпоративной среде — fio и Vdbench. Оба пакета позволяют создавать сложные и многообразные сценарии тестирования, с помощью которых можно протестировать поведение СХД или накопителя под разнообразной нагрузкой. Оба инструмента мультиплатформенные, при этом Vdbench требует установки Java. В этой и дальнейших статьях мы будет использовать fio, как более популярный и активно поддерживаемый инструмент. Графическая альтернатива под Windows — пакет IOMeter. Он давно не обновляется, но по-прежнему может быть использован для тестов, хотя функционально и уступает fio.
Отдельного упоминания заслуживает компонент ядра Linux под названием blktrace. Автором этой утилиты является Jens Axboe — maintainer уровня блочного хранения в Linux и автор fio. Blktrace позволяет получить из ядра детальную информацию о прохождении каждой операции ввода-вывода и сохранить эту информацию. Blktrace совместно с fio позволяет получить слепок реальной нагрузки и воспроизвести его в удобное время на той же или другой машине.
Первый тест производительности
Теперь настало время применить полученные навыки на практике и протестировать производительность чего-нибудь. Перед проведением теста всегда полезно сформулировать его цель. В нашем случае мы будем тестировать внутренний NVMe накопитель в ноутбуке, чтобы:
- Научиться с помощью iostat наблюдать за нагрузкой на диск;
- Освоить использование конфигурационных файлов fio и основные ключевые слова;
- Ознакомиться с выводом информации fio по ходу теста и после его окончания;
- Проверить спецификации нашего накопителя на соответствие заявленным вендором.
Подготовительный этап: устанавливаем пакеты fio и sysstat.
# apt install fio sysstat
В отдельном окне терминала начинаем наблюдать за дисками при помощи iostat. Расширенная (ключ -x) информация по загрузке дисков (ключ -d) будет обновляться каждую секунду.
# iostat -dx 1
Теперь необходимо создать нагрузку. Для этого мы создадим конфигурационный файл fio:
# cat seq1.fio [seq-read] rw=read bs=1024k filename=/dev/nvme0n1 runtime=180
Запускаем генератор нагрузки:
# fio -f seq1.fio seq-read: (g=0): rw=read, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=1 fio-3.28 Starting 1 process Jobs: 1 (f=1): [R(1)][14.4%][r=1367MiB/s][r=1367 IOPS][eta 02m:34s]
Остановимся на минуту и разберем вывод fio:
- seq-read — это название нашей задачи из конфигурационного файла
- rw=read — это тип нагрузки — последовательное чтение
- bs=1024KiB — размер блока
- ioengine=psync — это движок генерации нагрузки. Используемый по умолчанию psync — это синхронный ввод-вывод, означающий, что приложение дожидается получения ответа на запрос, прежде чем отправлять следующую команду.
- iodepth=1 — это глубина очереди с точки зрения fio. Глубину очереди со стороны ядра OS мы увидим в выводе iostat.
- дальше идет статистика по текущей задаче, где мы видим текущие показатели 1367 MiB/s пропускную способность и 1367 IOPs, что разумно, учитывая, что мы работаем с блоком 1024k
Для нашего устройства PM981a производитель Samsung заявляет скорость чтения до 3500 MB/s (3337 MiB/s). Наши тесты пока не в состоянии достаточно нагрузить накопитель, и это результат использования синхронного I/O. Для получения более высоких показателей нам необходимо сменить движок и увеличить глубину очереди. Также, обнаруживаем в man следующую информацию:
“Even async engines may impose OS restrictions causing the desired depth not to be achieved. This may happen on Linux when using libaio and not setting direct=1, since buffered IO is not async on that OS.”
То есть, для работы в режиме асинхронного ввода-вывода, который позволит нам получить высокую производительность, нам необходимо включить флаг direct=1.
# cat seq2.fio [seq-read-aio] ioengine=libaio direct=1 iodepth=16 rw=read bs=1024k filename=/dev/nvme0n1 runtime=180
# fio -f seq2.fio seq-read-aio: (g=0): rw=read, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=libaio, iodepth=32 fio-3.28 Starting 1 process Jobs: 1 (f=1): [R(1)][5.6%][r=1752MiB/s][r=1752 IOPS][eta 02m:50s]
Во время проверки мы видим, что движок сменился, глубина очереди выставлена, скорость подросла до 1750 MiB/s, но этого все равно недостаточно. Для серверных тестов рекомендуется проверить настройки BIOS, касающиеся энергосбережения и работы PCIe. Мы проводим тесты на лаптопе, поэтому пробуем перейти с питания на батареях на подключение к электросети и перезапускаем тест:
# fio -f seq2.fio seq-read-aio: (g=0): rw=read, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=libaio, iodepth=32 fio-3.28 Starting 1 process Jobs: 1 (f=1): [R(1)][4.4%][r=3338MiB/s][r=3338 IOPS][eta 02m:52s]
В соседнем окошке смотрим вывод iostat -dx 1
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util nvme0n1 6585,00 3537404,00 0,00 0,00 9,15 537,19 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 60,27 100,00
Нас интересуют:
- показатель пропускной способности rkB/s = 3537404 — наши искомые 3500 MB/s
- Aqu-sz = 60.27 — средний размер очереди с точки зрения ядра. Мы видим, как наша желаемая очередь iodepth=32 с блоком 1024k была преобразована движком libaio и ядром в средний размер io (rareq-sz) 537,19k и среднюю очередь 60.27. Очевидно, размер блока слишком велик, чтобы уместить его в одну операцию ввода-вывода.
- Параметр r_await, показывающий задержку, колеблется в районе 9 мс. Это очень высокая задержка для NVMe, даже при работе с большим блоком.
Попробуем немного оптимизировать наш конфигурационный файл, уменьшив размер блока до 256k:
# cat seq3.fio [seq-read-aio-256k] ioengine=libaio direct=1 iodepth=32 rw=read bs=256k filename=/dev/nvme0n1 runtime=180
# fio -f seq3.fio seq-read-aio-256k: (g=0): rw=read, bs=(R) 256KiB-256KiB, (W) 256KiB-256KiB, (T) 256KiB-256KiB, ioengine=libaio, iodepth=32 fio-3.28 Starting 1 process Jobs: 1 (f=1): [R(1)][5.0%][r=3386MiB/s][r=13.5k IOPS][eta 02m:51s]
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util nvme0n1 12960,00 3539200,00 0,00 0,00 2,44 256,02 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 0,00 31,57 100,00
Наконец, мы получили достойный результат — блок 256k не дробится на более мелкие части, мы получаем ожидаемую очередь в 32 команды, ну и задержка уменьшилась в 4 раза до 2.4 мс.
Осталось только дождаться окончания работы трехминутной задачи fio и посмотреть финальные данные:
seq-read-aio-256k: (groupid=0, jobs=1): err= 0: pid=16405: read: IOPS=13.4k, BW=3357MiB/s (3520MB/s)(590GiB/180003msec) slat (usec): min=10, max=637, avg=27.14, stdev= 8.91 clat (usec): min=1238, max=8001, avg=2348.74, stdev=253.35 lat (usec): min=1268, max=8179, avg=2377.61, stdev=253.37 clat percentiles (usec): | 1.00th=[ 2180], 5.00th=[ 2245], 10.00th=[ 2278], 20.00th=[ 2278], | 30.00th=[ 2278], 40.00th=[ 2278], 50.00th=[ 2311], 60.00th=[ 2311], | 70.00th=[ 2311], 80.00th=[ 2343], 90.00th=[ 2442], 95.00th=[ 2573], | 99.00th=[ 3294], 99.50th=[ 4146], 99.90th=[ 5735], 99.95th=[ 6456], | 99.99th=[ 7046] bw ( MiB/s): min= 1601, max= 3461, per=100.00%, avg=3359.75, stdev=183.91, samples=359 iops : min= 6404, max=13844, avg=13438.97, stdev=735.63, samples=359 lat (msec) : 2=0.08%, 4=99.37%, 10=0.56% cpu : usr=4.67%, sys=54.93%, ctx=2144295, majf=0, minf=2060 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=100.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.1%, 64=0.0%, >=64=0.0% issued rwts: total=2417124,0,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=32 Run status group 0 (all jobs): READ: bw=3357MiB/s (3520MB/s), 3357MiB/s-3357MiB/s (3520MB/s-3520MB/s), io=590GiB (634GB), run=180003-180003msec Disk stats (read/write): nvme0n1: ios=2415675/392, merge=0/319, ticks=5683252/1138, in_queue=5684495, util=100.00%
Здесь стоит обратить внимание в первую очередь на перцентили задержек. Мы видим, что 95% команд были выполнены в пределах 2573 мкс (2.6 мс), что совпадает с тем, что мы наблюдали в выводе iostat. Остальные показатели тоже совпадают с нашими ожиданиями — 3520 MB/s пропускной способности при последовательном чтении блоком 256k с глубиной очереди 32. С точки зрения вычислительных ресурсов, такой тест потребовал примерно 60% загрузки одного из ядер CPU.
На этом считаем наш тест успешно завершенным. Тестирование на запись проводить не будем, поскольку это деструктивное действие, кроме того, на SSD накопителях для домашнего использования активные тесты на запись могут серьезно уменьшить ресурс флеш-памяти.
Заключение
Мы ознакомились с азами тестирования производительности — необходимыми для понимания терминами, инструментариями и провели простой, но эффективный тест NVMe накопителя. Безусловно, этой информации недостаточно для того, чтобы считаться специалистом по производительности, но это основа для начала самостоятельных исследований.
Автор: Олег Ларин
Добавить комментарий
Комментариев пока нет