Как с fio проверить диски на достаточную производительность для etcd

Прим. перев.: эта статья — итоги мини-исследования, проведенного инженерами IBM Cloud в поисках решения реальной проблемы, связанной с эксплуатацией базы данных etcd. Для нас была актуальна схожая задача, однако ход размышлений и действий авторов может быть интересен и в более широком контексте.

Как с fio проверить диски на достаточную производительность для etcd

Краткое резюме всей статьи: fio и etcd

Производительность кластера etcd сильно зависит от скорости хранилища, лежащего в его основе. Для контроля за производительностью etcd экспортирует различные метрики Prometheus. Одной из них является wal_fsync_duration_seconds. В документации к etcd говорится, что хранилище можно считать достаточно быстрым, если 99-й процентиль этой метрики не превышает 10 мс…

Если вы обдумываете возможность организации кластера etcd на машинах под управлением Linux и хотите проверить, достаточно ли быстры накопители (например, SSD), рекомендуем воспользоваться популярным тестером I/O под названием fio. Достаточно запустить следующую команду (директория test-data должна быть расположена в примонтированном разделе тестируемого накопителя):

fio --rw=write --ioengine=sync --fdatasync=1 --directory=test-data --size=22m --bs=2300 --name=mytest

Осталось лишь посмотреть на вывод и проверить, укладывается ли 99-й процентиль fdatasync в 10 мс. Если это так, значит ваш накопитель работает достаточно быстро. Вот пример вывода:

fsync/fdatasync/sync_file_range:
  sync (usec): min=534, max=15766, avg=1273.08, stdev=1084.70
  sync percentiles (usec):
   | 1.00th=[ 553], 5.00th=[ 578], 10.00th=[ 594], 20.00th=[ 627],
   | 30.00th=[ 709], 40.00th=[ 750], 50.00th=[ 783], 60.00th=[ 1549],
   | 70.00th=[ 1729], 80.00th=[ 1991], 90.00th=[ 2180], 95.00th=[ 2278],
   | 99.00th=[ 2376], 99.50th=[ 9634], 99.90th=[15795], 99.95th=[15795],
   | 99.99th=[15795]

Несколько замечаний:

  1. В приведенном выше примере мы подстроили параметры --size и --bs под конкретный случай. Чтобы получить содержательный результат от fio, указывайте значения, подходящие для вашего сценария использования. О том, как их выбрать, будет рассказано ниже.
  2. Во время тестирования только fio нагружает дисковую подсистему. В реальной жизни вполне вероятно, что на диск будут писать и другие процессы (помимо тех, что связаны с wal_fsync_duration_seconds). Подобная дополнительная нагрузка может привести к увеличению wal_fsync_duration_seconds. Другими словами, если 99-й процентиль, полученный по итогам тестирования с fio, лишь слегка меньше 10 мс, велика вероятность, что производительность хранилища недостаточна.
  3. Для теста вам понадобится версия fio не ниже 3.5, поскольку более старые версии не агрегируют результаты fdatasync в виде процентилей.
  4. Приведенный выше вывод представляет собой лишь небольшой отрывок от общего вывода fio.

Подробно о fio и etcd

Несколько слов о WAL’ах etcd

Как правило, базы данных используют упреждающую журнализацию (write-ahead logging, WAL). etcd это тоже касается. Обсуждение WAL выходит за рамки этой статьи, однако для наших целей нужно знать следующее: каждый член кластера etcd хранит WAL в постоянном хранилище. etcd записывает некоторые операции с key-value-хранилищем (например, обновления) в WAL, прежде чем выполнить их. Если узел упадет и перезапустится в промежутке между snapshot’ами, etcd сможет восстановить транзакции, проведенные с момента предыдущего snapshot’а, ориентируясь на содержимое WAL.

Таким образом, каждый раз, когда клиент добавляет ключ в KV-хранилище или обновляет значение существующего ключа, etcd добавляет описание операции в WAL, представляющий собой обычный файл в постоянном хранилище. Прежде чем продолжить работу, etcd ДОЛЖНА быть на 100% уверена, что запись в WAL действительно сохранена. Чтобы добиться этого в Linux, недостаточно использовать системный вызов write, поскольку сама операция записи на физический носитель может быть отложена. Например, Linux в течение некоторого времени может продержать WAL-запись в кэше ядра в памяти (например, в страничном кэше). Чтобы гарантировать, что данные записаны на носитель, после записи необходимо задействовать системный вызов fdatasync — именно так поступает etcd (как видно на примере следующего вывода strace; здесь 8 — дескриптор файла WAL):

21:23:09.894875 lseek(8, 0, SEEK_CUR)   = 12808 <0.000012>
21:23:09.894911 write(8, ".20210220361223255266632$1020103026"34"rn3fo"..., 2296) = 2296 <0.000130>
21:23:09.895041 fdatasync(8)            = 0 <0.008314>

К сожалению, запись в постоянное хранилище занимает некоторое время. Затянувшееся выполнение вызова fdatasync может сказаться на производительности etcd. В документации к хранилищу указывается, что для достаточной производительности необходимо, чтобы 99-й процентиль продолжительности всех вызовов fdatasync при записи в файл WAL была меньше 10 мс. Есть и другие метрики, связанные с хранилищем, но в этой статье пойдет речь именно об этой.

Оцениваем хранилище с помощью fio

Оценить, подходит ли некое хранилище для использования с etcd, можно с помощью утилиты fio — популярного тестера I/O. Учитывайте, что дисковый ввод-вывод может происходить по-разному: sync/async, множество различных классов системных вызовов и т.п. Оборотная сторона медали заключается в том, что fio чрезвычайно сложна в использовании. У утилиты множество параметров, и различные комбинации их значений приводят к совершенно разным результатам. Чтобы получить вменяемую оценку в случае etcd, вы должны убедиться, что нагрузка на запись, генерируемая fio, максимально походит на нагрузку etcd при записи в WAL-файлы:

  • Это означает, что генерируемая fio нагрузка, по крайней мере, должна представлять собой серию последовательных записей в файл, где каждая операции записи состоит из системного вызова write, за которым следует fdatasync.
  • Чтобы включить последовательную запись, необходимо указать флаг --rw=write.
  • Чтобы fio писала с использованием вызовов write (а не других системных вызовов — например, pwrite), используйте флаг --ioengine=sync.
  • Наконец, флаг --fdatasync=1 гарантирует, что за каждым write следует fdatasync.
  • Два других параметра в нашем примере: --size и --bs — могут меняться в зависимости от конкретного сценария использования. В следующем разделе будет описана их настройка.

Почему мы выбрали fio и откуда узнали, как его настраивать

Эта заметка появилась из реального случая, с которым мы столкнулись. У нас был кластер на Kubernetes v1.13 с мониторингом на Prometheus. В качестве хранилища для etcd v3.2.24 выступали твердотельные накопители. Метрики etcd показывали слишком высокие задержки fdatasync, даже когда кластер простаивал. Нам эти метрики казались весьма сомнительными, и мы не были уверены в том, что именно они представляют. Вдобавок, кластер состоял из виртуальных машин, поэтому не получалось сказать, задержка была связана с виртуализацией или во всем виноваты SSD.

Кроме того, мы рассматривали различные изменения в конфигурации аппаратного и программного обеспечения, поэтому требовался способ их оценки. Конечно, можно было бы запустить etcd в каждой конфигурации и посмотреть на соответствующие метрики Prometheus, но это потребовало бы значительных усилий. Нам же был нужен простой способ, позволяющий оценить конкретную конфигурацию. Мы хотели проверить свое понимание метрик Prometheus, поступающих от etcd.

Для этого требовалось решить две проблемы:

  • Во-первых, как выглядит I/O-нагрузка, генерируемая etcd при записи в файлы WAL? Какие системные вызовы используются? Каков размер блоков записи?
  • Во-вторых, допустим, ответы на вышеперечисленные вопросы у нас есть. Как воспроизвести соответствующую нагрузку с fio? Ведь fio — чрезвычайно гибкая утилита с обилием параметров (в этом легко убедиться, например, здесь ­— прим. перев.).

Мы решили обе проблемы с помощью одного и того же подхода, опирающегося на команды lsof и strace:

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

Первое, что мы сделали, — использовали strace для изучения сервера etcd в кластере Kubernetes, пока тот простаивал.

Так было обнаружено, что блоки записи в WAL очень плотно сгруппированы, размер большинства лежал в диапазоне 2200-2400 байт. Именно поэтому в команде в начале этой статьи используется флаг --bs=2300 (bs — размер в байтах каждого блока записи в fio).

Обратите внимание, что размер блоков записи etcd может варьироваться в зависимости от версии, deployment’а, значений параметров и т.д. — это влияет на продолжительность fdatasync. Если у вас похожий сценарий использования, проанализируйте с помощью strace свои процессы etcd, чтобы получить актуальные значения.

Затем, чтобы получить четкое и всеобъемлющее представление о работе etcd с файловой системой, мы запустили ее из-под strace с флагами -ffttT. Это позволило охватить процессы-потомки и записать вывод каждого в отдельный файл. Кроме того, были получены подробные сведения о моменте старта и длительности каждого системного вызова.

Мы также воспользовались командой lsof, чтобы подтвердить свое понимание вывода strace в плане того, какой файловый дескриптор для какой цели использовался. Получился вывод strace, похожий на тот, что приведен выше. Статистические манипуляции со временами синхронизации подтвердили, что метрика wal_fsync_duration_seconds от etcd соответствует вызовам fdatasync с дескрипторами файлов WAL.

Чтобы сгенерировать с помощью fio рабочую нагрузку, аналогичную нагрузке от etcd, была изучена документация утилиты и подобраны параметры, подходящие нашей задаче. Мы убедились в том, что задействованы нужные системные вызовы, и подтвердили их продолжительность, запустив fio из strace (как это было сделано в случае etcd).

Особое внимание было уделено определению значения параметра --size. Он представляет собой общую нагрузку I/O, генерируемую утилитой fio. В нашем случае это полное число байтов, записанное на носитель. Оно прямо пропорционально числу вызовов writefdatasync). Для определенного bs количество вызовов fdatasync равно size / bs.

Поскольку нас интересовал процентиль, мы стремились к тому, чтобы число проб было достаточно большим для статистической значимости. И решили, что 10^4 (что соответствует размеру в 22 Мб) будет достаточно. Меньшие значения параметра --size давали более выраженный шум (например, вызовы fdatasync, которые занимают гораздо больше времени, чем обычно, и влияют на 99-й процентиль).

Дело за вами

В статье показано, как с помощью fio можно оценить, является ли достаточно быстрым носитель, предназначенный для использования с etcd. Теперь дело за вами! Исследовать виртуальные машины с хранилищем на базе SSD можно в сервисе IBM Cloud.

P.S. от переводчика

С готовыми примерами использования fio для решения других задач можно ознакомиться в документации или напрямую в репозитории проекта (там их представлено намного больше, чем упоминается в документации).

P.P.S. от переводчика

Читайте также в нашем блоге:

Источник: habr.com