Хранение истории цен конкурентов: схемы БД и временные ряды

Как хранить историю цен конкурентов в PostgreSQL: схемы таблиц, сжатие с TimescaleDB и построение аналитики для мониторинга рынка.

Мониторинг цен конкурентов — критически важная задача для любого бизнеса, работающего в e-commerce, розничной торговле или на маркетплейсах. Объёмы собираемых данных могут достигать миллионов строк в месяц, и их эффективное хранение, сжатие и анализ напрямую влияют на скорость принятия решений. При построении собственного решения или использовании готового сервиса парсинга цен важно заранее продумать архитектуру базы данных, способную справляться с высокой интенсивностью записи и обеспечивать быстрые аналитические запросы по историческим данным.

Особенности хранения ценовых данных

Цены, собранные с сайтов конкурентов, представляют собой классический пример временного ряда. Каждая запись имеет временную метку, идентификатор товара и числовое значение цены. Помимо цены могут фиксироваться: наличие товара, валюта, акционная цена, регион доставки. Ключевые характеристики такого потока данных:

  • Высокая частота поступления — парсеры могут обновлять цены десятки раз в сутки для одного товара;
  • Преобладание операций вставки — данные постоянно дописываются, удаления и обновления редки;
  • Аналитика «по окнам» — чаще всего запрашиваются изменения за день, неделю, месяц;
  • Необходимость сжатия старых данных — для экономии места и ускорения запросов.

Игнорирование этих особенностей приводит к раздуванию базы, падению производительности и высоким затратам на хранение. Поэтому инженеры ESK Solutions при разработке SaaS-решений для мониторинга цен всегда опираются на реляционные СУБД с поддержкой временных рядов и применяют облачные технологии для гибкого масштабирования.

Схемы базы данных на PostgreSQL

Классический подход — использование нормализованной структуры с отдельными таблицами для товаров, конкурентов и непосредственно цен. Примерная схема:

Таблица products

  • product_id (UUID или BIGINT) — уникальный идентификатор;
  • sku, название, категория, url на сайте конкурента;
  • metadata (JSONB) — дополнительные атрибуты.

Таблица competitors

  • competitor_id, название, домен, признак активности.

Таблица price_snapshots — основная таблица временного ряда

  • snapshot_id (BIGINT, первичный ключ), timestamp (TIMESTAMPTZ), product_id, competitor_id;
  • price (NUMERIC), currency (VARCHAR), in_stock (BOOLEAN), promo_flag (BOOLEAN);
  • raw_data (JSONB) — полный ответ парсера (может пригодиться для аудита).

Для высоконагруженных систем такую таблицу необходимо секционировать по времени (например, по месяцам или неделям). PostgreSQL с версии 10 предлагает декларативное партиционирование, что упрощает управление и ускоряет запросы за счёт обрезания секций (Partition Pruning).

Индексация и оптимизация

Нельзя создать один индекс на все поля: это замедлит вставку. Рекомендуется минимальный набор:

  • Составной индекс (product_id, timestamp) — покрывает типовые запросы «цена товара за период»;
  • Индекс на timestamp для очистки старых данных и временных срезов;
  • Частичный индекс на in_stock для фильтрации товаров в наличии.

При использовании JSONB стоит избегать сканирования всего документа — лучше вынести часто запрашиваемые поля, как показано выше. Система веб-аналитики сможет использовать эти индексы для построения дэшбордов без лишних задержек.

Сжатие и временные ряды: TimescaleDB и нативная компрессия

Даже при партиционировании объём данных растёт линейно. На помощь приходит расширение TimescaleDB — надстройка над PostgreSQL, превращающая его в полноценную time-series базу данных. Ключевые возможности:

  • Автоматическое разбиение на chunks (временные сегменты) вместо ручного партиционирования;
  • Встроенные гипертаблицы с понятным синтаксисом;
  • Нативная компрессия сжатием сегментов (до 90-98% экономии места);
  • Непрерывные агрегаты (Continuous Aggregates) — материализованные представления, обновляемые инкрементально;
  • Политика автоматического сжатия старых данных и удаления (Retention Policy).

Пример создания гипертаблицы из price_snapshots:

SELECT create_hypertable('price_snapshots', 'timestamp');

Затем включается сжатие для сегментов старше 7 дней:

ALTER TABLE price_snapshots SET (timescaledb.compress);

SELECT add_compression_policy('price_snapshots', INTERVAL '7 days');

При этом запросы к сжатым данным выполняются прозрачно. Такая архитектура отлично работает в составе SaaS-решений для мониторинга цен, где важно обеспечить быстрый отклик при любом окне выборки.

Пример оценки сжатия

Допустим, в день загружается 5 млн записей о ценах, что даёт 150 млн строк в месяц. Без сжатия каждая запись может занимать ~100 байт, итого 15 ГБ в месяц. После применения TimescaleDB-компрессии объём снижается до 0.3–0.5 ГБ. Экономия на дисковом пространстве и ускорение full-scan запросов делают такое решение экономически оправданным.

Аналитика на основе истории цен

Собранные данные — это лишь сырьё. Бизнес-ценность появляется после применения аналитических инструментов. Основные сценарии:

  • Вычисление средневзвешенной цены конкурента за период;
  • Выявление частоты и глубины скидок;
  • Сравнение собственных цен с рынком для динамического ценообразования;
  • Построение трендов и прогнозов.

В PostgreSQL это реализуется оконными функциями и агрегатами. Например, запрос, показывающий изменение цены относительно предыдущего снимка:

SELECT price - LAG(price) OVER (PARTITION BY product_id ORDER BY timestamp) AS price_diff ...

Если база развёрнута в облаке с автоскейлингом, нагрузка от сложных аналитических запросов не мешает оперативной обработке — опыт облачных сервисов ESK подтверждает, что отделение вычислительных ресурсов под аналитику с помощью read-реплик предотвращает конфликты.

Часто задаваемые вопросы

Можно ли хранить историю цен без специализированных расширений, используя обычный PostgreSQL?

Да, для небольших объёмов (до нескольких десятков миллионов записей) достаточно встроенных средств: партиционирование, правильные индексы и агрессивный VACUUM. Однако при росте данных вы неизбежно столкнётесь с деградацией производительности, и добавление TimescaleDB станет логичным шагом.

Как часто нужно запускать сжатие временных рядов?

Оптимально настраивать политику автоматического сжатия сегментов, например, через 7 дней после закрытия временного отрезка. В активных системах можно сжимать ежедневно по расписанию. Главное — не сжимать сегменты, в которые ещё может идти запись: TimescaleDB блокирует это автоматически.

Влияет ли сжатие на точность аналитики?

Нет. Компрессия TimescaleDB применяет lossless-алгоритмы, а непрерывные агрегаты пересчитывают точные значения. Ни одно числовое значение цены не округляется и не теряется.

Cтоит ли выносить горячие данные в оперативную память?

Да, для часто запрашиваемых периодов имеет смысл держать последние недели в таблицах без сжатия, а всё, что старше — в сжатом виде. Так вы получаете баланс между скоростью и стоимостью хранения. Мы помогаем проектировать такие решения в рамках услуг по комплексному парсингу и хранению цен.