ADG Оставить заявку
Блог DevOps 5 мин чтения

Prometheus 2.18 в production: снижение памяти и перестройка remote_write под Thanos

Обновляем Prometheus до 2.18 на клиентском кластере: потребление памяти упало на треть, перестраиваем remote_write для долгосрочного хранения в Thanos.

Контекст момента

Prometheus 2.18 (май 2020): улучшения производительности TSDB, снижение потребления памяти head chunk-ами, продвижение remote_write для интеграции с Thanos и Cortex

В рамках managed-сопровождения мы несколько недель назад обновили Prometheus на одном из клиентских кластеров с 2.15 до 2.18 и заодно разобрались с накопившимися вопросами по remote_write. Выход получился насыщенный: потребление памяти заметно просело без каких-либо изменений в конфигурации, а работа с Thanos перестала быть "ну как-то работает".

Почему вообще трогали

Клиент - средний e-commerce, Kubernetes-кластер с полутора сотнями подов, мониторинг через Prometheus. Проблема была конкретная: Prometheus периодически упирался в лимит памяти и получал OOMKill. Не каждый день, но раз в неделю - стабильно. Мы подняли лимит до неприличного значения, поставили алерт и поместили в беклог. Выход 2.18 с заявленными улучшениями TSDB дал повод разобраться наконец.

Параллельно давно висела задача настроить долгосрочное хранение метрик - клиент хотел смотреть тренды за несколько месяцев, а локальное хранилище Prometheus держит примерно две недели при текущих настройках retention. Thanos стоял ещё с прошлого года в минимальной конфигурации: sidecar, query, store - но remote_write в него не был толком настроен, всё работало через sidecar-режим без дополнительной надёжности.

Что изменилось в 2.18 по части памяти

Главное изменение в 2.18 касается head chunk-ов в TSDB. В прошлых версиях head chunks (то есть данные за последние два часа, которые ещё не сброшены на диск) хранились в памяти в несжатом виде. В 2.18 они перешли на сжатие - тот же формат, что и на диске.

На практике это значит, что оперативную память теперь занимают сжатые данные вместо сырых float64. Для типичного production-кластера с сотнями тысяч временных рядов разница ощутимая. В нашем случае потребление памяти Prometheus упало примерно на 30% сразу после обновления - при тех же scrape targets, том же интервале сборки, том же наборе правил.

Проверили через process_resident_memory_bytes в самом же Prometheus: до обновления стабильные 4.2-4.5 Гб, после - 2.9-3.1 Гб. OOMKill с тех пор не было.

Есть и обратная сторона: небольшое увеличение нагрузки на CPU при ingestion - распаковывать/упаковывать head chunks при каждой записи. На нашем кластере это в районе шума, заметно не стало. Но на очень высокочастотных scrape-ах с тысячами targets это стоит проверить.

Обновление: как делали

Обновление само по себе прошло без сюрпризов. Prometheus совместим по формату TSDB между версиями 2.x, данные на диске после обновления никуда не деваются и читаются без конвертации.

Несколько наблюдений по процессу:

Резервная копия TSDB перед обновлением. Не потому что ожидали поломки, а потому что production. Сделали снапшот через API Prometheus (/api/v1/admin/tsdb/snapshot), убедились что файлы скопировались.

Перезапуск с новым бинарником. В нашем случае Prometheus развёрнут как Deployment в Kubernetes с образом prom/prometheus:v2.18.1. Обновили тег, откатили деплой, подождали. Несколько минут на старт - TSDB инициализируется, head block собирается из WAL.

Первые 15 минут - аномальное потребление. При старте Prometheus загружает WAL, и память поначалу растёт до значений, близких к старым. Это нормально - он восстанавливает состояние head chunk-ов из журнала. После полного старта цифры падают до нового нормального уровня.

Перестройка remote_write под Thanos

Вот тут пришлось подумать чуть больше.

Sidecar-режим Thanos, который у нас стоял, работает следующим образом: sidecar читает блоки TSDB напрямую из файловой системы Prometheus и загружает их в object storage. Это надёжно для исторических данных, но есть задержка: блок появляется в object storage через два часа, когда Prometheus его закрывает. Для долгосрочного хранения это приемлемо. Для near-realtime запросов через Thanos Query - нет.

Thanos Receiver позволяет получать данные через remote_write напрямую, без задержки на компакцию. Мы развернули Receiver, настроили Prometheus на отправку туда.

Базовая конфигурация remote_write в prometheus.yml:

remote_write:
  - url: http://thanos-receive:19291/api/v1/receive
    queue_config:
      max_samples_per_send: 2000
      max_shards: 10
      capacity: 5000

Несколько параметров queue_config, на которые стоило обратить внимание:

max_shards - количество параллельных горутин, отправляющих данные. По умолчанию 5, мы подняли до 10 - у клиента высокий cardinality и нам нужно было избежать накопления очереди при пиковой нагрузке.

capacity - буфер в памяти перед отправкой. Если Thanos Receiver временно недоступен, Prometheus продолжает собирать метрики и буферизует их. При 5000 сэмплах и нашем scrape interval 15 секунд это примерно минута автономной работы.

min_backoff / max_backoff - таймауты при ошибках отправки. Оставили дефолт (100ms / 5s), пока устраивает.

Метрики самого remote_write смотрим через prometheus_remote_storage_* - там видно queue length, pending samples, failed samples. Первую неделю держали дашборд открытым, убеждались что очереди не копятся.

Sidecar оставили

Убирать sidecar мы не стали. Теперь работают оба механизма: sidecar загружает закрытые блоки в object storage для архива, receiver получает свежие данные с минимальной задержкой. Thanos Query смотрит и в Store Gateway (для архивных данных), и в Receiver (для свежих).

Дублирования данных формально нет: sidecar и receiver пишут в разные tenant-ы или разные bucket-ы - зависит от конфигурации. У нас разные bucket-ы: metrics-archive для sidecar, metrics-realtime для receiver. Store Gateway указывает на оба.

Где сейчас

Кластер работает на Prometheus 2.18 третью неделю. OOMKill нет, потребление памяти стабильно на треть ниже. Remote_write в Thanos Receiver работает, задержка появления данных в Thanos Query - секунды, не часы.

Открытый вопрос: компакция в Thanos Receiver. Receiver накапливает данные в своём собственном TSDB и периодически сбрасывает их в object storage - но алгоритм компакции там немного отличается от основного Thanos Compactor. Пока данных за несколько недель недостаточно чтобы убедиться что всё ок на длинном горизонте, следим.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.