Как ускорить запросы к InfluxDB с помощью InfluxQL Continuous Queries и разделения данных

Смирнов Вячеслав

Miro

Ускоряю и тестирую инфраструктурные сервисы в Miro

Развиваю сообщество нагрузочников

@qa_load

Об ускорении InfluxDB глазами инженера по нагрузке

Содержание

  1. ⁉️ Когда оптимизация InfluxDB важна
  2. ⚙️ Разделение данных на разные базы и серверы InfluxDB
  3. ⚙️ «Архивирование» базы InfluxDB в Grafana
  4. 🛠️ Сокращение фильтров по тегам в Grafana
  5. ⚙️ Сокращение метрик из JMeter, Gatling, ...
  6. ⚙️ Кеширование ответов от InfluxDB с nginx
  7. 📊 Мониторинг производительности InfluxDB
  8. ⚙️ Конфигурирование сервера InfluxDB
  9. 🔬 Анализ логов InfluxDB
  10. 🔬 Замер длительности ответа на запрос из Grafana в InfluxDB
  11. 🚀 Подготовка данных для ответа с помощью Continuous Queries
  12. ⚙️ Смена БД InfluxDB v1.8, InfluxDB v2, VictoriaMetrics или ClickHouse

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

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

  • Большой продукт

  • Вы отвечаете за инфраструктуру тестирования

  • Много тестов производительности с k6, JMeter, Gatling, Yandex.Tank, ...

  • Хранятся данные за годы и строятся тренды

  • И вы хотите ускорить InfluxDB OpenSource

Большой продукт

Вы отвечаете за инфраструктуру тестирования

Много тестов производительности с Yandex.Tank, k6, JMeter, Gatling

Хранятся данные за годы и строятся тренды

Вы хотите ускорить InfluxDB OSS v1.8 или v2.2

Поменяем разные настройки в разных местах

Оптимизируем запись в InfluxDB c Telegraf и MQ

Оптимизируем выборку c Grafana и nginx

Вычислим медленные запросы

И закешируем их с Continuous Queries

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

  • Большой продукт

  • Вы отвечаете за инфраструктуру тестирования

  • Много тестов производительности с k6, JMeter, Gatling, Yandex.Tank, ...

  • Хранятся данные за годы и строятся тренды

  • И вы хотите ускорить InfluxDB OpenSource

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

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

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

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

  • Разные сервисы для разных данных

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

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

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

Если данные в одной БД:

  • База данных большая
  • Нужна фильтрация данных
  • Высокий риск повреждения данных

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

Если данные в одной БД:

  • База данных большая
  • Нужна фильтрация данных
  • Высокий риск повреждения данных

Если данные в разных БД:

  • Бекапы проще
  • Фильтрация не нужна
  • Сложнее замедлить работу всех БД

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

Два сервиса c одной активной БД быстрее одного

Семь контейнеров быстрее одного большого?

Семь контейнеров быстрее одного большого?

Да, быстрее,

но если

используются не

все одновременно

Построение трендов по метрикам из одной БД

Простые запросы

для трендов

по всем метрикам

с фильтрами и без них

Скрипты и отдельная БД для общих трендов

Все еще простые запросы

для трендов по новым

метрикам в одной БД.

Но сложные механизмы

для трендов по всем

метрикам всех БД.

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

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

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

  • Разные сервисы для разных данных

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

3. ⚙️ «Архивирование» базы InfluxDB в Grafana

  • Grafana как реестр активных баз данных

  • Перенос файлов базы данных {name} на архивный сервер

  • Создание пустой базы данных {name}-{from}

  • Настройка Grafana DataSource с именем {name} на новую базу данных

  • Создание Grafana DataSource для архивной базы данных

Grafana как реестр активных баз данных

Имеем DataSource с именем "jmeter" в Grafana

Переключаем DataSource на новую базу данных

3. ⚙️ «Архивирование» базы InfluxDB в Grafana

  • Grafana как реестр активных баз данных

  • Перенос файлов базы данных {name} на архивный сервер

  • Создание пустой базы данных {name}-{from}

  • Настройка Grafana DataSource с именем {name} на новую базу данных

  • Создание Grafana DataSource для архивной базы данных

Способ посложнее: сократить запросы к базе

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

Ускорение фильтров заменой Query на Text

Еще сложнее: уменьшим объем метрик

5. ⚙️ Сокращение метрик из JMeter, Gatling, ...

  • Batch, batch, batch... увеличиваем размер batch'а при вставке

  • Не используем переменные в имени запроса

  • Замена интервала в 1 секунду на 1 минуту для тестов дольше 5 минут

  • Во время теста достаточно уровня детализации summary

  • SLA можно проверять по % ошибок, а Assertion вставить в тест

  • Детальный отчет можно формировать после теста

Настройки Gatling по умолчанию

gatling {
  data {
    writers = [console, file]

    graphite {
      host = "localhost"
      port = 2003
      #light = false              # only send the all* stats
      #protocol = "tcp"           # "tcp", "udp"
      #rootPathPrefix = "gatling" # the root prefix of the metrics
      #bufferSize = 8192          # internal data buffer size, in bytes
      #writePeriod = 1            # write period, in seconds
    }
  }
}

Batch, batch, batch... увеличиваем размер batch'а

Пусть копим метрики 60 сек

  writers = [console, file, graphite] # +graphite
  graphite {
    host = "localhost"
    port = 2003
    #light = false           
    #protocol = "tcp"        
    bufferSize = 65000       # 🚀 TCP limit 64k (было 8192)
    writePeriod = 60         # 🚀 Batch (было 1) 
  }

Batch, batch, batch...

Копим метрики 60 сек, без деталей

  writers = [console, file, graphite] # +graphite
  graphite {
    host = "localhost"
    port = 2003
    light = true             # 🚀 без деталей (было false)
    #protocol = "tcp"        
    bufferSize = 65000
    writePeriod = 60
  }

Batch, batch, batch...

Копим метрики 60 сек, без деталей, с потерями

  writers = [console, file, graphite] # +graphite
  graphite {
    host = "localhost"
    port = 2004              # udp-порт (было 2003)
    light = true
    protocol = "udp"         # 🚀 с потерями (было tcp)
    bufferSize = 65000
    writePeriod = 60
  }

Настройки JMeter по умолчанию

QUEUE_SIZE=5000 # Async Queue size
# Backend metrics window mode (fixed=fixed-size window, timed=time boxed)
backend_metrics_window_mode=fixed
# Backend metrics sliding window size for Percentiles, Min, Max
backend_metrics_window=100

# Backend metrics sliding window size for Percentiles, Min, Max
# when backend_metrics_window_mode is timed
# Setting this value too high can lead to OOM
# backend_metrics_large_window=5000
# Send interval in second
# Defaults to 5 seconds
backend_influxdb.send_interval=5

Batch, batch, batch... увеличиваем размер batch'а

Пусть у нас 1200 rps и копим метрики 60 сек

QUEUE_SIZE=72000 # 1200x60, было 5000, Async Queue size

Конфиг для backend_metrics_window_mode=fixed (не очищается):

backend_influxdb.send_interval=60 # было 5
backend_metrics_window=72000 # 1200x60, было 100

Конфиг для backend_metrics_window_mode=timed (очищается 🚀):

backend_influxdb.send_interval=60 # было 5
backend_metrics_large_window=72000 # 1200x60, было 5000

JMeter-сценарий, с медленной статистикой

var plan =  scenario.sendHttp(
  testId: getTestId(linksCount),
  linksCount: linksCount,
  nameOfSampler: "web?id=${id} (GET)" //переменная ${id} в имени
);

Получим много тегов в InfluxDB и большой индекс:

  • web?id=0 (GET)
  • web?id=1 (GET)
  • ...
  • web?id=1000000 (GET)

Или вообще ничего не запишем: ERROR org.apache.jmeter.visualizers.backend.influxdb

failed to send data to influxDB server.

Error writing metrics to influxDB Url:
http://influxdb:8086/write?db=jmeter100000,
responseCode: 400, responseBody:  {"error": "partial write:
  max-values-per-tag limit exceeded (100000/100000):
  measurement=\"jmeter\" tag=\"transaction\"
  value=\"web?id=19806 (GET)\" dropped=2"}

Error writing metrics to influxDB Url:
http://influxdb:8086/write?db=jmeter100000,
responseCode: 413, responseBody: {"error":"Request Entity Too Large"}

Не используем переменные в имени запроса

var plan =  scenario.sendHttp(
  testId: getTestId(linksCount),
  linksCount: linksCount,
  nameOfSampler: "web?id={id} (GET)" //вот так быстрее
);

Получим один тег в InfluxDB и малый индекс:

  • web?id={id} (GET)

SLA можно проверять по % ошибок (в summary)

Assertion на длительность вставить в тест

jmeter.apache.org/usermanual/component_reference.html#Duration_Assertion
gist.github.com/polarnik/7f5fdc5c70809c879dd42904b8639f31
gatling.io/docs/gatling/reference/current/core/check/#responsetimeinmillis
gatling.io/docs/gatling/reference/current/core/assertions/

5. ⚙️ Сокращение метрик из JMeter, Gatling, ...

  • Batch, batch, batch... увеличиваем размер batch'а при вставке

  • Замена интервала в 1 секунду на 1 минуту для тестов дольше 5 минут

  • Во время теста достаточно уровня детализации summary

  • SLA можно проверять по % ошибок, а Assertion вставить в тест

  • Детальный отчет можно формировать после теста

Ускорение выборки из InfluxDB через Grafana с nginx

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

  • Кеширование GET-запросов к /query в nginx

  • Настраиваем DataSource в Grafana на метод GET и http://nginx:80

  • Влияние интервалов времени Grafana на кеширование

  • Замена относительного интервала вида now()-6h...now() абсолютным

InfluxDB endpoints: кешируем запросы

  • https://docs.influxdata.com/influxdb/v1.8/tools/api/
  • /debug/pprof (GET)
  • /debug/requests (GET)
  • /debug/vars (GET)
  • /ping (GET, HEAD)
  • /query (GET, POST) ‼️
  • /write (POST)
  • /metrics (GET)
  • /api/v2/query (POST)
  • /api/v2/write (POST)
  • /health (GET)

InfluxDB endpoint /query (GET, POST)

  • GET:

    • SELECT ‼️
    • SHOW ‼️
  • POST:

    • SELECT INTO
    • ALTER
    • CREATE
    • DELETE
    • DROP
    • GRANT
    • KILL
    • REVOKE

Часть nginx.conf для кеширования /query (GET)

        location /query {
            proxy_cache mycache;
            proxy_cache_key "$host$request_uri";
            proxy_cache_min_uses 1;
            proxy_cache_methods GET;
            proxy_cache_valid 200 302 10m;
            proxy_pass http://influxdb:8086;
            proxy_cache_background_update on;
            proxy_cache_revalidate on;
            proxy_cache_lock on;
            add_header X-Cache-Status $upstream_cache_status;
        }

Настраиваем DataSource на http://nginx (GET)

/provisioning/datasources/jmeter_cache.yaml

apiVersion: 1
datasources:
  - name: jmeter_cache    # Имя DataSource в Grafana
    type: influxdb        # InfluxQL
    access: proxy         # Server
    database: jmeter      # Имя базы данных в InfluxDB
    url: http://nginx:80  # 👉 Адрес кеширующего сервера
    jsonData:
      httpMode: GET       # 👉 Метод GET
    basicAuth: false      # 🤷 Без аутентификации

Важен статический интервал для кеширования

Абсолютный:

Не относительный:

Относительный интервал Last 6 hours UTC:

curl -G 'http://influxdb:8086/query?db=mydb' --data-urlencode \
  'q=SELECT * FROM "metrics" \
  where time>1655827200000 AND time<1655848800000'

Каждую милисекунду дает новый диапазон:

curl -G 'http://influxdb:8086/query?db=mydb' --data-urlencode \
  'q=SELECT * FROM "metrics" \
  where time>1655827230023 AND time<1655848830023' #+30023=+30s

А это уже новый URL, новый ключ кеширования

Ссылка в Grafana Text (HTML) для автоматизации

<h2>
<a href="/d/${__dashboard}/?from=${__from}&to=${__to}&${db:queryparam}">
Select static time interval
</a>
</h2>

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

  • Кеширование GET-запросов к /query в nginx
location /query { proxy_pass http://influxdb:8086; }
  • Настраиваем DataSource в Grafana на метод GET и http://nginx:80

  • Влияние интервалов времени Grafana на кеширование

  • Замена относительного интервала вида now()-6h...now() абсолютным

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

Ускорение с 10 сек до 10 мсек (х 1000)

Кеширование с nginx ускоряет до 1000 раз

Мониторинг, чтобы закопаться в метрики InfluxDB

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

  • Мониторинг общесистемных метрик

    • CPU и память
    • Нехватка памяти для InfluxDB и перезапуски
  • Мониторинг внутренних метрик InfluxDB из базы данных __internal

    • Размеры шард InfluxDB
    • Количество значений тегов
    • Количество конкурентных запросов к InfluxDB

Мониторинг общесистемных метрик

CPU ~= Количество одновременных query

RAM ~= Размер задействованных shard

Мониторинг внутренних метрик InfluxDB из базы данных __internal

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

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

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

Размеры shard'ов InfluxDB

Shard — часть индекса, например, за 1 час

CREATE DATABASE "jmeter"
WITH DURATION INF REPLICATION 1
  SHARD DURATION 1h NAME "autogen";

ОЗУ ~= Размер shard'ов в памяти (метрики shard)

Размеры shard'ов InfluxDB в памяти из shard

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

Количество конкурентных запросов к InfluxDB

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

  • Мониторинг общесистемных метрик

    • CPU и память
    • Нехватка памяти для InfluxDB и перезапуски
  • Мониторинг внутренних метрик InfluxDB из базы данных __internal

    • Размеры shard'ов InfluxDB
    • Количество значений тегов
    • Количество конкурентных запросов к InfluxDB

Конфигурирование ограничений в InfluxDB по данным мониторинга

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

  • Ограничиваем max-concurrent-queries
    • Ограничиваем max_conns в nginx upstream
  • Ограничиваем query-timeout
  • Ограничиваем max-select-series
  • Ограничиваем max-select-buckets
  • Изменение типа индекса с inmem на tsi1
  • Логирование узких мест c log-queries-after

Ограничиваем max-concurrent-queries

[coordinator]
  # The maximum number of concurrent queries 
  # allowed to be executing at one time.  
  # If a query is executed and exceeds this limit, 
  # an error is returned to the caller.  
  # This limit can be disabled by setting it to 0.
  max-concurrent-queries = 5

Ограничиваем query-timeout

[coordinator]
  # The maximum time a query will is allowed 
  # to execute before being killed by the system.  
  # This limit can help prevent run away queries.  
  # Setting the value to 0 disables the limit.
  query-timeout = "60s"

Рассчитывайте на 100 МБайт аллокаций в сек

Если памяти 6000 МБайт, то 60s - предел

Ошибка при достижении query-timeout

query-timeout limit exceeded

SELECT mean(avg)
FROM jmeter
WHERE
  time>1656242940000000000 and
  time<1656242940000000000+10m
GROUP BY time(1m), transaction, statut, testid

Оцениваем предельное количество тегов ответа

Пусть есть:

  • 5000 значений transaction
  • 2 значения statut (ok, ko)
  • 10 000 комбинаций (серий) всего

И мы допускам запросы по всем значениям:

SELECT mean(avg) FROM "jmeter"

В лимитах выставляем 5000 х 2 + чуть-чуть

Ограничиваем max-select-series

[coordinator]
  # The maximum number of series a SELECT can run. 
  # A value of 0 will make the maximum series
  # count unlimited.

  # max-select-series = 0
  max-select-series = 10100

Ошибка при достижении max-select-series

max-select-series limit exceeded: (10101/10100)

SELECT mean(avg)
FROM "jmeter"

Оцениваем предел группировки по времени

Примерно 11-12 точек

select mean(avg)
from jmeter
where
  time>1656242940000000000 and
  time<1656242940000000000+10m
group by time(1m)

А на мониторе 1920 точек в ширину — больше пользователь не увидит

Ограничиваем max-select-buckets

[coordinator]
  # The maxium number of group by 
  # time bucket a SELECT can create.
  # A value of zero will max the maximum 
  # number of buckets unlimited.

  # max-select-buckets = 0
  max-select-buckets = 1920

Ошибка при достижении max-select-buckets

select mean(avg)
from jmeter
where
  time>1656242940000000000 and
  time<1656242940000000000+10m
group by time(1ms)

ERR: max-select-buckets limit exceeded: (600000/1920)

Изменение типа индекса с inmem на tsi1

Если хотим сохранять тысячи значений тегов

[data]
  # The type of shard index to use for new shards.  
  # The default is an in-memory index that is
  # recreated at startup.  
  # A value of "tsi1" will use a disk based index 
  # that supports higher cardinality datasets.
  index-version = "tsi1"

Логирование узких мест c log-queries-after

[coordinator]
  # The time threshold when a query will be 
  # logged as a slow query.  
  # This limit can be set to help
  # discover slow or resource intensive queries.  
  # Setting the value to 0 disables the slow query logging.
  log-queries-after = "2s"

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

  • Ограничиваем max-concurrent-queries
    • Ограничиваем max_conns в nginx upstream
  • Ограничиваем query-timeout
  • Ограничиваем max-select-series
  • Ограничиваем max-select-buckets
  • Изменение типа индекса с inmem на tsi1
  • Логирование узких мест c log-queries-after

Не ускоряет, но повышает стабильность

Профилирование досок Grafana, анализ запросов к InfluxDB

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

[coordinator]
  # The time threshold when a query will be logged as a slow query.  
  # This limit can be set to help
  # discover slow or resource intensive queries.  
  # Setting the value to 0 disables the slow query logging.
  log-queries-after = "2s"
Detected slow query:
  SELECT top(avg, 3) FROM jmeter
  WHERE time >= now() - 1d AND time <= now()
  GROUP BY time(1m), application, transaction, statut
  (qid: 27, database: jmeter10000, threshold: 2s)

10. 🔬 Замер длительности ответа на запрос из Grafana в InfluxDB: Access Browser, Method POST

Выявляем медленный и частый запрос

Среднее время отклика за тест

SELECT mean(avg)
FROM jmeter
WHERE
  statut = 'ok' AND
  time > now()-10d AND time < now()
GROUP BY testId, time(1d)

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

CREATE
  CONTINUOUS QUERY cq_mean_avg_query_1d ON jmeter10000
  RESAMPLE EVERY 10m FOR 1d
BEGIN
    SELECT   mean(avg) AS avg
    INTO     jmeter10000."archive".jmeter_1d
    FROM     jmeter10000."autogen".jmeter
    GROUP BY testId, time(1d, 0s)
END;

Запрос становится быстрее

SELECT last(avg)
FROM   "archive".jmeter_1d
WHERE  time > now()-10d AND time < now()
GROUP BY testId, time(1d)
----------------------------------------
----------------------------------------
SELECT mean(avg)
FROM   jmeter
WHERE  statut = 'ok' AND
       time > now()-10d AND time < now()
GROUP BY testId, time(1d);

‼️ Сокращайте количество Continuous Queries

CQ не кешируются с nginx

Не нужно делать слишком много CQ

Не нужно делать CQ, которые работают с большими shard'ами (год — много, достаточно дня)

Путь к Continuous Queries

  • 🔬 Анализ логов InfluxDB
log-queries-after = "2s"
  • 🔬 Замер длительности ответа на запрос из Grafana в InfluxDB
Grafana Datasource: Access Browser, Method POST + WebConsole
  • 🚀 Подготовка данных для ответа с помощью Continuous Queries
RESAMPLE EVERY 10m FOR 1d

Ускорение записи в InfluxDB со стороны источника метрик и альтернативы

Одновременная запись метрик может тормозить

🐌 🤔

Оптимизируем запись в InfluxDB c Telegraf и MQ

🚀 😀

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

InfluxDB v2 использует быстрый движок

VictoriaMetrics кеширует ответы и жмет данные

ClickHouse быстр (при наличии памяти) и удобен

🚀 Разгонный потенциал InfluxDB v1.8 огромен

Содержание

  1. ⁉️ Когда оптимизация InfluxDB важна
  2. ⚙️ Разделение данных на разные базы и серверы InfluxDB
  3. ⚙️ «Архивирование» базы InfluxDB в Grafana
  4. 🛠️ Сокращение фильтров по тегам в Grafana
  5. ⚙️ Сокращение метрик из JMeter, Gatling, ...
  6. ⚙️ Кеширование ответов от InfluxDB с nginx
  7. 📊 Мониторинг производительности InfluxDB
  8. ⚙️ Конфигурирование сервера InfluxDB
  9. 🔬 Анализ логов InfluxDB
  10. 🔬 Замер длительности ответа на запрос из Grafana в InfluxDB
  11. 🚀 Подготовка данных для ответа с помощью Continuous Queries
  12. ⚙️ Смена БД InfluxDB v1.8, InfluxDB v2, VictoriaMetrics или ClickHouse

Делите, кешируйте, ускоряйте!

https://github.com/polarnik/influxdb-bench

Ускорение InfluxDB, Смирнов Вячеслав, @qa_load

Feedback :

🙂 👍 👉

👀 🎦 🍿

https://github.com/polarnik/influxdb-bench

Повышаю качество более десяти лет. Занимаюсь системой дистанционного банковского обслуживания юридических лиц. Основной профиль моей работы — тестирование производительности. Развиваю сообщество инженеров по тестированию производительности, помогая коллегам в telegram чате «QA — Load & Performance».