Как хранить результаты нагрузочных тестов так, чтобы работать с ними было удобно? Если команда нагрузки сталкивается с такой проблемой, то возникает необходимость хранения данных. В частности, требуется, чтобы они сразу соответствовали фильтрам, а также чтобы приходилось реже выполнять сложные агрегатные функции и получать результаты быстрее.

Меня зовут Вячеслав Смирнов, я ускоряю и тестирую инфраструктурные сервисы в Miro. Ещё я развиваю сообщество нагрузочников @qa_load в telegram: делюсь там кое-какой экспертизой. В частности, если выполнять тесты производительности регулярно, по несколько раз в день, то вскоре в популярном хранилище тестов InfluxDB фильтровать результаты тестов производительности становится сложно. Сегодня я расскажу про 12 шагов, как можно ускорить InfluxDB. Основным из них является ускорение с помощью nginx кэширования. Пример использования кэширующего nginx, в качестве источника данных для Grafana можно найти тут. Все остальные примеры, которые мы будем разбирать я вынес сюда. А слайды оставил здесь.

По материалам доклада "Как ускорить запросы к InfluxDB с помощью InfluxQL Continuous Queries и разделения данных" конференции TestDriven Conf 2022 
(все схемы нарисованы в Miro)
По материалам доклада "Как ускорить запросы к InfluxDB с помощью InfluxQL Continuous Queries и разделения данных" конференции TestDriven Conf 2022 (все схемы нарисованы в Miro)

1. Когда оптимизация InfluxDB важна

Оптимизация важна, если у вас большой продукт. Глазами нагрузочника, это не продукт, в котором много кода, а тот, который написали вместе. Например, 30 команд, включая фронтовиков, бэкендеров и прочих. При этом команда нагрузки лишь одна из них, которая отвечает за инфраструктуру тестирования.

Она обслуживает все запросы остальных команд. А фичовые команды имеют разные предпочтения: кто-то хочет использовать JMeter, кто-то Gatling или k6. Кроме того, как правило, все эти команды хотят сохранять свои метрики. Причём сохранять метрики хотят долго, например, для всех своих версий, чтобы смотреть тренды производительности: деградирование или улучшение.

В этом случае, если выбирать не энтерпрайзное решение, в котором свои способы улучшения, и не InfluxDB Cloud, а опенсорсное решение v1.8 или v2.2, то полезно выполнить следующие шаги, чтобы ускориться:

  1. Поменять настройки в разных местах, выставить ограничения;

  2. Оптимизировать запись в InfluxDB c Telegraf и MQ;

  3. Оптимизировать выборку c Grafana и nginx;

  4. Вычислить медленные запросы;

  5. И закэшировать их с Continuous Queries.

2. Разделение данных на разные базы и серверы InfluxDB

В InfluxDB сложно отделить данные от индексов, в нём всё — это индекс. Поэтому самый простой способ сделать InfluxDB быстрее — просто поделить индексы. Рассмотрим несколько способов, как это сделать.

Разные базы данных для команд и стендов

Первый способ — сделать разные базы данных для команд и стендов

Представим, что вашу базу данных, ваш гигантский индекс пишет 7 разных команд, а потом каждый из них выбирает и строит свои метрики. Вы можете им предложить писать также, как писали, но только использовать 7 баз данных. Это супер ускорит метрики. 

Дело в том, что когда баз данных несколько, проще делать бэкапы, сложнее угробить всё хранилище сразу, и в принципе всё работает  быстрее.

Разные сервисы данных на новые и архивные

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

Можно пойти дальше и разделить сервер со свежими данными на несколько серверов. Например, в один сервер будут писать три команды, в другой — ещё две.

Контейнеры vs InfluxDB

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

Однако, если у вас куча данных и они ещё делятся на новые и архивные, то такое сделать сложно.

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

3. «Архивирование» медленной базы InfluxDB в Grafana

Как сделать так, чтобы пользователи, которые пользуются вашим InfluxDB, не увидели, что вы его поделили под капотом? Я назвал это «архивированием».

Если вы работаете в Grafana, то есть два варианта, как можно это сделать:

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

  2. Второй вариант: создать переменную с типом source, в которой хранится источник данных. Этот подход делает из Grafana витрину для данных, в которой можно выбрать источник.

И если мы выбираем второй вариант, то тут появляется:

Представим, что у вас есть некий DataSource, который называется JMeter. Год назад он работал со старым источником данных (old DB), а сейчас вы создали новую быструю версию new DB. В таком, случае можно в Grafana сказать: «Так, JMeter, ты теперь смотришь на новую версию, а я создал DataSource типа JMeter-2022 года (архив метрик до 2022 года), который будет смотреть на старую версию».

Таким образом, вы ничего не будете терять. Пользователи, как работали с JMeter, так и работают, просто теперь есть более быстрая версия вашего решения.

4. Сокращение фильтров по тегам в Grafana

В Grafana можно сократить количество запросов, если скрыть некоторые панельки в строчки. Например, здесь 5 строчек скрыты:

Всё, что я не вижу — это не запросы, а просто HTML. 

Но что нельзя скрыть? Нельзя скрыть верхнюю строчку с переменными для фильтров. Поэтому если у вас вдруг на доске 70 фильтров, то это перебор. Сделайте, например, 6, остальные лучше убрать, потому что их никак не скрыть, они постоянно выполняются.

Ещё один трюк с фильтрами:

Если у вас мало фильтров, но в одном фильтре выдаётся тысяча значений, то не делайте Query, который выбирает эту тысячу значений. Вместо этого сделайте текстовый фильтр, в который вы зададите регулярку.  Это подходит к VictoriaMetrics, Prometheus, ClickHouse, InfluxDB и ускоряет практически все выборки. Иначе, Grafana будет тормозить на фильтре, в котором 20 тысяч значений, так как ей сложно такой большой json, список и структуру поддерживать. Текстовые фильтры решают эту проблему.

5. Сокращение объёма метрик

Другой способ — вообще вставлять чего-либо поменьше. Разберём как это сделать на примере с Gatling. Вот его настройки по умолчанию:

Тут совсем не включена отправка в Graphite, а у Graphite выставлено, что по умолчанию нужно писать метрики каждую секунду. Что можно с этим сделать?

Можно чуть-чуть уплотнить эти метрики и, например, писать в Graphite каждые 60 секунд с максимальным размером кадра стека (для TCP это 64 КБ). Это ускорит, потому что Influx’у, Graphite’у и всем этим источникам выгоднее, чтобы к ним сразу прислали побольше данных, но пореже.

Более того, можно вообще присылать без деталей и по UDP (с возможными потерями).

Ведь на самом деле для того, чтобы провести аналитику, не нужно в каждый момент времени, прямо во время запуска теста, видеть время отклика по конкретному endpoint. Это можно сделать потом, даже если с потерями. 

Дело в том, что если InfluxDB зависает, то все инструменты в TCP будут делать retry, например, посылать TCP-пакет 5-10 раз. А так как источник данных уже завис, ситуация только усугубится. В UDP такой проблемы нет.

Аналогично с JMeter:

Тут очередь запросов размером в 5000. Я рекомендую сделать примерно такую математику:

Пусть в нашем тесте 1200 rps, нам нужно слать метрики один раз в 60 с. Для этого чуть-чуть меняем настройки, например, говорим, что наше окно теперь не 5000 очередь, а 72000 (1200 х 60 с). Далее выбираем один из вариантов: fixed или timed. Они отличаются только тем, что timed после отправки полностью очищает набор собранных метрик, а fixed постоянно хранит эту очередь, добавляет в конец новые значения и удаляет старые. На мой взгляд, это лишние действия, которые не нужны.

Ещё один момент со стороны тестов:

Если записывать результаты тестов по каждому ID и параметру, то в результате получится гигантский индекс. В нём может быть вообще миллион значений. Он будет тяжёлым и медленным. В итоге получится таймаут, появятся какие-то лимиты, в которые вы упрётесь и ничего с ними не сделаете. 

Способ ускорения InfluxDB в данном случае: не использовать переменные в имени запроса, но лишь обозначить, что в определённом месте будет такая-то переменная. Таким образом вы получите всего лишь одно значение, а значит и маленький индекс.

Также, если вы записываете всю статистику и все метрики в хранилище, чтобы потом отдельно открыть график по каждой странице и посмотреть, то после каждого теста вы, очевидно, тратите много времени на работу с графиками, стремитесь к высокой точности метрик, чтобы ещё больше времени тратить на их анализ. И так пока хранилище метрик не начнёт тормозить. Но если мы поставим SLA в сам тест, то есть assertion на время загрузки, то вы можете оценивать производительность вашей системы во время теста просто по количеству ошибок. Например, мы превысили SLA на 5%, тогда у вас будет 5% ошибок, с которыми вы столкнётесь. 

Можно расставить assertion и для JMeter и Gatling:

6. Кэширование ответов от InfluxDB с nginx

Когда мы работаем с InfluxDB, обычно присутствуют два элемента: Grafana и InfluxDB. 

В этом случае, если что-то начинает тормозить, то тормозит всё и у всех. Поэтому в эту связку полезно добавить третий элемент — nginx, который ускоряет работу в определённых обстоятельствах.

Что это за обстоятельства? Допустим, прошёл тест и команда из 3 человек строит отчёт по результатам. У всех троих он тормозит, никто не доволен. Но если добавить nginx в эту схему, то тормозить будет только у одного (у того, кто самый первый его открыл), а у остальных всё сработает очень быстро. При этом InfluxDB разгрузится и сработает быстрее, чем до этого.

В чём дело?

В InfluxDB v1.8 есть несколько endpoints. Но те из них, которые отвечают за выборку данных в /query, работают с методами GET и POST. Их можно кэшировать.

Метод GET используется для запросов SELECT и SHOW TAG VALUES, а POST — для всяких ALTER, CREATE, DELETE. Однако в Grafana-доске нет запросов к InfluxDB с использованием ALTER или DELETE. Получается, что 99,9999% — это все запросы на SELECT и SHOW из Grafana, а это именно то, что можно кэшировать. Поэтому мы можем создать nginx Docker-контейнер с несложным конфигом:

Тут ключевые строчки — это proxy_pass и proxy_cache. Всё остальные можно оставить со значением по умолчанию, но они тоже нужны.

Если перенастроить Grafana DataSource, чтобы он смотрел не на InfluxDB, а на nginx, то всё ускорится. Каким образом настроить конфиг — можно найти тут. Кроме этого, есть пример настройки DataSource:

Очень важно в этой схеме сделать так, чтобы Grafana-доска использовала абсолютный, а не относительный интервал времени, как, например, последние 6 или 24 часа.

Почему? Потому что относительный интервал постоянно меняется каждую миллисекунду, и кэширование не будет работать, так как это уже новый URL.

Для этого я рекомендую в каждую Grafana-доску с InfluxDB добавить текстовую HTML-панель (она есть в Grafana, это не запрос к DataSource, а просто статический HTML). И вписать туда ссылку, при клике на которую вы будете получать статические интервалы с копированием всех переменных. Он уже будет кэшироваться. 

Вот ссылка на доску Grafana со статическим интервалом:

/d/${__dashboard}/?from=${__from}&to=${__to}&${db:queryparam}

При отображении на доске она будет заменена. Например, на это, что уже можно кэшировать. В отличие от этой ссылки, где now-30m&to=now будет каждую мсек менять своё фактическое значение при отправке запроса на обновление отображаемых данных 

7. Мониторинг производительности InfluxDB

По мониторингу у меня два момента:

Первое — это процессор и память. Чем они интересны? 

Количество ядер, задействованных в CPU — это примерное количество одновременно выполняемых запросов на InfluxDB. Например, 2 параллельных запроса — это 2 ядра CPU в сотку. 

Объём памяти (RAM) — это примерно объём задействованных shards из вашего индекса.

Что такое вообще shard? Shard — часть индекса, например, за 1 час. Когда вы создаёте базу данных, ваш запрос на InfluxDB выглядит примерно так:

CREATE DATABASE "JMeter"

WITH DURATION INF REPLICATION 1

   SHARD DURATION 1h NAME "autogen";

А теперь как это все померить? Это и есть второй момент: у InfluxDB есть ещё внутренние метрики. Их так много (около 200), что новичку в них сложно разобраться. Поэтому я выделил всего три внутренние метрики из всего этого многообразия:  

shard : path : size : размеры индексов;

database : numSeries : количество значений тегов;

httpd : интенсивность (количество и длительность) запросов.

Размер файлика, когда он загружается в память — примерно самая критичная метрика в InfluxDB. Дело в том, что если памяти не хватает, то InfluxDB просто перезапускается и всё пересчитывает по новой.

Выглядит это так:

Вы делаете несложный запрос. Метрика там называется diskBytes. Это самая важная метрика. При этом на диске данный shard может быть 300 МБ, а в памяти под 2 ГБ. Название метрики тут не несёт смысла. 

Поэтому если вы видите, что shards разрослись, то вам нужно добавить память или грохнуть эти shards, потому что у вас может не быть 140 ГБ оперативной памяти на слабеньком Docker-контейнере.

Следующая важная метрика — это количество уникальных значений тегов:

Третья метрика — это количество конкурентных запросов, которая позволяет примерно оценить, сколько CPU вам понадобится:

В данном случае мне всегда нужны либо 0, либо 1 ядро.

8. Конфигурирование сервера InfluxDB

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

Первая из них — это количество конкурентных запросов:

Обычно на сервере с InfluxDB нет 140 ядер, поэтому стоит выставить какое-то адекватное ограничение. Например, max-concurrent-queries = 5.

Что касается длительности выполнения запросов, поделюсь с вами своей внутренней эвристикой:

Когда запрос в InfluxDB зависает, то каждую секунду он аллоцирует примерно 100 МБ оперативной памяти. Получается примерно такой расчет: если на сервере 6000 МБ оперативки, то можно зависнуть на гипотетическом одном запросе примерно на 60s, а дальше память всё равно кончится. Если же взять 5 конкурентных запросов для тех же 6000 МБ оперативки, то 60s надо ещё поделить на 5. 

Если у вас слишком длинный запрос, то вы получите не очень говорящую ошибку: query-timeout limit exceeded.

Ещё можно ограничить максимальное количество тегов ответа:

Это как произведение размера в одном теге на размер другого тега. Выглядит это ограничение так: max-select-series = 10100. Если вы превысите, то получите ошибку max-select-series limit exceeded.

Есть ещё важный момент касательно количества точек при группировке по времени. К примеру, я обладатель монитора с шириной 1920 точек. Поэтому даже если мне InfluxDB вернёт 20 тысяч точек, чтобы я их отобразил на графике, то Grafana не сможет это сделать, потому что есть физическое ограничение моего монитора. Так зачем же мне вообще возвращать группировку больше, чем 1920 точек? Незачем.

В данном запросе у меня вообще вернётся 11-12 точек, так как я группирую 10 минут по 1 минуте:

Поэтому разумно выставить ограничение max-select-buckets = 1920.

Если вдруг я выполню запрос за 10 минут с группировкой по 1 мс, то получу exception:

Ну и правильно! Надо сделать группировку побольше.

Ещё совет: если вы всё-таки хотите хранить гигабайты значений и не упираться ни во что, то можете перейти с быстрого индекса inmem на медленный индекс tsi1, используя index-version:

Самое последнее — можно установить порог, после которого текст вашего запроса попадёт в лог. Я лично выставляю 2 секунды: log-queries-after = "2s".

9. Анализ логов InfluxDB

Если у нас что-то залогируется, то мы в самой последней строчке этого лога увидим, что в базе данных jmeter10000 что-то идёт не так:

Если вы видите такие сообщения в логе, то только по одному логу понять, что тут не так, сложно. Но просто понять, что работа с базой данных jmeter10000 медленная.

10. Замер длительности ответа на запрос из Grafana в InfluxDB

Как рекомендация от человека, который видел это много раз, следующим шагом стоит сделать следующее. Вы идёте в DataSource с этой базой данных в Grafana и говорите: «Дай-ка я тебя чуть-чуть подтюню», то есть сделаю лучше: добавлю Access Browser и HTTP метод POST.

Так как вы используете POST, у вас в теле запроса есть структурированное тело запроса без URL-encoding, длинной строчки, всяких %20 и прочей красоты, которую вы должны декодировать в голове. Поэтому вы просто открываете web-консоль в доске Grafana с этим DataSource и смотрите какая там длительность и текст запросов, чтобы если что улучшить. Например, у вас есть запрос, который считает среднее время с группировкой по всему тесту:

Этот запрос тормозит. Его можно улучшить кэшированием ответа на него в самой базе данных. Для этого можно создать аналогию индекса через Continuous Queries.

Что такое Continuous Queries? Представим, что команда из трёх человек навалилась на задачу и считает среднее время ответов по всем 6 тестам, которые они сделали за всю историю запусков. Они получают ответы и InfluxDB напрягается. Continuous Queries позволит им закэшировать ответ, чтобы он был дан на запрос в будущем.

11. Подготовка данных для ответа с помощью Continuous Queries

Разберём на примере:

В данном случае ключевой строкой является №3:

RESAMPLE EVERY 10m FOR 1d

Она означает, что InfluxDB каждые 10 минут в один поток будет выполнять группирующий запрос и обновлять в нём же самом ответ. 

В одном проекте, где мне надо было сделать гигантскую аналитику, такой способ ускорил работу в разы.

Мой изначальный запрос выглядел так:

После изменения он стал простым: без всяких фильтров, сложных группировок или агрегатных функций. Он просто берёт последнее значение из заранее приготовленного архива или предрасчёта. Это называется Retention Policy — очень далёкий аналог названия индексов в InfluxDB.

12. Смена БД InflfluxDB v1.8, InflfluxDB v2, VictoriaMetrics или ClickHouse

Если ни один способ вам не понравился, вы можете перейти на альтернативные технологии:

— InfluxDB v2.2;

В нём есть новый быстрый движок. Кроме этого теперь все новые прикольные фичи имплементируются именно там, а InfluxDB v1.8 теперь на поддержке. В этой новой  версии есть HTTP GET метод и /query, оставленный для обратной совместимости. Поэтому закэшировать работу можно так же как и в InfluxDB 1.8, то есть с помощью nginx.

— VictoriaMetrics;

VictoriaMetrics кэширует ответы, жмёт метрики и по-другому хранит перцентили в bucket’ах. У неё есть свои приколы и хорошая документация. Это как сжатый Prometheus со своими плюсами. 

— ClickHouse.

Быстр (при наличии памяти) и удобен. Также, как Victoria, жмёт метрики. Для него есть свои оптимизации.

Но! Разгонный потенциал InfluxDB v1.8 огромен. Несмотря на то, что в нём самые старые технологии: REST вместо TCP, тем не менее эти REST с помощью «тут подкрутил, там настроил» хорошо конфигурируются, что позволяет за половину дня ускорить работу системы в 1000-10000 раз.

ИТОГО

Я рассказал 12 шагов, как подойти к ускорению и хранению ваших метрик: 

  1. Когда оптимизация InfluxDB важна;

  2. Разделение данных на разные базы и серверы InfluxDB;

  3. «Архивирование» медленной базы InfluxDB в Grafana;

  4. Сокращение фильтров по тегам в Grafana;

  5. Сокращение объёма метрик;

  6. Кэширование ответов от InfluxDB с nginx;

  7. Мониторинг производительности InfluxDB;

  8. Конфигурирование сервера InfluxDB;

  9. Анализ логов InfluxDB;

  10. Замер длительности ответа на запрос из Grafana в InfluxDB;

  11. Подготовка данных для ответа с помощью Continuous Queries;

  12. Смена БД InfluxDB v1.8, InfluxDB v2, VictoriaMetrics или ClickHouse.

Резюмируются они в три тезиса:

  1. Делите ваши большие индексы на маленькие;

  2. Кэшируйте всё, что можно: ответы в Influx, ответы на nginx;

  3. Ускоряйте всё это дело с помощью настроек или удалением чего-то.

На предстоящей конференции HighLoad++ 24 и 25 ноября будет доклад по тестированию от разработчика в Tarantool. Можно будет узнать много полезного о фаззинге. Причем простым языком и с примерами на Lua.

Комментарии (0)