За короткое время Prometheus стал одним из самых популярных средств для мониторинга. Благодаря, в том числе, и высокой скорости своей работы. Его локальное хранилище отлично подходит для краткосрочного хранения метрик и работы с ними. Иногда хочется хранить метрики распределённо месяцы и годы, автоматически разрежая старые данные, но не меняя интерфейса работы с ними.
Как раз об этом расшифровка доклада Алексей Палажченко на RootConf 2018. В докладе: Prometheus, Local Storage TSDB, Remote Storage Prometheus, PromQL, TSDB, Сlickhouse, PromHouse, немного InfluxDB.
Кому интересно, прошу под кат.
Друзья! Всем привет! Меня зовут Алексей Палажченко. Я работаю в компании Percona. Я хотел бы вам рассказать про долгосрочное хранение метрик в Prometheus.
Я работаю в компании Percona и делаю продукт, который называется percona monitoring and management. Это коробочное решение, которое наши клиенты ставят себе. PMM полностью open source. Он состоит из Prometheus, Grafana для рисования графиков, custom софта query analytics и нашей собственной обертки, которая позволяет вам делать некоторое управление. Например, вы можете добавить scrape target в Prometheus. Это новые источники, откуда он будет брать метрики без того, чтобы руками заходить в контейнер или виртуальную машину и править файл конфигурации.
Важно понимать, что это не SaaS. У нас нет production'а. Наш production находится у наших клиентов. На нем экспериментировать не очень хорошо. У нас есть ближайшая вещь, которую можно было бы назвать production — это https://pmmdemo.percona.com/. На момент доклада pmmdemo.percona.com пришлось выключить из-за GDPR.
Мы ставим PMM клиентам — коробочное решение: docker-контейнер или виртуальная машина. Им всем нравится Prometheus. Некоторые люди, которые первый раз смотрят на Prometheus, сталкиваются с pull моделью. Для новичков это неудобно. Вообще отдельный большой разговор. Можно спорить о pull или push методах. В среднем это примерно одно и то же.
Некоторые вещи в Prometheus очень крутые.
Prometheus query language — это действительно крутая вещь, которой нет аналога практически нигде.
Второе, что нравится, — это service discovery. Если у вас какая-то динамическая инфраструктура, kubernetes, то автоматически не нужно добавлять руками все target'ы для мониторинга. Если статическое — это тоже можно сделать довольно просто. Нужно использовать файл конфигурации.
Клиентам Prometheus нравится. Они хотят хранить метрики дольше и больше. Кто-то использует Prometheus только для оперативного мониторинга. Но кто-то хочет хранить метрики дольше, смотреть динамику, сравнивать с графиками год назад. При этом цель долгосрочного хранения метрик не является целью для проекта Prometheus. Изначально он создавался для того, чтобы хранить метрики недолго. SoundCloud хранит метрики буквально несколько дней. В Prometheus есть механизмы, которые позволяют это делать дольше, но они устроены немножко сбоку. Поэтому мы можем сделать решение для экосистемы Prometheus, не меняя само ядро системы. Мы на основе них можем сделать свое собственное решение в рамках этой же экосистемы.
Это не доклад про готовые решения. Это доклад про наш опыт, про нашу боль, про наши попытки. Если вы рассчитывали, что после этого доклада вы скачаете репозиторий или docker-контейнер, запустите и все заработает, то это не так. Но при этом это достаточно близко к тому, чтобы быть так. У нас есть наработки. Они все opensource. Вы можете взять попробовать. Они не готовы к production пока еще. Но с той информацией, которая есть в этом докладе, вы сможете понять почему так, что можно сделать лучше. Вы можете сделать свое решение, которое будет хорошо подходить вам.
Как метрики хранится в Prometheus? Есть local storage. Есть remote storage. Это фактически два разных мира. Они слабо пересекаются. Поэтому и доклад тоже разделен на 2 части.
Если вы были на предыдущем докладе в главном зале, где как раз было хорошее интро в Prometheus, вы знаете что локальный storage — это отдельная библиотека, которая называется TSDB. TSDB не имеет ничего общего с OpenTSDB. TSDB — это отдельный Go пакет, который можно использовать из своей программы на Go. На уровне библиотеки TSDB нет никакого клиента и сервера.
Эта библиотека оптимизирована для работы именно с time series данными. Например, в TSDB есть delta encoding, который позволяет вам хранить не сами числа, а именно изменения между этими числами. Это позволяет вам вместо того чтобы хранить 16 байт — хранить 1 байт. 1 байт под время и 1 байт под значение. То есть вы храните в среднем 1 или 2 байта именно за счет этого хорошего сжатия.
TSDB оптимизирован для pull модели. Данные туда только добавляются. В Prometheus нельзя записать исторические данные. Для этого нет API. Максимальная дельта примерно 5 минут. Если данные более старые, они не будут приниматься.
В TSDB нет никакого встроенного downsampling tsdb#313. Есть открытый issue, в котором была дискуссия на тему того, что в целом есть проекты, которые вокруг Prometheus что-то делают и там есть downsampling. Пока что решение такое, что в TSDB не будут добавлять downsampling.
Как бы нам получить данные с TSDB? TSDB — это база данных на диске. Работать с ней можно, если вы пишете Go программу. Но если вы не пишите программу на Go, то есть JSON API, который позволяет вам сделать query запросы. Если вы хоть раз пользовались Prometheus и хоть раз строили какой-нибудь график, вы знаете стандартный Query API, в котором есть параметр query, в котором можно выполнить любой PromQL запрос и опционально время. Если время отсутствует, то берется текущее время.
На слайде выделено специфический query, который в реальной жизни вы редко увидите. Это хак. Это позволяет нам вытащить все метрики, которые есть в Prometheus. Как это работает? На уровне PromQL говорится что нельзя написать такое выражение, которое бы заматчило все time seriers. Это прямо в правилах написано. Еще одно правило говорит о том, что нельзя сделать такой matcher, в котором все значения пустые. Если вы напишете просто фигурные скобочки это не будет работать. Если вы напишите name не равно чему-нибудь (не пустое значение), то не будет работать. А вот это реальный хак, который позволяет это сделать. При этом он даже не особо документирован. В самом коде есть комментарии о том, что это работает.
Второй запрос — это query_range, который делает то же самое, но возращает вам данные в диапазоне и с неким шагом. Он по сути делает query несколько раз для каждой step начиная с начала и до конца. Это тот API, который используется для того чтобы рисовать графики. Первый API использует для получения моментальных значений.
У нас есть API для получения метаданных. Если мы хотим получить все названия метрик, мы делаем вот такой запрос, где match это массив метрик. Их может быть несколько аргументов, но в данном случае мы передаем тот же самый match, который нам возвращает всё.
Второй мета API, который возвращает нам значение всех лейблов. Если мы хотим увидеть список всех job, мы вместо label_name пишем job и получаем этот список. Эти API возвращают нам JSON.
Есть еще один API, который возвращает нам все метрики самого Prometheus в формате, который является нативным для экспортеров. Формат называется expfmt. В самом Prometheus есть Federation API, который вам позволяет сделать такой запрос. Для чего это нужно? Самый простой вариант, если у вас есть какой-то код, который уже работает с expfmt, то вам не нужно его переучивать на то, чтобы работать с каким-то custom JSON API. Этот формат гораздо проще стримится, потому что если у вас JSON где-то на верхнем уровне объекта, чаще всего вам нужно этот объект распарсить целиком. Здесь это можно делать по строчке.
Самое главное это то, что это отдельный API. Он работает именно как настоящий export. Вы можете взять и другим Prometheus его заскрейпить. Это обычный job с обычными параметрами. Вам нужно передать параметр — query url. Если вы сделаете запрос curl, вы получите здесь то же самое. Мы получаем все метрики для текущего значения времени. Единственный нюанс: необходимо установить honor_labels для того, чтобы Prometheus, который будет скрейпить другой Prometheus через этот API, не перетирал значение job и instance label. Используя этот Federation API, вы можете загрузить все данные из одного Prometheus в другой.
Как это можно использовать?
Во-первых, надо самое главное сказать, что так делать не надо. TSDB оптимизирован для другого режимы работы. Если у вас есть Prometheus, которой скрейпит много данных, то он делает большое количество ввода-вывода. Если вы используете Federation API, то количество ввода вывода увеличивется примерно в 2 раза. Есть нюансы. В зависимости от того, как часто вы делаете скрейпинг на federate и как часто вы скрейпите таргеты. Если время не меняли, то это действительно увеличивает нагрузку в два раза. Поэтому если вы хотите заскейлить ваш Prometheus и включить федерейшн, то вы его убьёте. Нагрузка увеличится два раза.
Второй момент. Вы будете пропускать данные. Вы получите конфликт данных. Почему так? Этот API, как почти любой API в Prometheus, не атомарный. Если придут новые данные, закончится новый скрейп в тот момент, когда ваш federate запрос еще идет, вы можете получить для одной time series одни данные, для другой — уже новые. Если это не связанные time series, то это в целом не страшно. Но если у вас summary или гистограмма, которые на уровне expfmt представляется нескольким базовым метриками, то между ними будет несогласованность.
Как мы можем решить эту проблему атомарности? В Prometheus есть recording rules, который позволяет создать новую time series из существующей time series. Это можно делать реже. Это один из способов сделать downsampling. Например, скрейпите target каждую секунду, но дальше мы хотим сделать агрегацию node_cpu за одну минуту. Группировка в Prometheus 2.0 позволяет вам делать эти агрегации последовательно. Правила, которые находятся в одной группе, выполняется строго последовательно. В этот момент нет проблемы атомарности, нет проблемы, что данные поменяются в процессе. Но это не решает проблему того, что это допустим какие-то другие данные, которые связаны логически с этими, но не связаны с точки зрения модели данных. Чистой атомарности пока нет. Есть открытый issue на эту тему. Можно делать снапшоты. Можно сделать запрос PromQL к базе данных TSDB и из полученных значений отбросить все сэмплы, которые меньше какого-то значения времени, в которое началось в evaluation. Это был бы самый простой способ, но пока его не сделали.
Важно понимать что recording rules нужно делать на нижнем Prometheus, а не на том, который делает federation. Иначе вы будете пропускать пики, у вас неправильно будет работать мониторинг.
Как мы можем использовать эту комбинацию этих вещей для того чтобы сделать downsampling и долговременное хранение.
Первое. Мы просто ставим federation и загружаем все данные с того Prometheus. Это странное регулярное выражение похожее на зойдберга — это на самом деле просто двоеточие. Слева и справа от двоеточия звездочка. Мы используем стандартное название для recording rules, которое добавляет двоеточие в середину. При делении оригинального имени слева будет уровень агрегации, а справа функция. У нормальной метрики двоеточия нет. Если есть двоеточие, то это признак того, что это агрегация. После этого мы используем это название метрики в нашем графике. Если мы хотим, чтобы наш график, наш дашборд в grafana работал и c главным Prometheus, и c тем кто стоит выше, мы можем использовать выражение or. Мы берем либо одну метрику, либо другую, в зависимости от того которая есть. Мы можем схитрить и при помощи relabeling переименовать новую в метрику в старое имя. Это довольно опасный подход. Можно неправильно написать регулярные вложения и у вас будет конфликт time series. Prometheus будет писать много warnings в лог. Вы это увидите, но найти причину может быть довольно сложно. Но если сделать аккуратно, например сгенерировать эти регулярные выражения программно, то это будет работать. Дальше у вас будет обычный дашборд, где используется только node_cpu. В зависимости от того, какой Prometheus используется, вы будете получать либо исходные данные либо же агрегированные.
Как я уже сказал, recording rules можно генерировать довольно просто. Мы просто получаем все time series через api, которые я уже показывал. Мы создаем правила и эти правила должны использовать правильные функции и операторы. Не нужно использовать там rate с gauge. Это будет неправильно работать. Нужно использовали только с count. На том уровне, где вы работаете, у вас может не быть информации о типах данных. Например, если вы используете expfmt. Там информация о типах есть. Если JSON API, там этого нет. Как следствие, выражение, которые вы автоматом сгенерируете, может не иметь никакого физического смысла. Поэтому, можно использовать там либо белый список либо черный список. В зависимости от этого генерировать либо нужное вам правило, либо выкидывать те правила, которые не имеют смысла. Есть инструмент promtool, который позволяет вам проверить, что те правила, которые вы сгенерировали, тот конфиг, который вы сгенерировали, он имеет смысл. Он имеет корректный синтаксис.
Если у нас есть Grafana и там несколько Prometheus, нам нужно знать, на какой Prometheus отсылать запрос. Как бы нам это сделать?
Один из способов — это поставить специальный прокси, который будет смотреть на время в запросе, и в зависимости от этого выбирать Prometheus. В запросах есть время начала и время конца. В зависимости от этого можно руками делать роутинг. Можно было бы написать какую-то программу, которая это делает. На практике это делается nginx с модулем lua или небольшой программой.
А нужен ли нам вообще API? Можем ли мы работать с TSDB напрямую? Есть нюанс. Во-первых, если мы пытаемся использовать TSDB, который используется Prometheus сейчас, мы этого сделать не сможем. Там есть специальный lock-файл, который предотвращает это. Если мы напишем код, который будет игнорировать это и будем пытаться читать или писать данные, мы гарантированно их повредим. При этом даже чтением. Что можно сделать? Можем читать данные через API и создавать TSDB рядом. Дальше Prometheus остановить и его TSDB подменить. Но при этом мы можем просадить производительность, если будем читать все данные через API. Я об этом немного позже скажу.
Второй вариант. Можно скопировать (сделать hot backup) этих файлов, то есть скопировать как есть. Да, они будут поврежденные. Когда вы откроете, у вас будет warning о том, что данные повреждены. Их нужно починить. Вы можете потерять новые данные. Но нам это не важно. Мы хотим downsampling старых данных. Downsampling можно сделать используя PromQL. Но есть нюанс. Его оторвать от Prometheus гораздо сложнее, чем TSDB. Если вы немного знакомы с Go и c управлением зависимостями, то вендоринг (vendor) PromQL это большая боль. Я бы вам не советовал. По возможности избегайте этого.
Переходим к Remote Storage. Кто-нибудь работал с Remote Storage в Prometheus? Несколько рук. Remote Storage — это API, которое давно уже существует. Сейчас в версии 2.2 Remote Storage — помечен как экспериментальный. Более того известно, что API Remote Storage точно поменяется.
Remote Storage позволяет вам работать только с сырыми данными. Там нет никакого PromQL ни на входе, ни на выходе. Когда вы читаете, вы не можете использовать всю мощь PromQL. Он по сути выкачивает все данные из Remote Storage, которые соответствуют условию. Дальше PromQL работает уже с ними. Это имеет довольно большой overhead. Вам нужно много данных прокачивать по сети. Поэтому в Prometheus 2.3, который пока не вышел, но уже это вмержили, будут read hint. Мы чуть позже об этом поговорим.
API для metadata пока что отстутствует. Вы не можете сделать API, которое возвращает все time series из Remote Storage. Если вы сделаете запрос в API у Prometheus, то он в Remote Storage не пойдет. Он вернет вам time series, которые есть в его локальной базе данных. Если у вас локальная база данных отключена, он вернет вам 0. Что может быть немного неожиданно. Сейчас этот API использует ProtoBuf и точно его поменяют на gRPC в будущем. Cейчас это пока что не сделали, потому что gRPC требует HTTP2. А у них на практике с ним были проблемы.
API для записи выглядит вот так. В запросе есть набор лейблов. Набор лейблов как раз уникально индентифицирует time series. __name__
— это на самом деле просто лейбл со специальным именем. А сэмплы — это набор времени и значения — int64 и float64. При записи порядок неважен. Предполагается, что база данных, которая это пишет в себя, сама все сделает правильно. Prometheus может сделать некоторую оптимизацию и не сортировать это лишний раз. Соответственно запрос на запись — это просто несколько time series.
У конфигурации на запись довольно гибкая настройка. Там много параметров для конфигурации параллелизма записи. То что Prometheus называет шардами — это по сути конкурентные запросы. Можно ограничить максимальное количество сэмплов в одном запросе, максимум параллельных запросов, timeout, как повторять, какой backoff. Для многих баз данных 100 сэмплов за раз — это может быть очень мало. Если вы используете ClickHouse, как это делаем мы, то конечно значение надо сильно увеличивать. Иначе это будет очень неэфективно.
Remote read API выглядит вот так. Это просто диапазон по времени от начала до конца и набор match.
Match — это по сути набор пар name и value — обычный лейбл и тип условия. В сравнении есть равенства, неравенства или регулярное выражение. Это обычный time series selector, который вы видите в PromQL. Никаких функций здесь нет.
Ответ — это несколько time series, которые соответствуют этому запросу. Здесь сэмплы должны быть отсортированы по времени. опять же это помогает Prometheus немного сэкономить cpu — не нужно сортировать. Но предполагается, что ваша база данных должна это делать. В большинстве случаев так и будет, потому что, скорее всего, там будет индекс по времени.
В Prometheus 2.3 появились read hint. Что это такое? Это возможность подсказать Prometheus, какая внутренняя функция, которая работает с time series, которая запрашивается, будет применена. Это может быть либо функция, либо оператор агрегации. Это может быть rate. То есть это назыается func, но на самом деле это может быть sum, который с точки зрения PromQL на самом деле совсем не функция. Это оператор. И шаг. На предыдущем примере там был rate 1 минута. Здесь rate — это будет функция и одна минута в миллисекундах как шаг. Этот hint может игнорироваться remote базой данных. При этом в ответе нет никакого признака, игнорировался или нет.
Какая конфигурация у read?
Во-первых, есть такая конфигурация required_matchers. Это позволяет вам отсылать запрос на Remote Storage, которые соответствуют выражению. Чтобы читать агрегированные данные из Remote Storage, необходимо использовать запрос, в составе которого присутстует двоеточие.
Есть опция, которая позволяет вам читать или не читать недавние данные из Remote Storage, которые есть в TSDB. Обычно в стандартной конфигурации есть небольшая локальная TSDB, которая пишется на локальный диск. Она там хранит несколько часов или несколько дней. Данные, которые используете сейчас, которые используется для оповещения, которые используются для построения dashboard, читаются только из локального TSDB. Он быстрый, но не позволяет нам хранить очень много данных.
Старые исторические данные будут читаться из Remote Storage. Это дает понять, как Local Storage и Remote Storage между собой связываются. Отсутствует какая-либо дедупликация.
По сути что происходит. Данные берутся из локального Storage, данные берутся из Remote Storage, если read_recent включен. Они просто сливаются вместе. Казалось бы — это не проблема. Если предполагается, что мы недавние данные никак не downsample'ли, это точно те же данные, они полностью совпадают с локальными данными, у нас будет в два раза больше семплов, на никакие функции не должны влиять. На самом деле нет. Есть функция irate() и парная ей для gauge, которая возвращает нам разницу между двумя последними значениями. Она заглядывает на указанный диапазон времени назад, но при этом использует только два последних значения. Если у нас два последних значения имеют одинаковое время, то разница будет ноль. Это баг и практически невозможно найти его. Это починили буквально четыре дня назад. Bот ticket кому интересно.
Интересно, что remote read реализуется самим Prometheus начиная с версии 1.8. Именно тот способ, который позволяет вычитывать данные старого Prometheus, когда вы делаете миграцию на версию 2.x. Официальный способ советует подключать его как remote read. Данные будут вычитываться по мере необходимости.
Remote read можно использовать для того, чтобы делать query роутинг без прокси. На одном из предыдущих слайдов я показывал, что в зависимости от времени, мы можем делать роутинг на один Prometheus или другой. Точно так же можем этого избежать. Просто подключаем тот Prometheus, который стоит ниже, как remote read — и данные будут читаться оттуда. Но есть поправка на то, что конечно много данных будет перекачиваться. Особенно если вы не используете query hint.
Почему ClickHouse?
Для нашего исследовательского решения мы выбрали ClickHouse, потому что мы на него уже давно смотрели. У нас есть люди, которые постоянно занимаются перфомансом баз данных, постоянно проверяют новые базы данных. Наша компания занимается opensource базами данных.
Нам очень нравится его сырая производительность. Его мощность в пересчете на CPU, время и так далее, очень хорошая. Большинство подобных систем говорят про бесконечную маштабируемость, но мало говорят про эффективность для одного сервера. Многие наши клиенты хранят метрики на паре серверов.
Встроенная репликация, шардирование.
GraphiteMergeTree — это специальный движок для хранения данных графита. Нас он вначале очень сильно заинтересовал.
Движок предназначен для rollup (прореживания и агрегирования/усреднения) данных Graphite.
Graphite хранит в ClickHouse полные данные, а получать их может, и дальше там написано, что с прореживанием используется GraphiteMergeTree, без прореживания используеться MergeTree. Ощущение такое, что данные хранятся всегда полные, они не переписываются, это просто оптимизация чтения. Но в целом это неплохо. Когда мы делаем чтение, мы не выкачиваем данные, они автоматически агрегируются, мы получаем мало данных — это хорошо. Минус для нас, что данные хранятся все.
Я готовился в начале месяца к докладу. Кто-то заходит телеграмм чат и спрашивает — "GraphiteMergeTree данные downsample'нные"? Я уже пишу "нет". В документации написано, что нет. Но другой человек из чата отвечает "да, нужно вызвать optimize". Запускаю, проверяю — да правда. В документации по сути баг. Потом прочитал исходный код, проверил, оказывется там есть optimize, optimize final. Optimize final как раз изначально создавался именно для GraphiteMergeTree. На самом деле downsampling он делает. Но его надо вызвать руками.
У GraphiteMergeTree другая модель данных. Нет у него лейблов. Эффективно записать это все в названии метрик не очень хорошо получается.
Название метрик хранятся в одной таблице. Название метрик имеет разную длину. Это приводит к тому что, если мы делаем поиск index по названию метрики, из-за того что длина разная, этот индекс не будет так эффективен как, если бы этот индекс имел значение фиксированной длины. Потому что вам нужно делать поиск по файлу. Нельзя точно указать, куда нужно приземлиться для того, чтобы бинарный поиск делать.
Поэтому сделали свою собственную схему. На слайде показывается, как у нас хранятся time series в базе данных. Date, который нужен ClickHouse — это fingerprint. Если вы смотрели исходники Prometheus или TSDB, то вы знаете, что fingerprint по сути короткая быстрая checksum полного названия time series. Fingerprint — комбинация всех лейблов, ключей и значений. Имя — это обычный лейбл. Мы использует тот же самый алгоритм для совместимости. Если что-то дебажить, то это может быть удобно. Fingerprint совпадает и его можно проверить в TSDB и в нашем storage что они одинаковые. Лейблы хранятся в специальном JSON, который позволяет ClickHouse работать с ним его стандартными функциями. Это компактный JSON без пробелов, с немного упрощенным неймингом. Эта таблица во время работы не используется. Она всегда хранится в памяти нашего собственно решения, которой называется PromHouse. Она используется только, когда мы запускаем сервер для того, чтобы узнать, какие time series есть. Она вычитывается. По мере того, как новые time series приходят, мы их туда записываем. Все несколько инстансов PromHouse могут читать одну и ту же таблицу. ReplacingMergeTree говорит нам о том, что эти time series — есть несколько разных инстансов — пишут одну и ту же time series. Они смержатся — и никакой проблемы здесь не будет.
Cэмплы мы храним в отдельной таблице очень эффективно. При значении фиксированной длины этот fingerprint тот же самый, время и значения. У нас получается 24 байта на sample. Оно имеет строго фиксирую длину. Каждая колонка хранится отдельно. Поиск по fingerprint эффективен, потому что мы знаем, что размер фиксированный. Нет такой проблемы как с GraphitmergeTree, когда это строка. Мы используем кастомный(custom) partitioning. Первичный индекс fingerprint и по времени.
24 байта — это в упрощенном варианте. На самом деле он хорошо сжимается. По факту использует меньше места. В наших последних тестах степень компрессии примерно 1 к 42.
Как можем сделать ручной downsampling, если у нас GraphiteMergeTree есть, но не такой, как хотелось бы. По сути мы можем сделать это руками. Как раньше делали шардирование, партицирование, когда ничего встроенного не было. Делаем руками новую таблицу. Когда к нам приходит сэмпл по времени, определяем, в какую таблицу пишем.
Выбираем по времени из запроса, из какой таблицы читать. Если чтение происходит на границе, читаем несколько таблиц. Дальше мержим эти данные. Можно было бы использовать view для этого. Например, сделать view для несколько таблиц, который позволяет это читать одним запросом. Но в ClickHouse есть баг: предикат из view не подставляется в запросы. Поэтому если вы делаете запрос в view, то он идет на все таблицы. View мы не можем использовать.
Как мы делаем downsampling? Мы создаем временную таблицу. Копируем из нее данные insert into select, используя правильные функции.
Делаем rename, который атомарный под глобальным локом. Мы переименовываем существующую таблицу в старую. Новую в существующую. Дропаем старую таблицу. У нас данные за 148 день уже downsampling. Какая здесь проблема? Insert into красиво выглядит. На самом деле нам нужно применить правильные функции, правильную агрегацию сделать. На практике это не получается сделать одним большим запросом. Даже несколькими большими запросами не получается сделать. Это приходится делать из кода. Код посылает большое количество небольших запросов. Мы по-максимуму старались это сделать большими запросами, но это не очень эффективно получается. Downsampling данных одного дня пока что занимает меньше дня. В зависимости от количества данных может занимать долгое время.
В ClickHouse будут update/delete. Delete уже первую версию вмержили. Если будут работать update/delete, то наша схема downsampling данных может упроститься.
Во-вторых, в ClickHouse есть задача сделать кастомное сжатие (дельта, дельта в дельта). Это то, что делает TSDB. Это хорошо подходит для time series данных. Это особенно полезно, если мы будем иметь возможность выбирать тип компрессии в зависимости от типов данных. Например, counter, который только растет — для этого подходит дельта-дельта компрессия. Gauge, который колеблется вокруг величины, поэтому дельта хорошо работает.
Есть другие storage, которые работают. Есть InfluxDB, который работает из коробки. Его принято ругать за скорость, но то, что работает из коробки и вам ничего не нужно делать — это хорошо.
Есть OpenTSDB и Graphite, который только на запись. Стандартный адаптер из Prometheus не особо работает.
Есть CrateDB. Есть TimescaleDB, который fork PostgreSQL для time series баз данных. Говорят работает неплохо, но сами мы не пробовали.
Есть Cortex, который также был известен как проект франкенштейн. Это очень хорошо его описывает. Это ребята пытаются сделать решение на основе федерации Prometheus. Они хранят данные в S3.
Есть Thanos.
- У него очень интересная архитектура. Есть Prometheus, который использует локальный TSDB. Между ними создается кластер. Рядом с каждым Prometheus ставится специальный side-car, который по remote read и remote write API принимает запросы. Эти запросы он перенаправляет в Prometheus. Prometheus может использовать его remote read и remote write API. Все side-car соединенные между собой и между кастомным API мастера через gRPC, доступна репликация, есть перешардирование.
- Сложная архитектура.
- Оно довольно сыровато. Пару месяцев назад оно разваливалась с полпинка, когда запускалось.
Используя Pull-модель, много данных не записать. Нужно ждать целый год для заполения годичных данных. Мы же пытаемся как-то их туда записать.
В Prometheus отсутствует remote write, поэтому в локальный TSDB записать много данных не получится.
Вторая проблема. Если мы генерируем данные для нагрузочного тестирования, то они часто хорошо жмутся. Например, если мы берем существующие данные и генерируем 100 инстансов, и это одинаковые данные, то там коэффициент сжатия будет такой прекрасный, что в реальности не случаются.
Мы написали fake экспортер, который выглядит как обычный экспортер, который Prometheus может скрепить:
- Когда приходит скрейп, он идет на какой-то апстримовский экспортер. Берет с него данные.
- Генерирует много инстансов. Допустим скрейпит 1, а на выходе получаем 100.
- Немного меняет данные: плюс минус 10 % для counter и gauge.
- Не меняет простые значения 0 или 1. Потому что если есть метрика UP, которая ответчает показывает запущен ли сервис: да — 1 или нет — 0. И не очень понятно что означает 098 UP.
- Не меняем целые числа на вещественные и наоборот.
- Просто отдает данные в обычном формате expfmt.
Инструмент promload, который грузит данные. Чтение данных:
- Может читать из файлов в своем формате
- Может из remote read
- Может из какого-то экспортера читать
Пишет в разные форматы. В том числе в /dev/null, если мы хотим протестировать именно как чтение быстро работает.
Сейчас это инструмент нагрузочного тестирования не только для PromHouse, но и для любого решения, который использует remote read или Prometheus.
Мы хотим добавить кеширование чтения, потому что в наших тестах часто узким местом был именно fake экспортер, который долго генерировал данные. Мы могли бы их кэшировать. Пусть они будут нереально хорошие. Зато мы не будем тормозить. Нам не нужно было днями ждать нагрузочного тестирования.
Какая-то фильтрация на лету, какая-то модификация на лету.
Нативная поддержка TSDB. Для того чтобы именно работать с базой данных на диске, а не через API.
Фокус на аккуратность для миграции. Я один раз pmmdemo.percona.com положил: подключился, получил все метрики. Если вы делаете это нативным способом, то Prometheus открывает TSDB, поднимает все time series с диска, поднимает индексы, дальше лезет в chunk файлы, понимает что они реально есть. В этот момент все может просто лечь.
Наивный подход — это взять все time series и читать начиная со старых данных до новых. В этот момент он ляжет. Вам нужно сделать наоборот. Нужно сначала получить список time series несколькими запросами с регулярными выражениями. Например, time series, которые начинаются на A. Потом дай мне time series, которые начинаются на B. Дальше грузить их именно по метрикам, а не по времени. Это нелогично, но так это работает. Это нюанс, если вы будете делать что-то такое подобно. Если увидите, что там OOM Killer случился, то вы будете знать, что это из-за вас.
Результаты нагрузочного тестирования, графиков не будет. Нагрузочное тестирование занимает много времени и, к сожалению, из-за ошибки конфигурации все закрешилось. Поэтому результаты не получились.
В блоге Percona мы напишем, когда сделаем нагрузочное тестирование.
Могу сказать результаты без графиков. Запись была линейна. Чтение скакало и было не очень быстро. Нам не очень важно чтение текущих данных. Их можно через read hints ускорить. Можно включить read_recent для улучшения чтения. А для старых данных это нормально работает.
Люди хотят долговременные хранилища. Такой спрос есть. Мы делали доклад про PromHouse на PromCon. Там это была очень горячая тема. Thanos активно развивается.
Это уже возможно сейчас. Есть для этого решения. Есть API. Есть какие-то интеграции. Но все это нужно дорабатывать напильником. Нет production ready решений.
Ссылки где посмотреть. Первая ссылка — репозиторий PromHouse. Вторая ссылка — это куда он скорее всего переедет. Сейчас в одном репозиторий несколько разных вещей? не очень тесно связанных между собой. Поэтому нужно будет их переносить.
В нашем блоге будет информация про performance и какие-то новости.
Вопросы:
Вопрос: Не проверяли ли вы слухи про InfluxDB?
Ответ: Он был не очень хороший. Он сильно лучше стал. Все эти байки про то что InfluxDB медленный, разваливается — они про старую версию. Текущая версия работает стабильно. Я бы не сказал? что она быстро работает. Но она работает стабильно. Плюсы InfluxDB на мой взгляд:
- Во-первых, не нужно делать что-то рядом, потому что InfluxDB работает из коробки.
- Во-вторых, в ClickHouse, как в других решениях на основе базы данных, но не TSDB, вы можете использовать язык запросов, который вам более знаком. Язык запросов InfluxDB похож на SQL. На нем можно делать аналитику, которую на PromQL делать сложно. Если вы использете TimeScaleDB — там настоящий SQL.
Вопрос: движок GraphiteMergeTree только на запись получается? Если мы хотим графики показывать, Grafana необходимо настраивать на Graphite, чтобы показывать долговременное хранилище?
Ответ: Да. Интеграция, которая есть в самом Prometheus, работает только на запись. Он только пишет данные. Поэтому из Grafana вы ходите в Graphite.
Вопрос: И он теряет лейблы когда пишет?
Ответ: Там есть конфигурация, которая говорит, что с ними делать, как их вставлять, куда вставлять.
Информация из зала: Avito рассказывал, что они пишут свое решение для записей из Prometheus в Graphite.
Вопрос: там был вывод, что с записью все хорошо на сервер долговременного хранения.
Идет поток миллион метрик (5-минутки или 15-минутки). Хватит ли raid 6 sata дисков для хранения данных за год?
Ответ: В PMM у нас основной интервал скрейпинга — это секунда. При этом downsampling мы делаем начиная c 14 дня до 1 минуты. Второй шаг, мы пока ещё ни разу не делали. Мы не агрегируем эти данные по одной минуте еще меньше. Это занимало сотни гигабайт. Не было колебания скорости записи.
Вопрос: Данных по IOPS там нет?
Ответ: Нижняя часть нагрузочного тестирования псевдонаучная.
Вопрос: Поэтому и спрашиваю вашего опыта
Ответ: Наш опыт пока ничего плохого и не заметил. Но конечно, нам бы хотелось довести нормальное тестирование до конца. Описать метологию, результаты, сделать графики.
Вопрос: Если мы пишем в InfluxDB, то мы можем запрашивать долговременных данные из InfluxDB?
Ответ: Это зависит от read_recent. Это точно так же, как и с любым другим remote storage. Ничего уникального для InfluxDB нет. В зависимости от настройки. Если read_recent включен, то будем читать.
Вопрос: Например, для оперативных данных используем Prometheus. Долговременные данные пишем в InfluxDB. Grafana настраиваем на Prometheus. Prometheus может по PromQL вытаскивать оперативные данные и то, что записано в InfluxDB?
Ответ: Да.
Вопрос: Prometheus уже будет ходить в InfluxDB и вытаскивать и рисовать в Grafana?
Ответ: Да. При этом у Prometheus версии 2.2 не будет дедупликации и у вас будет проблема, о которой я говорил.
P.S. Большое спасибо за помощь в редактировании статьи: valyala gecube
Кому не сложно, добавьте им кармы.