Прим. перев.: эта статья — итоги мини-исследования, проведенного инженерами IBM Cloud в поисках решения реальной проблемы, связанной с эксплуатацией базы данных 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]
Несколько замечаний:
- В приведенном выше примере мы подстроили параметры
--size
и--bs
под конкретный случай. Чтобы получить содержательный результат отfio
, указывайте значения, подходящие для вашего сценария использования. О том, как их выбрать, будет рассказано ниже. - Во время тестирования только
fio
нагружает дисковую подсистему. В реальной жизни вполне вероятно, что на диск будут писать и другие процессы (помимо тех, что связаны сwal_fsync_duration_seconds
). Подобная дополнительная нагрузка может привести к увеличениюwal_fsync_duration_seconds
. Другими словами, если 99-й процентиль, полученный по итогам тестирования сfio
, лишь слегка меньше 10 мс, велика вероятность, что производительность хранилища недостаточна. - Для теста вам понадобится версия
fio
не ниже 3.5, поскольку более старые версии не агрегируют результатыfdatasync
в виде процентилей. - Приведенный выше вывод представляет собой лишь небольшой отрывок от общего вывода
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. В нашем случае это полное число байтов, записанное на носитель. Оно прямо пропорционально числу вызовов write
(и fdatasync
). Для определенного bs
количество вызовов fdatasync
равно size / bs
.
Поскольку нас интересовал процентиль, мы стремились к тому, чтобы число проб было достаточно большим для статистической значимости. И решили, что 10^4
(что соответствует размеру в 22 Мб) будет достаточно. Меньшие значения параметра --size
давали более выраженный шум (например, вызовы fdatasync
, которые занимают гораздо больше времени, чем обычно, и влияют на 99-й процентиль).
Дело за вами
В статье показано, как с помощью fio
можно оценить, является ли достаточно быстрым носитель, предназначенный для использования с etcd. Теперь дело за вами! Исследовать виртуальные машины с хранилищем на базе SSD можно в сервисе IBM Cloud.
P.S. от переводчика
С готовыми примерами использования fio
для решения других задач можно ознакомиться в документации или напрямую в репозитории проекта (там их представлено намного больше, чем упоминается в документации).
P.P.S. от переводчика
Читайте также в нашем блоге:
- «etcd 3.4.3: исследование надёжности и безопасности хранилища»;
- «Наш опыт работы с данными в etcd Kubernetes-кластера напрямую (без K8s API)»;
- «6 занимательных системных багов при эксплуатации Kubernetes [и их решение]».
Автор: Андрей Сидоров