Хранение временных рядов
1) Зачем отдельная архитектура для временных рядов
Временные ряды (time series) — это последовательности пар (timestamp, value) с тегами (labels), которые характеризуются:- Высокой скоростью записей (ingest) и периодичностью.
- Чтениями по диапазонам времени (scan + агрегаты/оконные функции).
- Взрывной кардинальностью из-за комбинаций тегов.
- Необходимостью ретеншна (ограничения по сроку хранения) и downsampling (сжатие по времени).
- Отсюда — специальная модель хранения, форматы компрессии и протоколы запросов.
2) Модель данных и контракт метрик
2.1 Именование и теги
metric_name: глагол/существительное в единственном числе (`http_requests_total`, `cpu_usage_seconds_total`).
labels: ключи-атрибуты (`job`, `instance`, `dc`, `pod`, `status`, `method`).
Инварианты: не менять семантику имени, добавлять версии (`metric_v2`) при несовместимых изменениях.
2.2 Типы рядов
Gauge (снимок), Counter (нарастающий итог), Histogram/Summary (распределения/квантили), Event/Span (трейс-точки).
Для финансов/плотностей — фиксируйте единицы измерения и агрегируемость (суммируется/усредняется).
2.3 Политика ретеншна и роллапов
Горячая детализация (секунды/1–10 мин) → теплые агрегаты (5m/1h) → холодные (1d/1w).
Для counter — хранить rate/deriv агрегаты.
3) Путь записи: прием, буферизация, компакт
3.1 Ingest-пайплайн
Scrape (pull, Prometheus) или push (OTLP/StatsD/Graphite), часто через gateway/agent.
Буферизация в WAL (write-ahead log), затем компакция в сегменты/блоки (LSM-подобная архитектура).
Batching и сортировка по времени повышают сжатие и скорость.
3.2 Обработка out-of-order и дублей
Окно допусков (lateness window, например 5–15 мин) + политика: `drop | upsert | keep-last`.
Дедупликация по `(series_id, timestamp)` с версионностью или «последняя запись побеждает».
3.3 Компрессия
Delta-of-delta для меток времени, Gorilla/XOR для float, RLE и varint для целых, dictionary для тегов.
Оптимальный размер блока («чанка») 1–8К точек — компромисс между IOPS и CPU.
4) Схемы хранения: TSDB vs SQL/колоночники
4.1 Специализированные TSDB
Prometheus (локально, короткий ретеншн, PromQL, remote_write).
VictoriaMetrics/M3/InfluxDB — горизонтальное масштабирование, долгий ретеншн, remote read.
Форматы блоков оптимизированы под range scan + тендинговые агрегации.
4.2 Реляционные/колоночные движки
TimescaleDB (PostgreSQL): гипертаблицы, чанки по времени/пространству, continuous aggregates.
ClickHouse: MergeTree/TTL/материализованные представления, отличная компрессия и агрегации по времени.
Выбор — по экосистеме запросов (SQL vs PromQL), требованиям к join/BI и операционным навыкам команды.
5) Схема и примеры
5.1 TimescaleDB: гипертаблица + continuous aggregate
sql
CREATE TABLE metrics_cpu(
ts timestamptz NOT NULL,
host text NOT NULL,
dc text NOT NULL,
usage double precision NOT NULL,
PRIMARY KEY (ts, host, dc)
);
SELECT create_hypertable('metrics_cpu', by_range('ts'), chunk_time_interval => interval '1 day');
-- Continuous unit (5 minutes)
CREATE MATERIALIZED VIEW cpu_5m
WITH (timescaledb. continuous) AS
SELECT time_bucket('5 minutes', ts) AS ts5m, host, dc, avg(usage) AS avg_usage
FROM metrics_cpu GROUP BY 1,2,3;
-- Politicians
SELECT add_retention_policy('metrics_cpu', INTERVAL '14 days');
SELECT add_retention_policy('cpu_5m', INTERVAL '180 days');
5.2 ClickHouse: агрегирующее хранение
sql
CREATE TABLE metrics_cpu (
ts DateTime,
host LowCardinality(String),
dc LowCardinality(String),
usage Float32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (host, dc, ts)
TTL ts + INTERVAL 14 DAY
SETTINGS index_granularity = 8192;
-- Rollup in hourly detail
CREATE MATERIALIZED VIEW cpu_1h
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (host, dc, ts)
POPULATE AS
SELECT toStartOfHour(ts) AS ts, host, dc, avg(usage) AS usage
FROM metrics_cpu GROUP BY ts, host, dc;
5.3 Prometheus/VictoriaMetrics: remote_write
yaml global:
scrape_interval: 15s remote_write:
- url: http://vminsert:8480/insert/0/prometheus/api/v1/write
6) Кардинальность: как не «взорвать» хранилище
6.1 Правила
Ограничивайте label cardinality (число уникальных значений). Не включайте `user_id`, `request_id`, `trace_id`.
Нормализуйте «многозначные» теги (категории → коды).
Используйте LowCardinality типы (в CH), словари/деревья меток (в TSDB).
6.2 Контроль и алерты
Метрики: `series_count`, `label_values{label}`, top-N «дорогих» рядов.
Политики отказа записи при превышении лимита кардинальности per tenant/job.
6.3 Истории/гистограммы
Для high-cardinality лучше хранить агрегаты (histogram buckets) и pre-rollup; квантили вычислять онлайн на агрегатах.
7) Ретеншн, downsampling и tiered-storage
7.1 Политики
Hot: 3–30 дней секундной/минутной детализации.
Warm: 90–365 дней 5m/1h агрегатов.
Cold: годы дневных агрегатов, архив в объектном хранилище (S3/Glacier) с Parquet.
7.2 Техники
Continuous aggregates (Timescale), материализованные представления (CH), retention + rollup tasks (Victoria/M3/Influx).
Tiered storage: «горячие блоки» локально, «холодные» в объектном с локальным кэшем.
8) Запросы и языки
8.1 PromQL (пример)
promql rate(http_requests_total{job="api",status=~"5.."}[5m])
Ищем темп ошибок 5xx по API.
8.2 SQL-агрегаты по окнам
sql
SELECT time_bucket('1h', ts) AS hour,
dc, avg(usage) AS avg, max(usage) AS pmax
FROM metrics_cpu
WHERE ts >= now() - interval '24 hours'
GROUP BY 1,2 ORDER BY 1;
8.3 Аномалии (эскиз)
Z-score/ESD по оконной статистике, STL-декомпозиция сезонности; хранить результаты в отдельном ряду `anomaly=1/0`.
9) Интеграции и протоколы
OTLP (OpenTelemetry): метрики/трейсы/логи, экспортеры на агентах (otel-collector) → TSDB/кликхаус/объектное.
StatsD/Graphite: простые счетчики/таймеры; прокси на edge, далее — конверсия в единый формат.
Kafka/NATS: буфер для всплесков ingest, replayer для бэкфилла; консьюмеры пишут батчами.
text kafka(topic=metrics) -> stream processor (normalize/tags) -> CH INSERT INTO metrics_cpu FORMAT RowBinary
10) Доступность, HA и федерация
Replica/HA-пары TSDB или федерация Prometheus (уровень регион → глобаль).
Remote read/write для долговременного хранения и централизованных дашбордов.
Shard-by-label/time: равномерное распределение ingest, locality по `dc/tenant`.
11) Наблюдаемость самого хранилища
11.1 Метрики
Ingest: `samples/sec`, `append_latency`, `wal_fsync_ms`.
Хранение: `blocks_count`, `compaction_queue_len`, `chunk_compression_ratio`.
Запросы: `query_qps`, `scan_bytes`, `p95/p99_latency`, `alloc_bytes`.
Кардинальность: `series_count`, top-labels.
11.2 SLO
«p99 latency для диапазона 1h ≤ 200 мс при QPS≤500».
«Ingest-drop ≤ 0.01% при burst до X samples/sec».
«Compaction backlog < 10 min».
11.3 Алерты
Рост `series_count` > Y%/час.
Очередь компакции/flush > порога.
Доля out-of-order > N%, dedup/late-drops.
12) Безопасность и мульти-тенантность
Изоляция по `tenant` (лейбл в ключах, отдельные таблицы/базы, квоты).
Санитизация меток (запрет PII), контроль размеров/значений.
Шифрование «в покое» и на транспорте, аудит доступа к «чувствительным» метрикам.
13) Эксплуатационные практики
Прогрев и холодный старт: pin «горячих» блоков в кэше, prefetch последних N часов.
Backfill: отдельные пайплайны с низким приоритетом, не смешивать с онлайном.
Версионирование схемы: миграции с параллельной записью (dual-write) и последующим свитчем.
Бюджет хранения: контроль `cost_per_TB_month` + forecast роста кардинальности.
14) Анти-паттерны
Теги с высокой кардинальностью (user_id, uuid) → взрыв рядов.
«Вечные» ряды без ретеншна → бесконтрольный рост.
Запись без батчинга/сортировки → плохая компрессия и IOPS-шторм.
Смешивание OLTP и длинных сканов на одном пуле дисков.
Отсутствие политики out-of-order → дубликаты и раздувание.
Гистограммы с сотнями бакетов → стоимость ×10 без пользы.
15) Чек-лист внедрения
- Определите метрики, их типы и единицы; зафиксируйте контракт имен/лейблов.
- Выберите движок (TSDB vs SQL/колоночник) и язык запросов (PromQL/SQL).
- Спроектируйте ретеншн/роллап (hot/warm/cold) и ILM-таски.
- Настройте ingest: WAL/батчи/сортировку, окна out-of-order.
- Включите компрессию (delta-of-delta/XOR/RLE), оптимальные чанки.
- Контролируйте кардинальность: квоты, алерты, политики отказа.
- Настройте HA/федерацию и remote-write/read.
- Дашборды SLO и метрики хранилища (ingest/query/storage).
- Политики безопасности/тенант-изоляции и отсутствие PII в лейблах.
- Регулярные «game day»: бэкфилл, потеря узла, всплеск ingest.
16) FAQ
Q: Что выбрать для мониторинга инфраструктуры: Prometheus или ClickHouse/Timescale?
A: Для нативного мониторинга и PromQL — Prometheus + длительный storage (Victoria/M3). Для BI/складских сценариев и SQL — Timescale/ClickHouse.
Q: Как хранить квантили без тяжелых summary?
A: Используйте histogram с аккуратными бакетами и вычисляйте квантили при запросе; или t-digest/CKMS в агрегатах.
Q: Как поступать с out-of-order?
A: Введите окно допуска (5–15 мин) и детерминированную политику dedup; для телеметрии из мобильных/edge — окно шире.
Q: Когда нужен rollup?
A: Всегда при ретеншне > 30–90 дней: агрегаты снижают размер ×10–100 и ускоряют аналитику.
Q: Можно ли смешивать логи и метрики?
A: Храните раздельно (форматы/запросы разные). Для корреляции используйте Exemplar/TraceID и дэшборды, но не складывайте все в одну таблицу.
17) Итоги
Эффективное хранилище временных рядов — это контракт метрик + дисциплина тегов, грамотный ingest (WAL/компакция), компрессия и политики ретеншна/роллапов. Добавьте контроль кардинальности, HA/федерацию и наблюдаемость самого стора — и вы получите предсказуемые p95, разумную стоимость на месяцы-годы и устойчивость к всплескам.