Прямой ответ
Временной ряд — последовательность пар «метка времени — значение», относящихся к одному набору меток (имя метрики плюс теги). Хранилища временных рядов оптимизированы под три свойства таких данных: запись только в конец, неизменяемость старых точек и чтение целыми диапазонами.
Модель данных:
- Серия идентифицируется именем метрики и полным набором пар «тег — значение». Добавление нового тега создаёт новую серию.
- Кардинальность — число уникальных серий. Это главный ограничитель: каждая серия требует собственной структуры в памяти и на диске.
- Точка — пара (timestamp, value), обычно 8 байт на каждую часть до сжатия.
Сжатие — то, что делает такие объёмы приемлемыми. Классический подход описан в статье о базе Gorilla:
- Метки времени: дельта-дельта. Точки поступают с примерно равным интервалом, поэтому хранят не саму метку, а разницу с предыдущей, а затем разницу разниц. Для стабильного интервала значение часто равно нулю и кодируется одним битом.
- Значения: XOR. Последовательные значения плавающей точки часто близки или равны; хранят результат XOR с предыдущим значением. При совпадении — один бит; при близости — только значащие биты.
- Блочное хранение. Точки серии пишутся блоками, что улучшает сжатие и ускоряет чтение диапазонов.
Результат: вместо 16 байт на точку типично получается 1–2 байта, то есть сжатие в 8–15 раз.
Второй ключевой механизм — downsampling (прореживание) и retention (срок хранения):
| Слой | Разрешение | Срок |
|---|---|---|
| Горячий | Исходное (10–15 с) | 7–30 дней |
| Тёплый | 1–5 минут | 3–12 месяцев |
| Холодный | 1 час | Годы |
Логика простая: для расследования инцидента нужны секунды за последние дни, а для тренда за год достаточно часовых агрегатов.
#VictoriaMetrics: прореживание задаётся флагом хранения
# -retentionPeriod=12m и -downsampling.period=30d:5m,180d:1h
-- Вариант на PostgreSQL: партиционирование по времени + агрегирующая таблица
CREATE TABLE metrics (
ts timestamptz NOT NULL, series_id bigint NOT NULL, value double precision
) PARTITION BY RANGE (ts);
CREATE TABLE metrics_1h AS
SELECT date_trunc('hour', ts) AS ts, series_id,
avg(value) AS avg_value, max(value) AS max_value
FROM metrics GROUP BY 1, 2;
Практика: проектирование и эксплуатация
1. Контролируйте кардинальность. Высокая кардинальность — основная причина деградации систем мониторинга. Метки, содержащие идентификатор запроса, e-mail пользователя или случайный идентификатор, порождают миллионы серий, каждая из которых стоит памяти. Практика: держите в метках только то, по чему планируете фильтровать и агрегировать; детали — в логи.
2. Разделяйте метрики и события. Метрика отвечает на вопрос «сколько в момент времени», лог — «что произошло». Попытка выразить события через метрики ведёт к росту кардинальности, а попытка считать процентили по логам — к дорогой аналитике. О распределении и процентилях — в статье про гистограммы метрик.
3. Планируйте слои хранения заранее. Перенос данных из горячего слоя в холодный после накопления объёма сложнее, чем настройка в начале. Решение о том, какие агрегаты нужны, принимается до того, как исходные данные удалены по сроку хранения.
4. Партиционируйте по времени. Это даёт дешёвое удаление устаревших данных: удаление партиции — операция с метаданными, а не массовое удаление строк. Подробнее — в статье про партиционирование PostgreSQL.
5. Учитывайте стоимость запросов с большим диапазоном. Запрос за год по исходному разрешению читает огромное число точек. Хорошая система предлагает автоматическое прореживание на стороне сервера (step в Prometheus API) — см. VictoriaMetrics.
6. Дедупликация при нескольких источниках. Если одни и те же метрики приходят с двух реплик, хранилище должно уметь отбрасывать дубли; иначе объём и стоимость запросов удваиваются — см. долгосрочное хранение в Mimir.
Рекомендации:
- Измеряйте объём до запуска. Оценка: число серий × частота снятия × срок хранения × байт на точку после сжатия. Ошибка в кардинальности на порядок даёт ошибку в объёме на порядок.
- Не храните метки с высокой энтропией. Каждое новое значение тега — новая серия навсегда, пока не истечёт срок хранения.
- Проверяйте, что агрегаты считают то же самое. Среднее от средних не равно среднему; для процентилей нужны гистограммы, а не усреднённые значения.
- Разделяйте hot и cold физически. Горячий слой — быстрый диск и память, холодный — объектное хранилище. Смешивание приводит к тому, что архивные запросы мешают оперативным.
- Тестируйте удаление по сроку. Данные, которые «должны удалиться через год», часто остаются из-за неверной настройки; проверка дешевле, чем выяснение при переполнении диска.
Практический сценарий: хранилище метрик исчерпало память за неделю
Контекст: кластер мониторинга, рассчитанный на полгода работы, начал перезапускаться из-за нехватки памяти через неделю после запуска.
- Диагностика. Число активных серий оказалось 4,1 млн вместо ожидавшихся 300 тысяч.
- Причина. В одну метрику добавили тег с идентификатором обработанного задания. Каждое задание создавало новую серию, которая оставалась в памяти до конца срока хранения.
- Дополнительно. Второй источник роста — тег с путём запроса, включавший параметры строки запроса: вместо нескольких сотен значений получилось более 200 тысяч.
- Действия. (а) Тег с идентификатором задания убрали, идентификаторы перенесли в журналы; (б) путь запроса заменили на шаблон маршрута без параметров; (в) добавили лимит на число серий на метрику и алерт при превышении порога; (г) настроили прореживание: исходное разрешение 15 дней, далее пятиминутные агрегаты.
- Результат. Число серий снизилось до 260 тысяч, потребление памяти — с 58 до 9 ГБ, срок хранения увеличили до года без роста затрат.
Вывод: объём хранилища временных рядов определяется не количеством серверов и не частотой снятия, а кардинальностью меток. Один неудачный тег стоит дороже, чем ошибка в выборе системы хранения.
FAQ
Подойдёт ли обычная реляционная база для метрик? Для небольших объёмов — да, особенно с партиционированием по времени. На больших объёмах специализированные хранилища выигрывают за счёт сжатия, ориентированного именно на пары «время — значение», и за счёт эффективной работы с высокой кардинальностью.
Что такое downsampling и чем он отличается от агрегации? Прореживание уменьшает разрешение старых данных, сохраняя их характер: обычно оставляют среднее, минимум, максимум и счётчик за интервал. Это форма агрегации, применяемая автоматически по сроку хранения, а не по запросу.
Почему нельзя восстановить процентиль из средних значений? Процентиль нелинеен: средний из почасовых p95 не равен p95 за сутки. Для корректного пересчёта нужны гистограммы или исходные точки.
Как выбрать срок хранения? От задач: расследование инцидентов — дни и недели с исходным разрешением, сравнение с прошлым периодом — месяцы с прореживанием, отчётность и ёмкостное планирование — годы с часовым шагом. Начинать стоит с короткого горячего слоя и длинного холодного.
Источники: VictoriaMetrics docs, Prometheus docs (storage), VLDB (Gorilla: дельта-дельта и XOR-сжатие) — ссылки в шапке.