Хранение временных рядов: рабочие приёмы из практики
Временные ряды быстро превращают простую таблицу в дорогой архив: растут индексы, замедляются запросы, а удаление старых данных занимает часы. Разбираем, как выбрать схему хранения и не усложнить обслуживание.

Таблица с метриками сначала кажется простой: время, устройство, значение. Но по мере роста данных индексы раздуваются, запросы за интервал читают лишнее, а удаление старых записей становится отдельной операционной задачей. Ниже — практический разбор для PostgreSQL 16: когда хватает обычных таблиц с партициями, когда смотреть на TimescaleDB или ClickHouse и какие решения проверить до запуска.
Сначала определите форму данных
Временной ряд — это не просто значение с датой. В схеме обычно есть время наблюдения, идентификатор источника, сама метрика и, иногда, набор меток: регион, версия приложения, тип датчика. Запросы тоже важно описать заранее: график за последний час, сравнение по устройствам, отчёт за месяц или поиск одного инцидента.
Для начала зафиксируйте четыре параметра: ожидаемый объём записей за сутки, срок хранения, долю поздних событий и самые частые фильтры. Без них невозможно осмысленно выбрать размер партиций и индексы. Миллион точек в сутки и несколько миллиардов — разные задачи, даже если схема таблицы выглядит одинаково.
Термин. Кардинальность — число уникальных значений. У метки «регион» она обычно невелика, у метки с уникальным ID запроса — может быть почти равна числу записей. Высококардинальные метки раздувают индексы и делают группировки дороже.
Три подхода — и цена каждого
| Подход | Что даёт | За что платите | Когда рассматривать |
|---|---|---|---|
| PostgreSQL 16 с декларативным партиционированием | Обычный SQL, транзакции и управление диапазонами времени средствами PostgreSQL | Нужно самостоятельно создавать будущие партиции, следить за индексами и регламентом хранения | Данные уже живут в PostgreSQL, а объёмы и запросы укладываются в его рабочую модель |
| TimescaleDB | Расширение для временных рядов поверх PostgreSQL: удобнее организовать разбиение и специальные операции с данными | Дополнительная зависимость, совместимость и эксплуатация расширения; доступность возможностей зависит от версии и способа развёртывания | Нужны PostgreSQL и его экосистема, но ручное обслуживание партиций стало обременительным |
| ClickHouse | Колоночное хранение и аналитические запросы по большим наборам событий | Придётся отдельно продумать модель данных, порядок сортировки, загрузку и эксплуатацию ещё одной СУБД | Основная нагрузка — агрегации по большим диапазонам, а не транзакционные изменения отдельных строк |
Это не рейтинг производительности. Итог зависит от формы запросов, железа, объёма и настроек. Если у вас уже есть PostgreSQL и нужен поиск отдельных событий вместе с транзакционными данными, начинать с замены базы обычно дороже, чем сначала измерить узкие места.
Партиционирование помогает управлять данными по времени, но не делает любой запрос быстрым. Индекс и фильтр должны соответствовать тому, как приложение действительно ищет точки.
Практический шаблон для PostgreSQL 16
Для таблицы, где большинство запросов ограничивает время и устройство, можно начать с месячных партиций. Границы указываются как полуоткрытый интервал: нижняя включается, верхняя — нет. В примере ниже создаётся таблица и одна партиция; остальные создавайте заранее по тому же шаблону.
CREATE TABLE metrics (
observed_at timestamptz NOT NULL,
device_id bigint NOT NULL,
metric_name text NOT NULL,
value double precision NOT NULL
) PARTITION BY RANGE (observed_at);
CREATE TABLE metrics_2025_01
PARTITION OF metrics
FOR VALUES FROM ('2025-01-01 00:00:00+00')
TO ('2025-02-01 00:00:00+00');
CREATE INDEX metrics_2025_01_device_time_idx
ON metrics_2025_01 (device_id, observed_at DESC);
SELECT observed_at, value
FROM metrics
WHERE device_id = 42
AND observed_at >= '2025-01-10 00:00:00+00'
AND observed_at < '2025-01-11 00:00:00+00'
AND metric_name = 'temperature'
ORDER BY observed_at DESC;
Замените демонстрационные даты на нужный диапазон. Индекс в примере ускоряет отбор по устройству и времени; если в реальных запросах чаще первым идёт другой фильтр, индекс может потребоваться изменить. Проверяйте план через EXPLAIN ANALYZE на репрезентативном объёме: сам факт наличия индекса не гарантирует, что планировщик выберет его.
Выбирайте гранулярность по объёму и обслуживанию
Слишком крупные партиции усложняют удаление старых данных и могут оставаться большими для обслуживания. Слишком мелкие создают множество объектов, индексов и операций, которыми нужно управлять. Начните с календарного месяца, если объём умеренный; при большой скорости поступления измерьте размер и время запросов на одной партиции, прежде чем переходить на неделю или день.
Создавайте партиции до того, как туда придут данные. Если запись попадёт в диапазон без подходящей партиции, вставка завершится ошибкой. Автоматическое создание будущих партиций — часть эксплуатации, а не задача, которую стоит откладывать до первого сбоя.
Держите метки под контролем
Не превращайте каждую переменную в отдельную колонку или индекс. Особенно осторожно добавляйте значения с почти уникальными идентификаторами: они увеличивают объём индексов и ухудшают группировки. Данные, по которым часто фильтруют, удобнее хранить в типизированных колонках. Редкие и изменчивые атрибуты можно держать отдельно, но сначала проверьте, как именно приложение будет их запрашивать.
Уникальные ограничения в партиционированной таблице PostgreSQL имеют важное ограничение: ключ уникальности должен включать столбец партиционирования. Если приложению нужна глобальная уникальность ID независимо от времени, продумайте её отдельно — например, на уровне источника идентификаторов или отдельной таблицы. Не обнаруживайте это при переносе схемы в production.
Порядок внедрения: пять проверок
- Снимите профиль запросов. Запишите типичные диапазоны времени, фильтры и требуемую задержку. Проверка: есть несколько реальных запросов, а не только формулировка «нужны графики».
- Оцените поток и хранение. Измерьте размер записей и индексов на тестовых данных, затем оцените рост за срок хранения. Проверка: прогноз учитывает индексы и резерв на рост, а не только размер полезных значений.
- Создайте схему и партиции. В PostgreSQL 16 проверьте границы диапазонов и заранее подготовьте расписание создания новых партиций. Проверка: тестовая запись попадает в ожидаемую партицию, а запись за пределами доступных диапазонов обрабатывается ожидаемым образом.
- Проверьте планы и индексы. Запустите типовые запросы на данных, близких к рабочему объёму, и изучите EXPLAIN ANALYZE. Проверка: запрос читает ожидаемый диапазон, а время и объём прочитанных строк укладываются в ваши требования.
- Отрепетируйте удаление старых данных. Проверьте процедуру на тестовой партиции и убедитесь, что резервное копирование и мониторинг отражают результат. Проверка: срок хранения соблюдается без случайного удаления актуального диапазона.
Что ломается на практике
Поздние события и пропуски в расписании
Событие может прийти позже своего времени наблюдения: после задержки сети, восстановления устройства или повторной отправки очереди. Если его диапазон уже удалён, вставка не пройдёт. Определите заранее, сколько времени система принимает опоздавшие данные и как восстанавливает старые диапазоны. Конкретный срок зависит от источника и требований к отчётности.
Индекс есть, а запрос всё равно тяжёлый
Частая причина — запрос не ограничивает время или приводит столбец к выражению, которое мешает использовать обычный индекс. Сравните план для запроса с явными границами времени и без них. Для аналитического сканирования огромного объёма строк индекс может не помочь: иногда важнее агрегированные данные или другая система хранения.
Сырые точки хранят бесконечно
Хранение всех исходных измерений дорого не только диском: растут резервные копии, время восстановления и обслуживание индексов. Если старые данные нужны только для графиков, рассмотрите отдельные агрегаты с более редким шагом и определите, сколько исходных точек действительно требуется. Политика хранения должна учитывать требования продукта и восстановления, а не только свободное место.
Когда что выбирать
- Оставаться на PostgreSQL 16, если важны транзакции и совместное использование данных, а вы готовы автоматизировать создание партиций и контролировать планы запросов.
- Проверить TimescaleDB, если хотите сохранить PostgreSQL, но хотите меньше ручной работы с временной структурой. До выбора проверьте актуальную документацию для версии расширения и условия вашей платформы.
- Рассмотреть ClickHouse, если основной сценарий — аналитика и агрегации по большим массивам событий. Сначала испытайте характерные запросы и загрузку; не переносите схему один в один без проверки модели и поведения.
Сжатие, автоматические агрегаты и доставка данных из очереди — отдельные темы. Здесь важнее сначала обеспечить предсказуемое разбиение по времени, индексы под реальные фильтры и проверяемую политику хранения.
Что делать дальше
Возьмите один типичный запрос приложения и прогоните его на копии данных с ожидаемым объёмом на несколько месяцев. Зафиксируйте время выполнения, прочитанные строки и размер индексов. Затем повторите тест с другой гранулярностью партиций и сравните результат. В production наблюдайте за ростом таблиц и индексов, временем самых частых запросов, пропусками создания партиций и количеством поздних событий. Эти показатели подскажут, когда текущая схема перестала справляться и пора менять подход.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: PostgreSQL 16: декларативное партиционирование, Timescale Documentation: hypertables
Частые вопросы
Обязательно ли использовать TimescaleDB для временных рядов?
Какой размер партиции выбрать?
Что будет, если событие придёт за пределами созданных партиций?
Можно ли удалить старые данные обычным DELETE?
Сработает ли пример на другой версии PostgreSQL?
Что измерять в production в первую очередь?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.