Skip to content

Latest commit

 

History

History
175 lines (117 loc) · 18.3 KB

promql.ru.md

File metadata and controls

175 lines (117 loc) · 18.3 KB

PromQL в StatsHouse

Что это и зачем

PromQL это язык запросов временных рядов, был создан командой Prometheus, получил широкое распространение. В StatsHouse реализовали потому что:

  1. Необходимо было расширить набор доступных операций над рядами;
  2. PromQL как раз про это, широко распространен, хорошо документирован.

Документация по языку PromQL доступна на сайте Prometheus https://prometheus.io/docs/prometheus/latest/querying/basics/

Где найти

Из обычного режима (стартовая страница) перейти в режим ввода PromQL выражения можно нажав кнопку "<>" рядом с именем метрики (в правом верхнем углу). При этом будет автоматически сгенерировано соответствующее текущему графику выражение PromQL.

Особенности реализации

Ответ зависит от возраста данных, запрошенного шага и разрешения метрики

Особенности реализации PromQL вытекают из особенностей хранения рядов в StatsHouse. Вместо сырых пар "время-значение" (как это делает Prometheus) StatsHouse хранит агрегированные за интервал значения.

Для каждого интервала агрегации мы всегда храним:

  • счетчик (сколько значений было);
  • сумму значений за интервал;
  • минимум;
  • максимум.

Опционально храним (если задано в настройках метрики):

  • string top;
  • персентили.

Совокупность этих значений мы называем "дайжест", отдельное значение внутри - "компонент дайжеста".

Любой из пунктов в таблице выше возможно агрегировать из более мелких интервалов в более крупные:

  • счетчики и суммы складываются;
  • из двух минимумов (максимумов) берется меньший (больший);
  • агрегация (и вычисление) "string top" и персентилей реализуется вероятностными алгоритмами.

При поступлении данные агрегируются сначала посекундно и хранятся так 2 суток. По истечении 2 суток секундные данные агрегируются в минутные и хранятся еще 31 день. На 33 день данные агрегируются для часовых интервалов и хранятся бессрочно.

 <- часовые данные | <- минутные данные | <- секундные данные
-------------------x--------------------x-------------------x
   33 дня назад -> |     2 дня назад -> |         сейчас -> |

Для каждого временного отрезка свое разрешение и если сегодня мы можем запросить секундные данные, то через двое суток уже только минутные, а еще через 31 день останутся только часовые.

Чем более старые данные мы запрашиваем, тем больше интервал агрегации, тем меньше детализация данных. В добавок к этому, в общем случае нельзя получить значение в произвольный момент времени (мы не знаем какие именно значения были внутри интервала, т.к. храним агрегаты).

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

StatsHouse сетку времени вычисляет следующим образом:

  1. Если запрошенный временной интервал пересекает границы изменения разрешения (2 и 31 день), то он бьется на под-интервалы;
  2. Для каждого интервала определяется шаг сетки (секунда, минута или час). Берется максимум из:
    • минимальный шаг интервала;
    • разрешение метрики;
    • указанный в запросе шаг округленный наверх до ближайшего кратному часу числу (1, 5, 15, 60 секунд или 1, 5, 15, 60 минут).

Выбор компонента дайжеста

Если не брать в расчет нативные гистограммы Prometheus (отдельная история), то PromQL работает с массивами чисел с плавающей точкой, значением в точке является число. В StatsHouse значением в точке (на интервале агрегации конечно) является дайжест. StatsHouse требует чтобы компонент дайжеста был известен в момент выполнения селектора метрики с тем чтобы компоненты PromQL выражения выше по AST дереву работали с массивами чисел (так проще).

Указать компонент дайжеста можно в селекторе __what__. Список возможных значений: "avg", "count", "countsec", "max", "min", "sum", "sumsec", "stddev", "stdvar", "p25", "p50", "p75", "p90", "p95", "p99", "p999", "cardinality", "cardinalitysec", "unique", "uniquesec". Они напрямую соответствует UI интерфейсу. Постфикс "sec" (seconds) означает "нормированное на длину интервала агрегации значение" (деленное на длину интервала в секундах).

Например, следующий селектор вернет счетчик метрики api_methods за интервал агрегации:

api_methods{__what__="count"}

StatsHouse пытается угадать что имелось в виду в запросе когда селектор __what__ не задан. Делает он это на основании используемых в PromQL выражении функций:

  • "increase", "irate", "rate", "resets" соответствуют __what__="count".
  • "delta", "deriv", "holt_winters", "idelta", "predict_linear" соответствуют __what__="avg"

Например, следующее выражение вернет rate счетчика метрики api_methods за 5 минут:

rate(api_methods[5m])

Если угадать не получилось, то возвращается счетчик для метрик-счетчиков и среднее (сумма деленная на счетчик) для метрик-значений.

Гистограммы

StatsHouse гистограммы хранит в структуре t-digest. По умолчанию гистограмма для метрики не строится, это нужно явно указать в настройках. Если указано, то получить доступ к персентилям можно указав необходимый в селекторе __what__ ("p25", "p50", "p75", "p90", "p95", "p99" или "p999"). Например, следующее выражение вернет 99 персентиль:

api_methods{__what__="p99"}

В Prometheus есть два вида гистограмм "традиционные" и "нативные". "Традиционные" гистограммы появились первыми, кодируются набором лейблов. Теоретически мы их поддерживаем, насколько хорошо будет видно когда допишем скрапер Prometheus совместимых источников. О поддержке "нативных" гистограмм пока речи нет.

История не выглядит завершенной, думаем как сделать лучше. Сейчас рекомендуется пользоваться родными гистограммами StatsHouse, запрашивать персентили через селектор __what__.

Группировка по умолчанию отсутствует

В Prometheus запрос по имени метрики возвращает все ряды метрики (все комбинации лэйблов). StatsHouse наоборот, по умолчанию агрегирует ряды метрики, возвращает результат агрегации. Различие обусловлено особенностями стораджа - им удобно вытаскивать все ряды, нам удобно (быстрее) вернуть агрегат.

К примеру, в StatsHouse запрос "api_methods" всегда вернет единственный ряд. Для группировки нужно воспользоваться одним из операторов агрегации PromQL (перечислить нужные ключи в "by").

Расширения PromQL

Функции над "range vector" также принимают "instant vector"

Но не наоборот. При этом диапазон подразумевается равным расстоянию до ближайшей точки слева. Функция применяется к этим двум точкам. Например, можно написать rate(api_methods) вместо rate(api_methods[1s]) (для интервалов с секундным разрешением эти выражения эквивалентны).

Функция "prefix_sum" для вычисления префиксных сумм

Вычисляет префиксную сумму. Например, для последовательности "1, 2, 3, 4, 5, 6" вернет "1, 3, 6, 10, 15, 21".

Лэйблы "__what__" и "__by__"

Были добавлены чтобы можно было легко выразить любой стандартный запрос StatsHouse. Селектор __what__ принимает компонент дайжеста (перечислены в секции "выбор компонента дайжеста"), селектор __by__ тэги для группировки по ним (группировка по умолчанию отсутствует).

Лэйбл "__bind__"

Используется для привязки переменных дэшборда к селектору. В значении нужно указать разделенные запятой список пар <имя_тэга>:<имя_переменной>. В примере ниже группировка и значения переменной "environment" будут применены к селектору "api_methods":

api_methods{__bind__="0:environment"}

Если значения переменной environment="production,staging" и применена группировка, то выражение выше будет эквивалентно:

api_methods{0="production",0="staging",__by__="0"}

Оператор "default"

Бинарный оператор. Слева принимает ряд, справа - ряд или литерал.

  • Если справа литерал, то NaN значения слева будут заменены на значение литерала справа.
  • Если справа ряд, то логика сопоставления рядов аналогична стандартному оператору "or". NaN значения слева заменяются затем на соответствуюшие значения справа.

Поведение функций "topk", "bottomk"

  1. Считаются не в каждой точке (как это делает Прометей), а для каждого ряда считается характеристика (сейчас сумма квадратов) и по ней выбирается ТОП.
  2. Мы изменили поведение функций topk, bottomk в присутствии сдвигов. В наш ТОП попадают также ряды из соседних сдвигов. Например, если год назад в ТОП попадали ряды "а" и "б", а сегодня там "в" и "г", то в каждом сдвиге будет четырые элемента "а", "б", "в" и "г". Это помогает сравнивать топы между сдвигами (вчера значение ряда было Х, сегодня 100Х).

Несколько значений фильтров на равенство (и неравенство) можно указывать через запятую

Запятая при этом не может быть частью значения. Например, следующее выражение вернет ряды, у которых тег 1 равен "foo" или "bar":

api_methods{1="foo,bar"}

FAQ

Как поделить два ряда?

Бинарные операции (вроде деления) пром проводит над рядами с полностью совпадающими тэгами. У каждого ряда после выполнения селектора появляется тэг "__name__", который содержит название метрики. Соответсвенно по умолчанию две разные метрики друг на друга не делятся (у них разное значение "__name__"). Нужно добиться чтобы движок прома перед выполнением бинарной операции получил два ряда с полностью совпадающими тэгами. Самый прямой путь указать по каким тэгам мы хотим выполнить операцию. Для этого придумали модификатор "on".

https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching-keywords

Если не совпадает только тег "__name__", то избавиться от него можно умножив ряд на единицу (несколько хачно, но работает).

Лучше всегда указывать в модификаторе "on" по каким тегам нужно произвести бинарную операцию.

Как подсчитать сколько ненулевых тегов было в каждый момент времени?

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

https://stackoverflow.com/questions/51882134/prometheus-query-to-count-unique-label-values

count(count by(a) (metric))

В переводе на наш диалект у меня получается:

count(count by(1) (metric{__what__="count",__by__="1"}))

Всё бы ничего, но у меня получается ровный график, потому что нулевые значения тега тоже засчитываются. Может сталкивался кто с таким вопросом?

Так происходит потому что StatsHouse по умолчанию ряды не группирует.

PromQL выражение "count(count by(a) (metric))" подразумевает что metric возвращает все ряды какие есть в базе. Далее, "count by" группирует их по тегу "а", получаем множество рядов с единственным тегом "a" (ряды уникальные, значения "а" не повторяются). Далее "count" считает число этих рядов в каждой точке (отдельный ряд дает +1 в точке если у него там что-то отличное от null).

У нас же выражение metric вернет один ряд (все теги агрегирует еще на уровне ClickHouse) без тегов (с единственным тегом __name__="metric", но у нас в интерфейсе он как тег не показывается). Далее "count by" на нем не имеет смысла (тега указаного в by там нет). В твоем случае правильно будет сделать так:

q=count(metric{__what__="count",__by__="1"})

Выражение внутри count вернет ряды сгруппированые по тегу 1 (по аналогии с "count by(1)" из поста SO), а count вернет число рядов (абсолютно та же семантика что в проме).