Перейти к содержимому

Time Series

Time Series Databases — специализированные базы данных для хранения и анализа временных рядов (последовательностей измерений с метками времени). Оптимизированы для высокоскоростной записи (миллионы точек в секунду), эффективного сжатия (алгоритмы Delta-of-Delta, Gorilla — 1–4 байта на точку) и быстрых агрегаций по времени. Ключевые компоненты: timestamp, metric name, value, tags (индексируются) и fields. Популярные системы: InfluxDB (InfluxQL/Flux), Prometheus (PromQL, pull-модель, стандарт для Kubernetes), TimescaleDB (PostgreSQL + расширение, полный SQL), QuestDB (высокая производительность, SQL), VictoriaMetrics (масштабируемая, совместима с Prometheus). Паттерны запросов: агрегации по времени (mean, max, percentile), сравнение с предыдущим периодом (rate, increase), обнаружение аномалий, join. Retention policies, continuous queries, интеграция с Grafana. Проектирование схемы (избегать высокой кардинальности тегов). Идеальные сценарии: мониторинг инфраструктуры и приложений, IoT, финансовые данные, бизнес-метрики.

Введение: Мир, который меняется каждую секунду

Представьте, что вы смотрите на график температуры за окном. Каждую секунду датчик присылает новое значение: +21.1, +21.2, +21.0, +21.3… За день набирается 86 400 точек. За месяц — 2.5 миллиона. За год — 31 миллион. А если у вас не один датчик, а тысяча? Десять тысяч?

Это не просто “много данных”. Это особый тип данных — временные ряды (time series). Каждая точка имеет временную метку и набор значений. Данные поступают непрерывно, в огромных объемах, и запросы к ним обычно спрашивают: “что было за последний час?”, “средняя температура за вчера”, “максимальная нагрузка за месяц”.

Обычные базы данных (реляционные, документные) не оптимизированы для таких сценариев. Они хранят каждую точку как отдельную строку, и запрос “средняя температура за месяц” требует сканирования миллионов строк. Индексы по времени помогают, но не решают проблему полностью.

Time Series Database (TSDB) — это специализированная база данных для хранения и анализа временных рядов. Она оптимизирована для:

  • Высокоскоростной записи (миллионы точек в секунду)
  • Эффективного хранения (сжатие до 90-98%)
  • Быстрых агрегаций по времени (среднее, максимум, процентили за час/день/месяц)

Что такое временной ряд

Временной ряд (time series) — это последовательность измерений, упорядоченных по времени. Каждое измерение содержит:

  • Timestamp (временная метка): когда произошло измерение
  • Metric name (имя метрики): что измеряем (температура, CPU, цена акции)
  • Value (значение): числовое значение
  • Tags/Labels (метки): дополнительные измерения (датчик #1, комната “кухня”, сервер “web-01”)

Пример временного ряда (в формате InfluxDB Line Protocol):

weather,temperature,location=moscow,sensor_id=1 value=21.5 1704067200000000000
weather,temperature,location=moscow,sensor_id=1 value=21.6 1704067260000000000
weather,temperature,location=moscow,sensor_id=1 value=21.4 1704067320000000000
КомпонентПримерОписание
MeasurementweatherГруппа метрик (аналог таблицы)
Tagslocation=moscow, sensor_id=1Метки для фильтрации (индексируются)
Fieldvalue=21.5Само значение (не индексируется)
Timestamp1704067200000000000Время в наносекундах

Почему обычные БД не подходят

Проблема 1: Огромные объемы записи

1000 датчиков, каждую секунду → 86 миллионов точек в день → 31 миллиард в год.

Реляционная БД: каждая точка — INSERT. 31 миллиард INSERT в год — это десятки тысяч запросов в секунду. Реляционная БД справится, но потребует мощного железа и тонкой настройки.

TSDB: пакетная запись, сжатие, LSM-деревья. Та же нагрузка на значительно более слабом железе.

Проблема 2: Неэффективное хранение

Каждая точка в реляционной БД хранит:

  • timestamp (8 байт)
  • метрику (много байт)
  • теги (много байт)
  • значение (8 байт)
  • служебную информацию (индексы, MVCC)

Итого: сотни байт на точку → терабайты в год.

TSDB: сжатие временных рядов (алгоритмы Gorilla, Delta-of-Delta) → 1-4 байта на точку.

Проблема 3: Медленные агрегации

-- Реляционная БД: сканирование 31 миллиарда строк
SELECT AVG(value) FROM metrics 
WHERE metric = 'temperature' 
  AND location = 'moscow' 
  AND timestamp BETWEEN '2024-01-01' AND '2024-01-31';

TSDB: данные уже отсортированы по времени и сжаты. Агрегации используют пропуск блоков (min/max метаданные) и векторизованную обработку.

Как работают Time Series Databases

Хранение: Столбцы или LSM?

InfluxDB / TimescaleDB / QuestDB: колоночное хранение для временных рядов.

Timestamp column:  [t1, t2, t3, t4, t5...]
Value column:      [v1, v2, v3, v4, v5...]
Tag column:        [tag1, tag1, tag2, tag2, tag1...]

Prometheus / VictoriaMetrics: LSM-деревья (как в колоночных БД, но с оптимизациями для временных рядов).

Сжатие: Алгоритмы для временных рядов

Delta-of-Delta (для timestamps):

Исходные timestamps: 1000, 1001, 1003, 1006, 1010...
Разности:             +1,   +2,   +3,   +4...
Разности разностей:   +1,   +1,   +1...  (почти константа → отличное сжатие)

Gorilla (Facebook, для значений):

  • XOR соседних значений (для чисел с плавающей точкой)
  • Если значение не изменилось — 1 бит
  • Если изменилось немного — несколько бит
  • Сжатие до 1-2 байт на точку (с 8 байт)

Run-Length Encoding (для повторяющихся значений):

Значения: 1,1,1,1,1,2,2,2,3,3,3,3...
Сжатые:   5x1, 3x2, 4x3...

Индексы: Инвертированные индексы для тегов

Временные ряды часто фильтруют по тегам (location, sensor_id, host). TSDB строят инвертированные индексы для быстрого поиска серий.

-- Найти все временные ряды с location=moscow И sensor_id=1
-- Инвертированный индекс:
"location=moscow"    [series1, series2, series3]
"sensor_id=1"        [series1, series4, series5]
Пересечение  [series1]

Популярные Time Series Databases

InfluxDB

Самая популярная TSDB (на момент написания). InfluxQL (SQL-подобный) + Flux (новый язык).

-- InfluxQL
SELECT MEAN(value) FROM temperature 
WHERE location='moscow' AND time > now() - 1h 
GROUP BY time(5m)

-- Flux (более мощный)
from(bucket: "weather")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "temperature" and r.location == "moscow")
  |> aggregateWindow(every: 5m, fn: mean)

Характеристики InfluxDB:

  • InfluxQL (SQL-подобный) и Flux
  • Сжатие Gorilla (до 90% экономии)
  • Retention policies (автоматическое удаление старых данных)
  • Continuous queries (автоматические агрегации)
  • Кластеризация (в платной версии)

Когда использовать:

  • Общая мониторинговая платформа
  • IoT (Internet of Things)
  • Аналитика временных рядов

Prometheus

Стандарт де-факто для мониторинга в Kubernetes и микросервисной архитектуре.

# Prometheus metrics format
http_requests_total{method="GET", endpoint="/api", status="200"} 1234

# PromQL (Prometheus Query Language)
http_requests_total{method="GET"}[5m]
rate(http_requests_total[1h])

Характеристики Prometheus:

  • Pull-модель (HTTP-скрейпинг)
  • PromQL (специализированный язык)
  • Теги (labels) — основа модели данных
  • Интеграция с Grafana, Alertmanager, Kubernetes
  • Single-node (нет встроенной кластеризации)

Когда использовать:

  • Мониторинг Kubernetes
  • Мониторинг микросервисов
  • Инфраструктурная метрика (CPU, память, диск)

TimescaleDB

PostgreSQL + расширение для временных рядов. Лучший выбор, если вы уже используете PostgreSQL.

-- TimescaleDB (SQL)
CREATE TABLE temperatures (
    time TIMESTAMPTZ NOT NULL,
    location TEXT NOT NULL,
    value DOUBLE PRECISION
);

SELECT create_hypertable('temperatures', 'time');

-- Автоматическое партиционирование по времени
SELECT time_bucket('5 minutes', time) AS five_min,
       location,
       AVG(value)
FROM temperatures
WHERE time > NOW() - INTERVAL '1 day'
GROUP BY five_min, location;

Характеристики TimescaleDB:

  • Полный SQL (все возможности PostgreSQL)
  • Гипертаблицы (автоматическое партиционирование по времени)
  • Сжатие (до 90%)
  • Continuous aggregates (материализованные представления)
  • Кластеризация (multinode)

Когда использовать:

  • Уже используется PostgreSQL
  • Нужен SQL и ACID
  • Сложные запросы с JOIN к реляционным данным

QuestDB

Колоночная TSDB с фокусом на производительность и SQL.

-- QuestDB (SQL)
SELECT timestamp, location, avg(value) 
FROM temperatures
WHERE location = 'moscow' 
  AND timestamp BETWEEN '2024-01-01' AND '2024-01-31'
SAMPLE BY 1h;

Характеристики QuestDB:

  • Очень высокая производительность (быстрее InfluxDB во многих тестах)
  • Полный SQL (с расширениями для временных рядов)
  • Колоночное хранение, сжатие
  • InfluxDB Line Protocol совместимость

Когда использовать:

  • Высокая производительность критична
  • Нужен SQL
  • Не требуется кластеризация (пока)

VictoriaMetrics

Совместимая с Prometheus, но более масштабируемая.

Характеристики VictoriaMetrics:

  • Совместимость с PromQL и InfluxQL
  • Кластеризация (open source)
  • Более эффективное сжатие, чем Prometheus
  • Поддержка больших объемов (миллиарды активных временных рядов)

Когда использовать:

  • Нужна масштабируемая альтернатива Prometheus
  • Большие объемы метрик
  • Kubernetes + длительное хранение

Сравнение Time Series Databases

ХарактеристикаInfluxDBPrometheusTimescaleDBQuestDBVictoriaMetrics
ЯзыкInfluxQL/FluxPromQLSQLSQLPromQL/InfluxQL
МодельPull/PushPullPush/PullPushPush/Pull
СжатиеGorillaGorillaGorillaGorilla+Gorilla+
КластеризацияТолько платнаяНетДаНетДа (open source)
ACIDНетНетДа (PostgreSQL)НетНет
Интеграция с SQLНетНетПолнаяПолнаяНет
K8s нативныйНетДаНетНетДа
СложностьСредняяСредняяНизкая (SQL)Низкая (SQL)Средняя

Retention Policies и Continuous Queries

Retention Policy (Политика хранения)

Автоматическое удаление старых данных.

-- InfluxDB
CREATE RETENTION POLICY "one_year" ON "weather" DURATION 365d REPLICATION 1 DEFAULT

-- TimescaleDB
SELECT add_retention_policy('temperatures', INTERVAL '1 year');

Continuous Queries (Автоматическая агрегация)

Предварительное вычисление агрегатов для ускорения запросов.

-- InfluxDB
CREATE CONTINUOUS QUERY "cq_1h" ON "weather"
BEGIN
  SELECT MEAN(value) INTO "temperature_1h" FROM "temperature"
  GROUP BY time(1h), location
END

-- TimescaleDB (Continuous Aggregates)
CREATE MATERIALIZED VIEW temperatures_hourly
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', time) AS hour,
       location,
       AVG(value) AS avg_temp
FROM temperatures
GROUP BY hour, location;

Паттерны запросов в TSDB

Агрегации по времени

-- InfluxQL
SELECT MEAN(value), MAX(value), PERCENTILE(value, 95) 
FROM temperature 
WHERE time > now() - 24h 
GROUP BY time(1h), location

-- PromQL
avg_over_time(temperature[1h])
max_over_time(temperature[1h])
quantile_over_time(0.95, temperature[1h])

Сравнение с предыдущим периодом

-- InfluxQL (с подзапросом)
SELECT difference(MEAN(value)) FROM (
  SELECT MEAN(value) FROM temperature 
  WHERE time > now() - 24h 
  GROUP BY time(1h)
)

-- PromQL
rate(http_requests_total[1h])  -- изменение в секунду
increase(http_requests_total[1h])  -- абсолютное изменение

Обнаружение аномалий

-- PromQL: запросы с ошибками > 5% от общего числа
(
  sum(rate(http_requests_total{status=~"5.."}[5m]))
  /
  sum(rate(http_requests_total[5m]))
) > 0.05

Join между временными рядами

-- TimescaleDB (SQL)
SELECT t1.time, t1.value - t2.value AS difference
FROM temperatures t1
JOIN temperatures t2 
  ON t1.time = t2.time 
  AND t2.location = 'spb'
WHERE t1.location = 'msk' 
  AND t1.time > now() - 1h

Когда использовать Time Series Databases

Идеальные сценарии

СценарийПочему подходит
Мониторинг инфраструктурыCPU, память, диск, сеть — классические временные ряды
Мониторинг приложений (APM)Время ответа, количество запросов, ошибки
IoT (Internet of Things)Датчики температуры, влажности, давления
Финансовые данныеЦены акций, курсы валют, объемы торгов
Аналитика пользовательского поведенияАктивность пользователей по часам, конверсия
Метрики бизнесаПродажи по часам, количество заказов
Научные данныеЭкспериментальные измерения, сейсмические данные

Сомнительные сценарии

СценарийПочему плохо подходит
Транзакционные данные (OLTP)TSDB не поддерживают сложные транзакции
Данные с непредсказуемой схемойTSDB требуют четкой структуры (метрики, теги)
Хранение бинарных данныхTSDB для чисел, не для изображений
Очень маленькие объемы (сотни точек)Оверхед; можно использовать SQLite

Проектирование схемы для TSDB

Выбор между полем (field) и тегом (tag)

ТипИндексируетсяКардинальностьПример
TagДа (инвертированный индекс)Низкая-средняяlocation, sensor_id, host
FieldНетВысокаяtemperature, cpu_usage, value

Правило: Фильтруйте по тегам, агрегируйте по полям.

Проблема высокой кардинальности

Плохо (высокая кардинальность тегов):

http_requests{request_id="abc-123-def"}  # request_id уникален для каждого запроса

Каждый уникальный тег создает новую временную серию. Миллион уникальных request_id → миллион серий → крах производительности.

Хорошо (низкая кардинальность тегов):

http_requests{method="GET", endpoint="/api", status="200"}

Пример хорошей схемы

# Метрика: использование CPU
cpu_usage{host="web-01", cpu="0", mode="user"} 0.45
cpu_usage{host="web-01", cpu="0", mode="system"} 0.12
cpu_usage{host="web-01", cpu="0", mode="idle"} 0.43

# Метрика: температура датчика
temperature{sensor_id="temp-01", location="room-101"} 22.5

Сжатие: Почему TSDB экономят место

Сравнение размеров

Тип храненияБайт на точку1 млн точек1 млрд точек
Реляционная БД (без сжатия)50-20050-200 МБ50-200 ГБ
Реляционная БД (со сжатием)20-5020-50 МБ20-50 ГБ
TSDB (Gorilla)1-41-4 МБ1-4 ГБ

Пример: 1 миллиард точек (1000 датчиков × 1 год × 1 точка в 30 секунд)

База данныхПриблизительный размер
PostgreSQL (без сжатия)~150 ГБ
PostgreSQL (с TOAST)~50 ГБ
InfluxDB~5 ГБ
TimescaleDB (со сжатием)~3 ГБ
QuestDB~2 ГБ

Интеграция с визуализацией: Grafana

Grafana — стандарт для визуализации временных рядов. Поддерживает все популярные TSDB.

# Datasource: Prometheus
# Query: rate(http_requests_total[5m])
# Panel: Graph / Heatmap / Table / Gauge

Что можно построить в Grafana:

  • Графики метрик за время
  • Heatmap (распределение значений по времени)
  • Дашборды с алертами
  • Аномалии и прогнозы (с машинным обучением)

Распространенные ошибки

Ошибка 1: Хранение каждой точки как отдельной записи в реляционной БД

-- Плохо (медленно, много места)
INSERT INTO metrics (timestamp, sensor_id, value) VALUES (NOW(), 1, 22.5);

Как исправить: Использовать специализированную TSDB или TimescaleDB.

Ошибка 2: Высокая кардинальность тегов

# Плохо (миллионы серий)
http_requests{user_id="12345"} 1

Как исправить: Не используйте уникальные идентификаторы в тегах. Выносите их в поля (не индексируемые).

Ошибка 3: Слишком много метрик

Создание отдельной метрики для каждого экземпляра вместо использования тегов.

# Плохо
temperature_room_101 22.5
temperature_room_102 23.1

# Хорошо
temperature{room="101"} 22.5
temperature{room="102"} 23.1

Ошибка 4: Хранение нечисловых данных

# Плохо (значение не число)
event{type="deployment"} "success"

Как исправить: TSDB оптимизированы для чисел. События храните в реляционной БД или логах (Elasticsearch).

Ошибка 5: Отсутствие retention policy

Данные накапливаются годами, база раздувается, производительность падает.

Как исправить: Всегда настраивайте политику хранения. Старые данные удаляйте или агрегируйте (downsampling).

Резюме для системного аналитика

  1. Time Series Databases — специализированные базы данных для временных рядов. Они оптимизированы для высокоскоростной записи (миллионы точек в секунду), эффективного хранения (сжатие до 1-4 байт на точку) и быстрых агрегаций по времени.

  2. Ключевые компоненты: timestamp (время), metric name (имя метрики), value (значение), tags/labels (метки для фильтрации). Теги индексируются, поля — нет.

  3. Сжатие — суперсила TSDB. Алгоритмы Delta-of-Delta (для timestamps) и Gorilla (для значений) сжимают данные до 90-98%.

  4. InfluxDB — самая популярная (InfluxQL, Flux). Prometheus — стандарт для Kubernetes (PromQL, pull-модель). TimescaleDB — для тех, кто уже использует PostgreSQL (полный SQL).

  5. Высокая кардинальность тегов — главный враг. Не используйте уникальные идентификаторы (user_id, request_id) в тегах. Это создает миллионы временных серий и убивает производительность.

  6. Retention policies и continuous queries позволяют автоматически удалять старые данные и предварительно вычислять агрегаты для ускорения запросов.

Проверка знаний

Вопрос 1 из 4
Что такое time series БД?
Когда time series БД особенно уместна?
Какая проблема особенно важна для time series систем?
Что обычно важно в time series запросах?

Вопросы, где были ошибки