Производительность кластера etcd во многом зависит от производительности его хранилища. etcd экспортирует некоторые метрики в Prometheus, чтобы предоставить нужные сведения о производительности хранилища. Например, метрику wal_fsync_duration_seconds. В документации к etcd сказано: чтобы хранилище считалось достаточно быстрым, 99-й процентиль этой метрики должен быть меньше 10 мс. Если вы планируете запустить кластер etcd на машинах Linux и хотите оценить, достаточно ли быстрое у вас хранилище (например, SSD), можно использовать fio — популярный инструмент для тестирования операций ввода-вывода. Запустите следующую команду, где test-data — это каталог под точкой подключения хранилища: Нужно просто посмотреть результаты и проверить, что 99-й процентиль длительности fdatasync меньше 10 мс. Если да, у вас достаточно быстрое хранилище. Вот пример результатов: Примечания Что такое WAL в etcd Обычно базы данных используют журнал упреждающей записи; etcd тоже его использует. Здесь мы не будем подробно обсуждать журнал упреждающей записи (write-ahead log, WAL). Нам достаточно знать, что каждый член кластера etcd ведет его в постоянном хранилище. etcd записывает каждую операцию с парами ключ-значение (например, обновление) в WAL, прежде чем применить их в хранилище. Если между снимками один из членов хранилища аварийно завершается и перезапускается, он может локально восстановить транзакции с момента последнего снимка по содержимому WAL. Когда клиент добавляет ключ в хранилище пар ключ-значение или обновляет значение существующего ключа, etcd вносит запись об этой операции в WAL, который представляет собой обычный файл в постоянном хранилище. Прежде чем продолжить обработку, etcd ДОЛЖЕН быть полностью уверен, что запись в WAL действительно произошла. В Linux для этого недостаточно одного системного вызова write, так как фактически запись в физическое хранилище может быть отложена. Например, Linux может некоторое время хранить запись WAL в кэше в памяти ядра (например, страничном кэше). А для того, чтобы данные точно записались в постоянное хранилище, нужен системный вызов fdatasync после записи, и etcd как раз использует его (как можно увидеть в результате работы strace, где 8 — это дескриптор файла WAL): К сожалению, запись в постоянное хранилище не проходит мгновенно. Если вызов fdatasync работает медленно, производительность системы etcd падает. В документации по etcd говорится, что хранилище считается достаточно быстрым, если в 99-м процентиле вызовов fdatasync при записи в файл WAL занимают меньше 10 мс. Есть и другие полезные метрики для хранилища, но в этом посте мы говорим только об этой метрике. Если нужно оценить, подходит ли ваше хранилище для etcd, используйте fio — очень популярный инструмент тестирования нагрузки ввода-вывода. Следует помнить, что дисковые операции могут быть самыми различными: синхронные и асинхронные, множество классов системных вызовов и т. д. Как следствие — fio достаточно очень сложно использовать. У него имеется множество параметров, и разные комбинации их значений выдают совершенно непохожие рабочие нагрузки ввода-вывода. Чтобы получить адекватные цифры для etcd, следует убедиться, что тестовая нагрузка записи от fio максимально близка к реальной нагрузке от etcd при записи файлов WAL. Следовательно, fio должен, как минимум, создавать нагрузку в виде ряда последовательных операций записи в файл, каждая запись будет состоять из системного вызова write, за которым следует системный вызов fdatasync. Для последовательных операций записи fio нужен параметр --rw=write. Чтобы при записи fio использовал системный вызов write, а не pwrite, стоит указать параметр --ioengine=sync. Наконец, чтобы после каждой записи вызывался fdatasync, нужно добавить параметр --fdatasync=1. Два других параметра в этом примере (--size и --bs) зависят от конкретного сценария. В следующем разделе мы расскажем, как их настроить. В этом посте мы описываем реальный случай. У нас был кластер Kubernetes v1.13, который мы мониторили с помощью Prometheus. etcd v3.2.24 размещался на SSD. Метрики по etcd показывали слишком высокие задержки для fdatasync, даже когда кластер ничего не делал. Метрики были странными, и мы толком не знали, что они означают. Кластер состоял из виртуальных машин, нужно было понять, в чем проблема: в физических SSD или в слое виртуализации. К тому же мы часто вносили изменения в конфигурацию оборудования и ПО, и нам нужен был способ оценивать их результат. Мы могли запускать etcd в каждой конфигурации и смотреть на метрики Prometheus, но это слишком хлопотно. Мы искали достаточно простой способ оценивать конкретную конфигурацию. Мы хотели проверить, правильно ли мы понимаем метрики Prometheus от etcd. Но для этого нужно было решить две проблемы. Во-первых, как выглядит нагрузка ввода-вывода, которую etcd создает при записи в WAL? Какие системные вызовы используются? Какой размер у записей? Во-вторых, если мы ответим на эти вопросы, как воспроизвести аналогичную рабочую нагрузку с fio? Не забывайте, что fio — очень гибкий инструмент со множеством параметров. Мы решили обе проблемы одним подходом — с помощью команд lsof и strace. lsof отображает все дескрипторы файлов, используемые процессом, и связанные с ними файлы. А с помощью strace можно изучить уже запущенный процесс или запустить процесс и изучить его. strace выводит все системные вызовы от изучаемого процесса (и его дочерних процессов). Последнее весьма важно, поскольку etcd как раз применяет подобный подход. Первым делом мы использовали strace для изучения сервера etcd для Kubernetes, когда на кластер не было нагрузки. Мы увидели, что почти все записи WAL были примерно одного размера: 2200–2400 байт. Поэтому в команде в начале поста мы указали параметр --bs=2300 (bs означает размер в байтах для каждой записи fio). Обратите внимание, что размер записи etcd зависит от версии etcd, поставки, значений параметров и т. д. и влияет на длительность fdatasync. Если у вас похожий сценарий, изучите свои процессы etcd с помощью strace, чтобы узнать точные цифры. Затем, чтобы хорошо представить себе действия в файловой системе etcd, мы запустили ее со strace и с параметрами -ffttT. Так мы пытались изучить дочерние процессы и записать выходные данные каждого из них в отдельном файле, а еще получить подробные отчеты о начале и длительности каждого системного вызова. Мы использовали lsof, чтобы подтвердить свой анализ выходных данных strace и увидеть, какой дескриптор файла для каких целей использовался. Так с помощью strace получились результаты, показанные выше. Статистика по времени синхронизации подтвердила, что показатель wal_fsync_duration_seconds из etcd соответствует вызовам fdatasync с файловыми дескрипторами WAL. Мы изучили документацию по fio и выбрали параметры для нашего сценария, чтобы fio сгенерировал нагрузку, аналогичную etcd. Также мы проверили системные вызовы и их длительность, запустив fio из strace, по аналогии с etcd. Мы тщательно подобрали значение параметра --size, которые представляет всю нагрузку ввода-вывода от fio. В нашем случае это общее число байтов, записываемых в хранилище. Оно получилось прямо пропорциональным количеству системных вызовов write (и fdatasync). Для определенного значения bs количество вызовов fdatasync = size/bs. Поскольку нас интересовал процентиль, у нас должно было быть достаточно образцов для достоверности, и мы подсчитали, что нам будет достаточно 10^4 (получается 22 мебибайта). Если --size меньше, могут возникать выбросы (например, несколько вызовов fdatasync отрабатывают дольше обычного и влияют на 99-й процентиль). Мы показали, как использовать fio и узнать, достаточно ли у хранилища скорости для высокой производительности etcd. Теперь вы можете попробовать это на практике самостоятельно, используя, например, виртуальные машины с хранилищем SSD в IBM Cloud.