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| Компонент | Пример | Описание |
|---|---|---|
| Measurement | weather | Группа метрик (аналог таблицы) |
| Tags | location=moscow, sensor_id=1 | Метки для фильтрации (индексируются) |
| Field | value=21.5 | Само значение (не индексируется) |
| Timestamp | 1704067200000000000 | Время в наносекундах |
Почему обычные БД не подходят
Проблема 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
| Характеристика | InfluxDB | Prometheus | TimescaleDB | QuestDB | VictoriaMetrics |
|---|---|---|---|---|---|
| Язык | InfluxQL/Flux | PromQL | SQL | SQL | PromQL/InfluxQL |
| Модель | Pull/Push | Pull | Push/Pull | Push | Push/Pull |
| Сжатие | Gorilla | Gorilla | Gorilla | Gorilla+ | 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.05Join между временными рядами
-- 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-200 | 50-200 МБ | 50-200 ГБ |
| Реляционная БД (со сжатием) | 20-50 | 20-50 МБ | 20-50 ГБ |
| TSDB (Gorilla) | 1-4 | 1-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).
Резюме для системного аналитика
Time Series Databases — специализированные базы данных для временных рядов. Они оптимизированы для высокоскоростной записи (миллионы точек в секунду), эффективного хранения (сжатие до 1-4 байт на точку) и быстрых агрегаций по времени.
Ключевые компоненты: timestamp (время), metric name (имя метрики), value (значение), tags/labels (метки для фильтрации). Теги индексируются, поля — нет.
Сжатие — суперсила TSDB. Алгоритмы Delta-of-Delta (для timestamps) и Gorilla (для значений) сжимают данные до 90-98%.
InfluxDB — самая популярная (InfluxQL, Flux). Prometheus — стандарт для Kubernetes (PromQL, pull-модель). TimescaleDB — для тех, кто уже использует PostgreSQL (полный SQL).
Высокая кардинальность тегов — главный враг. Не используйте уникальные идентификаторы (user_id, request_id) в тегах. Это создает миллионы временных серий и убивает производительность.
Retention policies и continuous queries позволяют автоматически удалять старые данные и предварительно вычислять агрегаты для ускорения запросов.