Язык запросов для TSDB. Улучшаем PromQL (Александр Валялкин, VictoriaMetrics)
Добрый день! Сегодня я вам расскажу немного про PromQL. Это язык запросов для time-series баз данных. Затем расскажу, как мы его усовершенствовали в разрабатываемой нами time-series базе данных VictoriaMetrics.
PromQL — это язык запросов, который используется в системе мониторинга Prometheus.
PromQL отличается от других альтернативных запросов для time-series баз данных — таких, как SQL, InfluxQL или Flux, или от языка запросов, который используется в Graphite. Многие time-series базы данных используют свои собственные языки запросов. Я считаю, что PromQL — это один из лучших языков запросов для time-series баз данных, потому что он лаконичный и позволяет решать большинство задач, стоящих перед пользователями, которые хотят работают с time-series данными.
Рассмотрим пример SQL запроса, типичного для выборки time-series данных.
Что делает этот запрос? Он выбирает все метрики с именем node_network_receive_bytes_total
. Для всех этих метрик он выбирает их имена, все timestamps и все значения. Как видите, запрос очень простой. Он состоит из подзапроса, состоит еще из одного подзапроса и из JOIN.
Теперь посмотрим, как он выглядит в PromQL. Вот этот же запрос в PromQL языке программирования.
s
PromQL позволяет строить красивые графики в Grafana. Этот запрос завернут в rate, потому что он оканчивается на total. Такая метрика называется counter, и она возвращает значение, которое постоянно растет. Если я бы его просто вывел на этом графике, то мы бы увидели тут постоянно растущий вверх график. Чтобы вывести скорость изменения этого графика, применяем функцию rate и получаем такие графики, где видна скорость получения данных по сети.
Но у PromQL есть еще недостатки по сравнению с SQL.
У него ограничена функциональность, т. е. на SQL можно сделать то, чего нельзя сделать на PromQL. SQL — более мощный язык. Например:
- В SQL можно в SELECT вставить все, что хочешь, в отличие от PromQL, где можно делать выборки только по заранее фиксированному набору полей — имя метрики, лейблы, значения и таймстемпы.
- В PromQL ограниченный JOIN.
- Нет нормального GROUP BY. Есть аналог, он делает группировку по временным рядам.
- Нет сортировки. Есть стандартная сортировка только по времени.
- Нет HAVING.
- И нет еще других фич, которые есть в SQL.
Эти ограничения не так важны для тех, кто работает с time-series базами данных. Потому что SQL — мощный язык, но для time-series он не очень походит.
https://github.com/grafana/grafana/issues/11451
https://github.com/prometheus/prometheus/issues/3746
https://github.com/prometheus/prometheus/issues/3806
Следующий недостаток у PromQL — это data model в голове главного разработчика Prometheus и языка программирования PromQL. Его зовут Brian Brazil.
Из-за этой модели, которая у него в голове, и которую он не хочет никак изменять, существуют баги, которые Brain считает не багами, а фичами.
- Первая баго-фича — это то, что PromQL возвращает пустой график, если интервал между соседними точками на графике меньше, чем
scrape_interval
, т.е. интервал между реальными точками, которые хранятся в базе данных. Если он становится меньше, то мы в итоге получаем пустой график. - Вторая бага-фича — это то, что функция
increase()
, которая возвращает разницу между точками во временном ряду, может возвращать дробное значение, даже если во временном ряду хранятся только целочисленные значения. Например, метрика увеличилась на 10, аincrease()
вернет 9,5. Это из-за интерполяции, которая соответствует data model в голове Brian Brazil. См. вот эту багу. - Также можно принести к недостаткам PromQL то, что у него нет функции для вычисления min/max значения для rate. Rate — это функция, которая возвращает скорость изменения метрики на заданном интервале. Т. е. rate возвращает среднюю скорость изменения метрики на этом интервале, но не возвращает ни максимального, ни минимального значения.
- В rate нужно указывать в квадратных скобках значение
[d]
. Это интервал от текущей точки, для которой рисуется график, до точки в прошлом, на основе которого вычисляется rate. Для тех, кто только начинает пользоваться PromQL, это не совсем понятно и удобно. Я был таким же начинающим пользователем, и я не понимал назначение этих квадратных скобок, и часто пропускал их, и не понимал, почему мои графики не показываются, а Prometheus ругается на мои запросы.
Также к недостаткам PromQL можно отнести то, что в связке с Grafana, когда в Grafana рисуются дашборды, в запросах часто приходится повторять один и тот же набор фильтров.
Вот пример стандартного фильтра, который встречается в большинстве дашбордов в Grafana. И он должен повторяться в каждом запросе.
Как решить эти проблемы?
Та-там — VictoriaMetrics. Это система time-series баз данных, которую разрабатываем мы.
- Это time-series база, которая может работать как long-term remote storage для Prometheus, т. е. может хранить данные из Prometheus.
- Она может принимать данные в форматах не только Prometheus, но и Influx, OpenTSDB, Graphite, CVS, JSON lines, Prometheus text exposition format.
- VictoriaMetrics понимает PromQL, в нее встроен движок PromQL.
- VictoriaMetrics улучшает PromQL с помощью MetricsQL. Она решает все проблемы, которые были на предыдущем слайде. Дальше мы рассмотрим несколько таких примеров.
Примеры:
Первый пример — убираем квадратные скобки в rate.
Вот такой PromQL запрос можно записать в таком виде в VictoriaMetrics. VictoriaMetrics понимает весь стандартный PromQL, но некоторые части запроса можно сократить.
Что тут происходит? В квадратных скобках мы пишем $__interval
. Grafana вместо этой переменной вставляет туда интервал между соседними точками, которые она рисует на графике. Иногда пользователи приходят к вот такому решению, когда указывают интервал в квадратных скобках. И если они перейдут на VictoriaMetrics, то им не обязательно указывать этот интервал. Можно написать rate без квадратных скобок — rate(m)
, это будет работать аналогично rate(m[$__interval])
.
Следующий пример. Мы можем в квадратных скобках указывать множитель для этого интервала. Например, вот такой запрос, где интервал умножается на 5. Это означает, что для каждой точки мы будем вычислять значение на 5 последних интервалах. В PromQL можно изменить на вот такой запрос: rate(m[5i])
.
Следующий пример. Мы можем этот интервал использовать в вычислениях, т. е. указывать не только в квадратных скобках, но и в самом запросе. Например, в PromQL есть такая функция deriv()
, которая вычисляет производную.
Вот написано, как вычислять производную с помощью MetricsQL в VictoriaMetrics. Мы берем значение метрики. Отнимаем от него значение метрики, которое было на 5 интервалах назад, и делим на длину этого интервала, состоящий из 5 интервалов. Функция step()
возвращает длину интервала в секундах. Получаем производную.
В VictoriaMetrics решена бага-фича, где в запросе указывается интервал, который меньше, чем расстояние между соседними точками, хранимыми в базе данных. Как вы помните, Prometheus в этом случае возвратит пустой график, VictoriaMetrics возвратит нормальный график, который ожидают пользователи.
Prometheus для вычисления rate()
нужно, чтобы на каждом интервале было минимум две точки. Если там будет меньше, чем две точки, то Prometheus вернет пустой график. А VictoriaMetrics смотрит, если на данном интервале нет ни одной точки, то она берет предыдущие пару точек, но при условии, если они находятся не дальше, чем двойной scrape_interval
на данном time-series для того, чтобы не рисовать график там, где его не должно быть. Таким образам получаем такой график. Тут у нас интервал указан в 0,1 секунду. Видно на графике, что между вертикальными линиями 1 секунда и график нормально рисуется, все отлично, хотя scrape_interval, т. е. расстояние между точками, которые хранятся в Prometheus, составляет, по-моему, 10 секунд.
Также в VictoriaMetrics добавлены rollup функции, которые возвращают одновременно три значения. Это минимальное, максимальное и среднее значения. Это позволяет наглядно видеть границы вашей метрики и не терять информацию при зуме в графиках.
VictoriaMetrics поддерживает несколько rollup функций такие, как просто rollup. Это функция для метрик, которые могут меняться во времени произвольным образом.
Также поддерживается функция rollup_rate. Это аналог rate. Это функция для метрик типа «счетчики» (Counters). Еще есть другие функции, которые иногда могут пригодиться — см. документация по MetricsQL.
Это пример применения rollup_rate к метрике. Как мы видим, возвращаются у нас три линии, три значения. Максимальное значение для каждой точки, построенной на графике, а также среднее значение и минимальное.
Если на этом графике построить просто rate для такой же величины, то мы бы увидели только вот этот средний график. Как мы видим, что у нас максимальное значение намного превышает среднее. И есть еще минимальное. Если мы это будем зумить, будем выбирать график за больший промежуток времени, либо меньший, то мы не будем терять информацию о максимальном и минимальном значениях в отличие от rate, где при зуме в большую сторону, то у нас rate берет среднее значение по этому интервалу.
В VictoriaMetrics есть также функции range_*
, которые иногда бывают полезными. Они вычисляют глобальное значение по всему отображаемому графику, который вы запросите.
Есть минимальное, максимальное, среднее значение, а также quantile, sum, первое значение, последние значение.
Где он может быть полезен? Например, вычислить отклонение от среднего. Вот там какой-то rate вычисляется. И мы от него отнимаем тоже rate и заворачиваем его в range_avg()
. И получаем такой график, где видно, что он колеблется около нуля.
Есть еще оператор default, который заменяет пропуски в графиках значениями, которые указаны справа от default.
Например, график без дефолта показывал бы только значение sinus, который больше, чем 0,3. Он бы показывал вот такие выступы sinus. А мы пишем дефолт и указываем rand, т. е. случайное значение. И видим, что там, где у нас были бы пропуски графика, есть рандомное значение. Вместо rand обычно пишут дефолт 0, чтобы заполнить пропуски графика.
Также есть более удобное управление label«ами. Стандартный PromQL поддерживает управление label«ами. Там есть две функции: label_replace и label_join, которые сложны в использовании — постоянно нужно смотреть документацию, чтобы знать, какие аргументы что означают.
В MetricsQLL я решил добавить функции, которые более простые для управления label«ами:
- label_set — позволяет устанавливать произвольно labels на time-series. Устанавливает label со значением bar во все временные ряды query.
- label_del — для удаления произвольных labels из query.
- label_keep — удаляет все labels, кроме тех, которые перечислены в функцию.
- label_copy и label_move — копирует и меняет label.
https://victoriametrics.com/promql/expand-with-exprs
В PromQL есть еще такая фича, которая позволяет упрощать сложные PromQL-запросы, где есть много повторений, которые занимают много строчек кода. Это WITH templates. Это аналог common table expressions — CTE. И эту штуку можно попробовать вот здесь.
Самый простой пример — убираем повторяющиеся фильтры. Вот у нас есть PromQL-запрос. Как мы видим, в нем повторяются фильтры {instance=~”$node:$port”, job=~”$job”}
в каждом из запросов.
С помощью WITH templates
это можно заменить на вот такой запрос. В нем прописано with (cf = {instance=~”$node:$port”, job=~”$job”})
. cf
— это имя шаблона, который будет потом использоваться у нас дальше. Дальше указываем фильтры, которые везде повторяются. И потом прописываем вот эти метрики, вместо повторяющихся фильтров пишем cf
.
Следующий пример — WITH templates
поддерживает функции. Вот у нас есть функция ru
, которая принимает три аргумента: free, limit, filters. И вычисляет вот такую штуку.
Ru — это resource usage. Free — это количество ресурсов, которое у нас доступно еще. Limit — это максимальное количество ресурсов. Filters — это фильтры метрикам для данного ресурса.
Видно, что эта формула будет возвращать resource usage и resource utilization, т. е. это процент использования ваших ресурсов.
В WITH мы прописываем эту функцию, потом ее используем. Передаем туда эти метрики из node exporter с таким фильтром для labels. И такая функция вернет нам процент оперативной памяти. Вместо метрик для оперативной памяти сюда можно подставлять произвольные метрики, например, использования дискового пространства, использования сети. И получается у вас resource usage.
Вот более сложный пример. В WITH templates
можно прописывать сколько угодно templates. У нас тут прописано три templates. И потом мы вызываем функцию ru, про которую было рассказано. И получаем процент использования процессора в системе.
Функция ru уже добавлена в MetricsQL в VictoriaMetrics, т. е. не надо прописывать WITH такую штуку. Можно сразу писать ru.
Первый параметр — это количество ресурсов, которые еще не использованы. И второй параметр — это максимальное количество ресурсов. И она вернет вам процент использования ресурсов.
Мы рассмотрели только небольшую часть возможностей и фукнций MetricsQL, которые есть в VictoriaMetrics. Там есть еще куча других улучшений, которые кому-то могут понадобиться, а кому-то могут показаться бесполезными. Но они туда добавлялись постепенно в тот момент, когда они нам требовались для наших нужд, либо для нужд наших клиентов.
Вот несколько улучшений таких штук.
- Это операторы if, ifnot.
- Возможность указывать доробные интервалы в отличие от PromQL:
rate(q[1.5m]) offset 0.5d
. - Функции
start()
иend()
там есть, которые возвращают первое и последнее значение времени на рисуемом графике. - Есть функция
smooth_exponential()
, которая сглаживает график. - И есть куча других функций, советую вам пройтись по этой ссылке и почитать, что там есть еще.
Мы закончили с MetricsQL и сейчас мы рассмотрим такую штуку, как template meta-variables для Grafana.
Как вы знаете, в Grafana есть template variables. Можно в дашборде установить много произвольных template variables и потом их использовать в каждом графике на этом дашборде. Затем сверху в Grafana появляется меню, где можно выбирать значения для каждого variables. И, соответственно, будут обновляться ваши все графики.
Как мы уже рассмотрели ранее, недостаток PromQL в связке с Grafana в том, что для таких дашбордов нам приходится часто повторять одни и те же фильтры для template variables. И если вы собираетесь менять ваш дашборд, то большая вероятность, что вы где-нибудь забудете что-то скопировать, либо изменить и у вас графики с течением времени станут показывать не то, что вы хотели.
Template meta_variables решает эту проблему. Когда думали о том, как добавить в WITH templates эти variables в Grafana, чтобы сделать удобнее, то решили первым сделать template meta-variables, которые будут полезны для всех пользователей Grafana.
Вот есть issue для этого и наш pull request. Мы создали issue, создали PR. И только разработчики Grafana до сих пор не могут рассмотреть этот PR. Так что советую пройтись по ссылкам, поставить лайки, прокомментировать и спросить: Когда вы это замержите?
.
Вот что позволяет делать template meta-variables. Мы прописываем новую variables, которая называется commonFilters. И указывает вот такое значение, где упоминаются другие variables: job, instance.
И тогда, где у нас повторяются эти фильтры, мы можем заменить такой запрос на вот такой запрос. Почти как WITH templates
, только это будет работать даже со стандартным Prometheus.
- Типичный дашборд содержит десятки запросов с повторяющими фильтрами.
- Template meta-variables позволяет заменить все эти повторяющиеся фильтры одним meta-variables.
- Это позволяет быстро редактировать запросы на дашбордах. Мы используем один фильтр вместо того, чтобы править каждый отдельный запрос на дашборде.
- Template meta-vars полезны не только для Prometheus, но и для любого datasource в Grafana.
Сейчас VictoriaMetrics пока с закрытыми исходниками, но на следующей неделе собираемся открывать исходники.
Будут открыты исходники как single-node версии, так и кластерной версии. И я считаю, что это очень знаковое событие, т. к. сейчас нет time-series баз данных с открытыми исходниками, у которых есть нормальная кластерная версия. На следующей неделе в open source можете использовать бесплатно. Update: исходники уже открыты — см. исходники single-node версии и исходники кластерной версии
Можно ли использовать WITH templates в глобальной области видимости для одного дашборда или для нескольких дашбордов в Grafana?
Мы планируем так сделать. И мы думаем над этим. Первым шаг — это разработка template meta-variables, которая не зависит от WITH templates и будет работать везде.
Александр, а что будет происходить, когда мы льем данные из разных источников? У нас с одной стороны InfluxQL, с другой стороны Graphite, выборки все равно Prom«вые?
Да, данные можно лить с разных источников по разным протоколам. А запрашивать данные пока можно с PromQL и MetricsQL, пока мы не собираемся добавлять дополнительный язык запросов. В будущем, может быть, что-нибудь добавим, но пока все данные запрашиваем с помощью MetricsQL.
Т. е. я нафигачил туда данных из Graphite, а потом как я к ним обращусь? Они как-то нормализуются?
Все time-series данные, которые VictoriaMetrics поддерживает, они имеют регулярную структуру, т. е. у них есть matric name и теги, и value.
Но в Graphite нет тегов.
В Graphite есть теги.
В Graphite недавно есть теги.
И VictoriaMetrics поддерживает теги графитовские. Т. е. она с тегами работает и без них.
Т. е. она не будет интеллектуально как-то конвертировать все это дело?
Нет. Имя метрики Graphite без тегов нас попадет в metric name вместе с точками этими в VictoriaMetrics, т. е. нет никаких конвертаций. Вернее, есть небольние конвертации для Influx line protocol, потому что там немного странный протокол. Там есть теги и филды (tags & fields). Имена филдов преобразуется в metric names. А теги становятся лейблами.
Спасибо!
У меня вопрос про alerting. Мы сейчас используем Thanos, он PromQL никак не расширяет, поэтому смело расставляем alerting в Prometheus и живем. А с расширением PromQL и с последующим alerting, как у вас проблема решена? У вас отдельный alerting?
VictoriaMetrics поддерживает полностью весь PromQL, который стандартный в Prometheus. Можете писать стандартные запросы Prometheus«а, они будут работать. Или в дашбордах Grafana, которые работают с Prometheus, заменить в их datasource для дашбордов только url на VictoriaMetrics. Ваши дашборды будут работать, ничего не поломается. Это первое.
И второе про alerting — сейчас alerting’а нет в VictoriaMetrics. Т. е. предполагается, что alerting будет происходить на Prometheus, которые пишут данные в VictoriaMetrics. Но в будущем мы собираемся создать с alerting сервис, который будет отдельно от Prometheus, отдельно от VictoriaMetrics, который будет совместим с alerting rules Prometheus’а. И он будет опрашивать внешний datasource. Он сможет опрашивать VictoriaMetrics, доставать оттуда данные и на основе их делать alerts, и отправлять их в alert manager. Update: такой сервис уже есть — vmalert.
У VictoriaMetrics своя база данных или она является смесью синтактического сахара для запросов?
У VictoriaMetrics собственная база данных, написанная с нуля. И она намного эффективнее, чем конкурирующие базы данных. Она эффективнее по использованию ресурсов. Она требует меньше места на диске, т. е. данные сжимаются лучше, чем у конкурентов. Она требует меньше ресурсов процессора. И быстрее выполняет запросы. Также она требует меньше оперативной памяти для хранения большого количества метрик, time-series данных.
Еще вопрос. Вы сказали, что решили все проблемы PromQL.
Не все.
Из тех, которые вы показали.
Те, с которыми мы сталкивались.
Я помню, там был JOIN. Полноценный JOIN нормально работает с нескольких таблиц? Я смогу слинковать данные?
В VictoriaMetrics данные можно лить с разных источников, в том числе с нескольких Prometheus, с нескольких Graphite, с любых источников. Эти данные все сливаются в одну таблицу фактически. И запросы можно строить на всех данных этой таблицы с разных источников. Можно отправлять запросы в VictoriaMetrics и строить запросы по данным, собранным с разных Prometheus, хоть с тысячи, которые пишут в VictoriaMetrics.
Т. е. ее можно использовать как кэш, как надстройку над текущими базами данными?
Нет, не совсем. VictoriaMetrics, в отличие от Thanos, не опрашивает Prometheus’ы, которые в нее пишут. VictoriaMetrics полностью автономная база. Она не знает ничего про все эти источники данных. Источники данных пишут данные в VictoriaMetrics. И потом VictoriaMetrics строит все запросы по всем данным, которые у нее хранятся. Она не лезет во внешние источники, чтобы забрать оттуда какие-то данные, в отличие от Thanos. Thanos забирает неданво записанные данные из Prometheus’ов, с которыми он работает. И он подвержен проблемам обрыва сети или тормозов Prometheus. VictoriaMetrics этим проблемам не подвержена.
Понятно, спасибо!
Спасибо за доклад! Вы сказали, что вы открываете исходные коды баз данных вашей VictoriaMetrics как single-node, так и кластерной версии. Это будет прямо open source или все-таки будет какая-то лицензионная политика платной версии?
Исходники будут открыты под Apache2 лицензии. Эта лицензия позволяет вам использовать исходники где хотите, главное оставлять копирайт наш, чтобы видеть, что вы использовали эти исходники. Т. е. вы можете в коммерческих продуктах использовать, но главное, вы должны указать, что в вашей коммерческой продукции использован код VictoriaMetrics.
Спасибо! У вас на сайте есть сравнение с InfluxDB, с Timescale. Сравнение с колоночными базами проводили вы? Где-то можно посмотреть benchmarks?
Сравнение с колоночными базами не проводили. InfluxDB фактически тоже колоночная база данных. Мы с ней проводили сравнение. С ClickHouse не проводили сравнение. Но на сайте Altinity, это компания, которая оказывает консалтинговые услуги по ClickHouse, есть сравнения. Они сравнивали производительность ClickHouse как time-series базу данных с Influx, по-моему. И с Timescale вроде тоже. См. https://altinity.com/blog/tag/benchmark/ .
Я сравнивал, какие у них результаты были с нашими результатами. Там есть два типа запросов, которые быстро выполняются, т. е. за пару миллисекунд и тяжелые запросы, которые выполняются в течение секунды и больше.
Легкие запросы VictoriaMetrics выполняет намного быстрее, чем ClickHouse, а тяжелые запросы немного быстрее, чем ClickHouse, т. е. там примерно одинаково: кое-где медленнее, кое-где быстрее.
Какое при этом потребление ресурсов?
Мы проводили benchmarks с учетом того, что потребление ресурсов 100%. И ClickHouse, как мы знаем, старается тоже потреблять 100% CPU при выполнении запросов.
Понятно, спасибо!
Как у вас с резервированием, с кластеризацией?
Репликации нет. Мы решили не делать репликацию на уровне кластера, на уровне VictoriaMetrics, потому что правильно сделать репликацию, чтобы она была надежной и гарантировала то, что она рекламирует, это очень сложно. Мы пока не придумали, как правильно это сделать. И полагаемся на реплицированный storage, на google cloud диски. Они рекламируются как диски, которые уже реплицируемые и на них данные не могут потеряться или испортиться из-за проблем оборудования, т. е. из-за выхода из строя физических дисков. Поэтому мы решили вот так пока делать. Может быть, в будущем добавим репликацию, но пока вот так. Т. е. желательно, если вы хотите использовать кластерную версию в production, хранить данные на надежных дисках, чтобы репликация уже происходила на уровне дисков, где вы храните данные. Update: мы добавили репликацию в кластерную версию VictoriaMetrics — см. https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#replication-and-data-safety .
У меня еще вопрос. У вас можно как-то реализовать функцию, когда, например, данные за вчера наложить на данные за сегодня?
Да, это стандартная функция, которая есть в PromQL. Offset называется. Можно прописать offset 1d, и она вам вернет все значения для этой метрики за предыдущих день. С этими значениями можно делать любые операции — например, складывать, вычитать, сравнивать с текущими значениями.
Есть какие-нибудь аналитические функции? Т. е., например, прочитать перцентиль этого дня, например, понедельник за последние три месяца?
Эта стандартная функциональность PromQL — quantile_over_time. Это то, что поддерживает Prometheus, перцентили там есть, можно считать за любой промежуток времени, который нужно передавать в квадратных скобках. Например, quantile_over_time(0.99, m[24h])
подсчитает 99-й персентиль по значениям метрики за последние 24 часа.
Спасибо!
Насколько будет открытой модель разработки? Вы сейчас заопенсертитесь. После этого возможны два варианта: либо делаем merge раз в полгода, и они там по 5 GB, либо ведете разработку в открытую
Мы будем стараться вести разработку в открытую. Может быть, вопрос по другому перефразировать? Будем ли мы мержить сторонние pull requests?
Это вторая часть вопроса.
Будем, но не все. Если мы будем видеть, что они полезны, то будем. Но будем стараться делать не так, как Brain Brazil, т. е. не будем говорить, что у нас есть data model и все. Если там хороший pull request, то будем мержить.