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

Loki 3.0 GA: обновляем стек логов и меряем, что изменилось с новым движком индексации

Обновили несколько стендов до Loki 3.0 GA. Смотрим на потребление памяти при индексации и разбираем новый синтаксис LogQL для join-запросов по метаданным.

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

Grafana Loki 3.0 GA: новый движок индексации и улучшенная производительность запросов LogQL

Grafana Labs выкатила Loki 3.0 как стабильный релиз, и мы решили не тянуть - обновили несколько клиентских стендов, которые ведём в рамках managed-сопровождения. Накопили наблюдения за полторы недели - вот они.

Что нового в 3.0 и зачем мы вообще обновлялись

Ключевая штука в Loki 3.0 - переработанный движок TSDB-индексации, который теперь стал единственным поддерживаемым бэкендом индекса. Старый BoltDB-Shipper уходит в прошлое. Это не просто переименование: TSDB по архитектуре значительно лучше справляется с высоким cardinality меток, а потребление памяти при индексации на бумаге должно было снизиться. «На бумаге» - это мы проверяли.

Второй важный блок - structured metadata. В Loki 2.x можно было прикреплять метаданные только через метки потока (stream labels), что либо взрывало индекс, либо заставляло выбирать, что туда вообще класть. В 3.0 structured metadata живут отдельно от меток: они хранятся на уровне записи, не влияют на cardinality индекса, и при этом доступны в LogQL-запросах.

Третье - обновлённый синтаксис LogQL, в частности оператор | join для сопоставления логов разных потоков по общим полям метаданных. Это было больное место: раньше для такого приходилось либо городить Prometheus-метрики, либо мириться с тем, что в Grafana join между потоками не сделаешь без дополнительной логики.

Обновление: как это выглядело

Стенд - Kubernetes-кластер, Loki в режиме distributed, объём примерно 15-20 ГБ логов в сутки. До обновления жили на Loki 2.9.3.

Перед апгрейдом проверили несколько вещей: формат конфигурации в 3.0 поменялся в нескольких местах. boltdb_shipper из конфига надо убрать, явно прописать tsdb как store. Compactor тоже слегка поменял параметры - retention_enabled теперь обязателен для явного указания, иначе 3.0 будет ругаться при старте. Нашли это в changelog, а не методом тыка, что приятно.

Helm-чарт grafana/loki для версии 3.0 немного изменил структуру values - особенно в секции loki.storage. Пришлось пройтись по diff и подправить несколько ключей. Сам апгрейд занял около 40 минут вместе с проверками.

Ingester после рестарта поднялся нормально. TSDB-индекс начал строиться заново - на это ушло минут 20, в течение которых запросы по историческим данным работали медленнее. Клиента предупредили заранее, окно выбрали ночное.

Память: что намерили

До обновления ingester-поды на этом стенде потребляли в среднем около 1,2-1,5 ГБ каждый. После перехода на TSDB и нескольких дней прогрева картина изменилась - держится в районе 0,9-1,1 ГБ. Снижение заметное, не колоссальное, но стабильное. На другом стенде с более агрессивным cardinality меток (там сервис динамически генерирует метки с идентификаторами сессий, что исторически создавало проблемы) разница оказалась чуть более выраженной.

Честно: мы не проводили строгое A/B-сравнение с одинаковой нагрузкой. Это production, не лаборатория. Но тенденция на нескольких стендах совпадает, что уже говорит что-то.

Скорость ingestion субъективно стала ровнее - меньше микро-задержек, которые иногда появлялись в 2.9 при пиковой нагрузке. Объективно намерить это сложнее без специально подготовленного бенчмарка.

LogQL join: разбираем синтаксис

Новый оператор | join позволяет сопоставлять записи из разных потоков по общему полю в structured metadata. Пример из реальной задачи: у нас есть поток логов приложения и поток логов nginx, оба содержат request_id. Раньше сопоставить их в одном запросе было нетривиально.

В Loki 3.0 это выглядит примерно так:

{app="myservice"} | json | keep request_id, level, msg
  | join on (request_id)
    {app="nginx"} | json | keep request_id, status, upstream_response_time

На выходе получаешь записи с полями из обоих потоков, сопоставленными по request_id. Это работает, но с оговоркой: временное окно для join по умолчанию небольшое, и если между записями разных потоков есть значительный лаг, записи не сматчатся. Параметр within позволяет это окно расширить.

На практике join оказался полезным именно для разбора инцидентов - быстро посмотреть, что происходило в nginx в момент, когда приложение начало писать ошибки, по одному полю запроса. В мониторинге в реальном времени мы пока не используем - LogQL-запросы с join ощутимо тяжелее обычных.

Structured metadata: стоит ли перестраивать pipeline

Тут мы пока в режиме осторожного экспериментирования. Structured metadata требует изменений в агенте - нужно либо Promtail с поддержкой structured_metadata секции, либо Alloy (который Grafana активно продвигает как замену). Перестраивать агентский слой под конкретную фичу на всех клиентах сразу - избыточно.

На одном пилотном стенде попробовали: передаём через Alloy в structured metadata поля trace_id и user_id, которые раньше были либо в метках (и взрывали cardinality), либо только в теле лога (и были доступны только через парсинг). Теперь они доступны в запросах без парсинга и не влияют на размер индекса. Это удобнее, хотя и требует аккуратности при настройке.

Итог

Loki 3.0 - это не революция, но нормальный шаг вперёд. TSDB как единственный индексный бэкенд - это скорее хорошо: меньше вариантов конфигурации, меньше шансов выбрать неправильный. Снижение потребления памяти мы видим, join в LogQL работает и закрывает реальную задачу.

Кому стоит обновляться сейчас: тем, у кого проблемы с cardinality меток или большим потреблением памяти ingestor'ами. Остальным - в ближайшее maintenance-окно, не срочно.

Конфигурационные изменения между 2.9 и 3.0 есть, changelog надо читать - но они предсказуемые и хорошо задокументированы.

Контакт

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

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