Grafana Loki 2.8: structured metadata в production и миграция схемы хранения
Апгрейд Loki до 2.8 в production-кластере: structured metadata снижает нагрузку на индекс, миграция схемы и что изменилось в LogQL на практике.
Grafana Loki 2.8 - structured metadata, улучшенный LogQL и снижение нагрузки на хранилище за счёт выноса метаданных из индекса
Loki 2.8 вышел на прошлой неделе, и мы довольно быстро взяли его в production - не потому что торопились, а потому что одна фича в release notes прямо закрывала задачу, которую мы держали в бэклоге уже месяца три: structured metadata. Рассказываем как прошло и что реально изменилось.
Проблема, которую решали
У нас несколько кластеров Kubernetes с централизованным сбором логов через Promtail -> Loki -> Grafana. Объём логов приличный - сотни гигабайт в день на крупных клиентах, хранение в S3-совместимом объектном хранилище. Индекс в Loki традиционно хранится отдельно (у нас BoltDB-shipper, постепенно мигрируем на TSDB), и именно индекс - главная статья расходов по IOPS и CPU.
Проблема классическая: хочется фильтровать логи по полям вроде trace_id, request_id, user_id - но если добавить эти поля в лейблы Loki, кардинальность индекса взлетает до небес. Мы это проходили: один из клиентов добавил user_id как лейбл, и через неделю Loki начал тормозить на ingestion, а размер индекса вырос в 4 раза. Пришлось откатывать.
Workaround был такой: парсить user_id прямо в LogQL через | json или | logfmt, но это работает только если формат лога позволяет, и это CPU на query-time, а не на write-time. При больших объёмах и сложных запросах это ощутимо.
Structured metadata: что это
В 2.8 появился третий уровень хранения данных о логе - помимо лейблов (индексируются) и тела лога (не индексируется). Structured metadata - это пары ключ-значение, которые прикрепляются к конкретной записи лога, хранятся рядом с чанком, но не попадают в индекс. Их можно фильтровать в LogQL через лейбл-фильтры напрямую, они видны в результатах запроса.
Идея простая: pod, namespace, job - в лейблы (фильтрация по ним в 99% запросов). trace_id, request_id, span_id - в structured metadata (нужны для drill-down, но не для первичного поиска). Тело лога - остаётся телом лога.
На уровне конфига Promtail это выглядит так:
pipeline_stages:
- json:
expressions:
trace_id: trace_id
request_id: request_id
- structured_metadata:
trace_id:
request_id:
Поля убираются из лейблов, пишутся в metadata. LogQL для поиска по ним:
{namespace="production", app="api"} | trace_id="abc123"
Синтаксис тот же что для лейбл-фильтров, что удобно - не надо переучиваться.
Миграция схемы: schema_config и период перекрытия
Самое неудобное в апгрейде Loki - не сам бинарник, а схема хранения. Loki хранит периоды (periods) в конфиге, и при смене схемы нужно корректно закрыть старый период и открыть новый. Структурированные метаданные требуют минимум схемы v13 (в 2.8 это новый тип, схемы v11/v12 metadata не поддерживают).
Наш конфиг до апгрейда:
schema_config:
configs:
- from: 2022-01-01
store: boltdb-shipper
object_store: s3
schema: v11
index:
prefix: index_
period: 24h
После - добавляем новый период с v13:
schema_config:
configs:
- from: 2022-01-01
store: boltdb-shipper
object_store: s3
schema: v11
index:
prefix: index_
period: 24h
- from: 2023-04-03
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_tsdb_
period: 24h
Важный момент: дата начала нового периода не может быть в прошлом. Если поставить дату вчера - Loki откажется стартовать с ошибкой про перекрытие периодов. Мы поставили текущую дату, и новая схема начала применяться с сегодняшнего дня. Старые данные остаются читаемыми через старую схему - Loki умеет работать с несколькими периодами одновременно.
Заодно переехали с BoltDB-shipper на TSDB - это отдельная история, но в 2.8 TSDB стал дефолтным и рекомендованным. TSDB заметно быстрее на запросах с агрегацией по времени.
Что получили на практике
Прошло несколько дней после апгрейда, и можно сделать первые наблюдения - без громких цифр, просто по ощущениям и метрикам Grafana на самом Loki.
Размер индекса растёт медленнее. На одном из кластеров мы перевели в structured metadata около 8 полей, которые раньше были либо в лейблах (4 поля), либо дублировались в теле лога и парсились на query-time (4 поля). Индекс за первые трое суток на новой схеме вырос на треть меньше чем за аналогичный период до апгрейда. Выборка маленькая, но направление правильное.
Запросы по trace_id и request_id стали работать предсказуемее. Раньше фильтрация через | json | trace_id="..." иногда была медленной из-за необходимости читать все чанки за период и парсить JSON налету. Теперь metadata-фильтр применяется раньше, чтение чанков сокращается. Субъективно - заметно на периодах больше суток.
Новый LogQL: | keep и | drop. Это не связано с metadata напрямую, но появилось в 2.8. Операторы позволяют оставить или выбросить поля из результата запроса. Полезно когда парсишь жирный JSON-лог и хочешь в результатах видеть только 3-4 поля - не надо городить сложный label_format.
Что не понравилось
Structured metadata не работает с push-API если клиент не поддерживает новый формат. Promtail 2.8 поддерживает, Vector - добавили поддержку недавно, Fluentbit - пока нужно проверять. У нас один кластер с Fluentbit как shipper - там metadata пока не используем, ждём проверки совместимости.
Документация по metadata-фильтрации в момент релиза была откровенно куцей - пара примеров и всё. Пришлось читать тесты в исходниках чтобы понять граничные случаи с regexp-фильтрами.
В целом апгрейд прошёл рабочим образом - structured metadata закрывает реальную проблему с кардинальностью, которая у нас висела давно. Для клиентов на нашей managed-инфраструктуре будем переводить постепенно: сначала там где индекс растёт быстрее всего и есть высококардинальные поля. Посмотрим что покажут данные за полный месяц на новой схеме.