программы
1864
2
27 февраля 2023
программы

Тестирование блочных систем хранения данных: взгляд перфоманс инженера

Изображение создано
с помощью нейросети
Изображение создано с помощью нейросети
1864
2
27/02/2023

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

Сергей Качкин — перформанс инженер, консультант с опытом отладки и тестирования производительности приложений на Linux и систем хранения данных. Он 12 лет проработал в HP — начинал с установки и тестирования СХД, затем работал инженером в L3 поддержки ядра операционной системы HP-UX, занимался производительностью ядра, файловых систем и менеджеров томов. Затем руководил отделом технической экспертизы в YADRO, где вместе с командой автоматизировал процесс тестирования, отладки и настройки высокопроизводительных серверов и систем хранения данных. В своей статье Сергей рассказывает об особенностях тестирования производительности блочных систем хранения данных.

Выбор цели теста

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

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

В тестирование лучше не лезть, если:

  • вы не знаете, что конкретно вы хотите;
  • вы хотите с помощью синтетики имитировать поведение сложного приложения (например, базы данных);
  • вы хотите воспроизвести проблему с продуктива в тестовом окружении c помощью синтетики.

Часто в такой ситуации вы думаете: «Ага, сейчас я воспроизведу её в тестовом окружении и спокойно, не торопясь, отдебажу». Но вместо решения вы можете получить две непонятных нагрузки: одну на продуктиве, вторую в тестовом окружении. Они будут совершенно разными по природе, но похожими по симптому. Законы Мёрфи тут срабатывают удивительно надёжно. Синтетические тесты плохо воспроизводят реальность. По субъективному опыту, лучше окунуться в трассировку запросов, статистику и логи с продакшена.

Важно

Отличия синтетики от реальных нагрузок

Почему синтетика не воспроизводит реальность? На самом деле, проблемы всего две: пространство и время.

Скорость поступления запросов реальных приложений

Время — это интервал между запросами. В синтетике скорость поступления запросов — это всегда какой-то случайный процесс с известным распределением: либо это равномерное распределение, либо это пуассоновский процесс, но это всегда что-то определённое.
В реальной жизни, если верить исследователям, скорость поступления запросов — это самоподобный процесс. Это значит, что в нём случается много выбросов, вне зависимости от масштаба выборки. Предположу, что возникают они из-за того, что в системах есть точки синхронизации. В файловых системах — fsync (), в базах данных — чекпоинты, когда большое количество блоков сливается в СХД разом. Эти выбросы в реальной жизни часто являются источником проблем — роста времени отклика, переполнения очередей и так далее.

Пятно данных

Вторая проблема — это пятно данных. Опять же, в синтетике мы чаще всего обращаемся к блокам случайно по заданному пятну данных или всей поверхности. В реальной жизни, если верить разработчикам теста SPC, вероятность повторного обращения к блоку зависит от времени с момента первого обращения. Если мы что-то «потрогали», скорее всего, мы сразу повторим это, а если нет, вероятность сильно снижается.
Однако, даже зная то, что синтетика не воспроизводит реальную жизнь, всё равно есть смысл ею заниматься: системы хранения данных разрабатываются, продаются и принимаются с синтетикой, и только потом — уже у заказчика — они встречаются с реальной жизнью.

Выбираем инструмент

Чем чаще всего пользуются инженеры, когда начинают тестировать диски? Классика — dd, cp и им подобные инструменты. Это плохо работает: они однопоточные, умеют воспроизводить только последовательные паттерны и дают крайне мало статистики на выходе.

Нагружайте СХД специальными утилитами. Их много, однако почти все давно заброшены. Из того, что можно использовать, и того, что применяем и мы, и заказчики, остались только fio и Vdbench.

Vdbench

Vdbench — инструмент, который хорошо прижился в энтерпрайзе. Многие наши заказчики тестируют с его помощью свои дисковые массивы, мы тоже его используем. Он логично сделан, у него понятная структура настроек, он кроссплатформенный, на нём можно компоновать сложные составные паттерны.
Но есть и минусы. Он умер в 2008 году. Второй недостаток (если первого недостаточно) — в нём нет асинхронного ввода-вывода. Тем не менее, Vdbench используется в повседневной работе — и у нас, и у заказчиков.

fio

Де-факто стандартом для тестирования блочного доступа является fio. В 2023 году это единственное, что стоит использовать. Его автор — Йенс Аксбо (Jens Axboe) — тот самый инженер, который разрабатывает блочный стек Linux. Соответственно, все новые фичи Linux сразу поддерживаются в fio. Инструмент гибкий, в нём много опций логирования и отчетов, к нему много готовых обёрток и парсеров, он постоянно дорабатывается.
Из минусов — в fio легче запутаться в настройках. В нём меньше возможностей, чтобы делать составные нагрузки. Вы не всегда сможете полностью перенести проект из Vdbench. Есть шанс налететь на баг, особенно в варианте клиент-сервер.

Тестирование случайного IO

С инструментом определились, теперь поговорим про сами нагрузки. Исторически они разделяются на случайный доступ и последовательный (тяжелое наследие механических жестких дисков, в которых паттерн влияет на скорость). Сейчас чаще всего мы встречаемся со случайным доступом. Это типовая нагрузка для SSD-дисков. Основные целевые приложения — базы данных. Они требовательны ко времени отклика, и их часто размещают на SSD.
Как правило, это мелкий блок (4k-32k) и какое-то соотношение чтение-запись (например, 70/30). Размером пятна данных мы регулируем количество кэш-хитов.
Частный случай — если надо проверить пропускную способность СХД с SSD дисками. Тут можно сделать большой блок (>128КБ).

Выбираем размер очереди

Есть один параметр, на который стоит обратить внимание — глубина очереди (IO depth). Её указывают во всех тестах, но никто не объясняет, как выбрано значение. Если вы возьмёте любой бенчмарк дисков, там будет написано: «Протестировано с такой-то глубиной очереди». Но почему именно такой?
Глубина очереди связана с результатом по закону Литтла:

IO depth = IOPS * Latency

Мы задаём на входе теста глубину очереди и на выходе получаем какое-то произведение IOPS и времени отклика (latency). Например, у нас есть требование: 100K IOPS со временем отклика 1 мс.

  • cлучайная нагрузка, 8KiB, чтение/запись = 70/30,
  • 100 000 IOPS,
  • Время отклика — 1 мс.

Считаем очередь по закону Литтла:

100 000 IO/sec * 0.001 sec = 100 IO

Значит очередь должна быть 100 — ни больше, ни меньше. Дальше мы запускаем тест с очередью 100 и смотрим, получится ли 100 000 IOPS. Может получиться:

  • 200k IOPS, latency 0.5 ms,
  • 10K IOPS, latency = 10 ms,
  • 50K IOPS, latency = 2 ms.

Также закон Литтла можно переписать иначе:

IOPS = IO depth / latency

И это тоже может стать основой теста. Мы сообщаем fio или Vdbench, сколько IOPS мы хотим получить, и утилита сама подберёт за нас нужную очередь. Такой подход «наоборот» тоже полезен. Он хорошо подходит для длительных тестов. Допустим, просим 100K IOPS и оставляем на неделю. Или начинаем делать СХД «больно» разными способами. В таком тесте хорошо видно влияние фоновых процессов, скрабберов, ребилда пула или влияние отказа компонентов, репликации и снэпшотов. Это удобно: мы видим, насколько отказы и дополнительные функции влияют на время отклика.

Измеряем масштабируемость дисковой подсистемы

Что делать, когда требования по производительности не сформулированы? Допустим, запрос на тестирование от заказчика или руководителя звучит просто и лаконично: «Надо измерить производительность стораджа». Измерение масштабируемости может помочь решить задачку.
Из теории мы знаем, что при росте нагрузки в какой-то момент производительность (IOPS) расти перестанет, а будет увеличиваться только время отклика (latency). Причём для разных конфигураций СХД насыщение будет наступать в разные моменты.

Поэтому когда нет чёткой цели на тестирование, будем увеличивать очередь и найдём область насыщения. В качестве производительности СХД будем показывать точку 95% утилизации системы и точку, где время отклика не превышает 1 мс. Тут нет тайного смысла, просто надо выбрать что-то конкретное и заданное в цифрах.
Для поиска этих точек и визуализации удобно использовать Universal Scalability Law. Это расширение закона Амдала, в который добавлен еще один компонент. Статья-первоисточник про USL выложена тут. Есть готовые проекты на R (и судя по всему, исследователи предпочитают использовать именно его — все последние апдейты заезжают в проект на R). Есть и проект на Python, тоже вполне рабочий.

Суть в том, что вы делаете несколько обмеров с разной глубиной очереди, дальше готовая библиотека вычисляет коэффициенты alpha, beta, gamma, которые описывают график.

На графике вы наглядно видите, в каком диапазоне очередей у вас система масштабируется, а где она уходит в насыщение (т.е. где IOPS перестают расти). В тестировании web-сервисов, насколько я слышал, часто увеличивают нагрузку до некоторого процента отказов в обслуживании. К дискам такое неприменимо, отказов быть не должно никогда.

Важный момент, который надо проговорить: мы тестируем СХД целиком, а не отдельный диск. СХД — это софт, шины, кэши, медиа. В этом тесте мы не всегда требуем, чтобы диски были в ''steady state'' и считаем, что они FOB (fresh out of box). То есть на выходе мы получаем максимальные цифры, которые позволяет СХД. Если мы хотим более приближенные к жизни цифры, надо делать поправку на характеристики дисков с учётом числа, модели конкретного SSD и следовать методике SNIA.

Тест устоявшегося состояния СХД осложняется тем, что у нас нет прямого доступа к SSD диску, чтобы гарантированно прописать весь объем SSD с нужной интенсивностью. СХД может резервировать часть пространства под spare, и гарантированно прописать весь объем SSD не получится. Тестирование устоявшегося состояния в СХД с SSD — тема для отдельной статьи. Вывод: результат теста мы считаем максимальным «попугаем», на который способна СХД.

Тестирование последовательного доступа

Тест потоковых нагрузок — чаще всего просто головная боль. Этот тип нагрузок характерен для механических жестких дисков. Это бэкапы, стриминг медиа, архивы. Это всегда что-то большим блоком — 128K-1024K. Если у вас случайная нагрузка, то скорее всего вам понадобятся SSD, если потоковая, то можно рассматривать обычные механические жесткие диски.

Один некорректно сделанный тест может повлечь за собой снежный ком других неверных технических решений — в процессе проектирования архитектуры или поиска проблем при отладке. А это сотни потраченных впустую человеко-часов.

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

Появляется вариативность настроек, из-за которой приходится делать исследовательскую работу. По-хорошему, для каждого теста надо уточнять, что именно считается потоковой нагрузкой в конкретном случае.
Часто требование для резервного копирования формулируется просто в МБ/c, например, «надо 3 000 МБ/c». Но на практике это 10 потоков по 300 МБ/с, или 100 по 30? Для СХД это две большие разницы. Как следствие, для потоковых нагрузок подходят две метрики.

  1. Скорость на один поток в МБ/c. Определяется временем отклика СХД.
  2. Общая пропускная способность на все потоки в МБ/c.

Потоковая нагрузка и самая частая ошибка с fio

Популярная ошибка с потоковой нагрузкой на сырых устройствах и fio — это увеличивать количество потоков, не разнося их по пятну данных. Это много где описано, но все всё равно наступают на эти грабли (и мы тоже).

Пятно данных обоих потоков будет выглядеть вот так:

Так делать не надо. Когда вы задаете в fio несколько потоков, они все начинают читать с начала диска. Соответственно, при первом обращении данные попадут в кэш. Вы получите кэш-хиты там, где вы их не ждали. Результат будет искажённый. Как сделать лучше? Например, так.

Пятно данных выглядит примерно так.

Через 500 запросов каждый поток «прыгает» в случайный блок на диске. Таким образом есть надежда, что они не пересекутся. Вот ещё один пример.

Получается пять потоков fio, равномерно распределённых по поверхности.

Мы делим дисковое пространство на 5 областей (size=20%) и запираем каждый поток в свою область. Таким образом, мы покрываем все пространство диска. Это более предсказуемый паттерн.

Если нужна файловая система, то каждому потоку даем свой набор файлов, который они будут заполнять.

Моё личное мнение про файловую систему — если есть возможность, лучше её не использовать. Она добавляет сложных компонентов: файловый кэш, журнал транзакций, алгоритмы оптимизации (read ahead, flush behind). Кроме того, невозможно проконтролировать, к каким блокам на диске мы обращаемся. Всё это будет искажать результаты.

На эту тему есть статья Benchmarking file system benchmarking: it *IS* rocket science, где наглядно описано, что все существующие бенчмарки файловых систем не очень хорошо работают. Поэтому, если условия позволяют упростить тест, лучше делать его на сырых устройствах.

От простых нагрузок больше пользы

Когда говорят о тестировании сложных СХД, часто рекомендуют делать составные нагрузки, имитирующие сложные приложения (OLTP, OLAP, VDI, и т. д.). Например:

  • «горячие» и «холодные» зоны на диске,
  • смесь блоков разных размеров,
  • смесь последовательного и случайного доступа.

По моему субъективному опыту, от простых однокомпонентных нагрузок пользы больше. Да, это меньше похоже на реальные приложения и «продаётся» хуже. Если вы инженер и тестируете хранилище, от вас будут просить более реалистичные сценарии, составные паттерны. Все хотят видеть, как на вашей системе будет работать какой-нибудь Postgres, а не ваши 100 000 IOPS на странной синтетике 70/30 8K.

У меня есть несколько аргументов против составных нагрузок. Во-первых, это всё равно не будет похоже на реальное приложение, как я писал выше. Во-вторых, сложно анализировать, как это всё работает внутри СХД. В-третьих, такую нагрузку сложнее масштабировать. Не всегда можно корректно добавить 10% к напору. И последнее — если это «продающий» бенчмарк, который вы используете в маркетинге, то будут возникать сомнения, что нагрузку подогнали под нужный результат.

Наверх

2 комментария

  • Сергей спасибо!
    Очень интересная статья!
    Понравилось описание тестирования с примерами, стало понятнее.
    Не совсем понял только почему в ваших примерах iodepth=какому-то числу, те мы ищем точку насыщения по latency? Ведь при выборе iodepth мы ограничиваем себя по latency сверху. Как я понял iodepth подбирается/должна подбираться автоматически (как в fio это выглядит?) чтобы узнать сколько IOPS будет при времени отклика например 1мс. И как iodepth в fio соотносится с реальной нагрузкой, например какую длину очереди ставить при последовательной или OLTP нагрузке или классической 70/30 8к. Или опять же мы толкаемся только от приемлемой latency?

    Еще очень интересна тема как сайзить массив, потому что раньше были вендорские сайзеры, а сейчас считаю по старинке вручную, толкаясь от типа диска и рейд группы. И при этом не вчитывается ни кэш, ни дедуп/компрессия (онлайн), и в итоге те показатели что раньше давал сайзер, теперь при пересчете вручную получается что нужно чуть ли не в два раза больше дисков, хотя в тесте я выходил на то что выдал сайзер.
    Ну и конечно очень интересна тема перформанса, как и что влияет и что от чего зависит.

    Спасибо!

  • Спасибо за коментарий! Iodepth в fio чаще всего задаётся вручную и да, часто всё сводится к точке насыщения по latency.

    На реальной нагрузке глубина очереди также работает, как и в синтетике, только определяется архитектурой приложения. В iostat/sar/ и в большинстве мониторинговых агентов можно на живой системе посмотреть текущую глубину очереди.
    Там работают те же правила.

    Автоматический подбор iodepth существует и в fio, но не видел, чтобы в fio его использовали много.

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