На самом деле не существует «лучшего способа» хранения данных временных рядов, и это, честно говоря, зависит от ряда факторов. Тем не менее, я собираюсь сосредоточиться в первую очередь на двух факторах:
(1) Насколько серьезен этот проект, который заслуживает ваших усилий по оптимизации схемы?
(2) Каковы ваши модели доступа запрос действительно будет похоже?
Помня об этих вопросах, давайте обсудим несколько вариантов схемы.
Плоский стол
Возможность использовать плоскую таблицу имеет гораздо больше общего с вопросом (1) , где, если это не серьезный или крупномасштабный проект, вам будет гораздо проще не слишком много думать о схеме, и просто используйте плоский стол, как:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Не так много случаев, когда я бы рекомендовал этот курс, только если это крошечный проект, который не требует много времени.
Размеры и факты
Таким образом, если вы устранили препятствие в вопросе (1) и хотите получить более производительную схему, это один из первых вариантов, который следует рассмотреть. Он включает некоторую базовую нормализацию, но извлекает «размерные» величины из измеренных «фактических» величин.
По сути, вы хотите таблицу для записи информации о поездках,
CREATE trips(
trip_id integer,
other_info text);
и таблица для записи временных меток,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
и, наконец, все ваши измеренные факты со ссылками внешнего ключа на таблицы измерений (то есть meas_facts(trip_id)
ссылки trips(trip_id)
и meas_facts(tstamp_id)
ссылки tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
На первый взгляд может показаться, что это не очень полезно, но если у вас есть, например, тысячи одновременных поездок, то все они могут проводить измерения один раз в секунду, а во второй. В этом случае вам придется перезаписывать отметку времени каждый раз для каждой поездки, а не просто использовать одну запись в tstamps
таблице.
Вариант использования: этот вариант будет полезен, если существует много одновременных отключений, для которых вы записываете данные, и вы не против получить доступ ко всем типам измерений одновременно.
Поскольку Postgres читает по строкам, в любое время, например, когда вам нужно, например, speed
измерения в заданном временном диапазоне, вы должны прочитать всю строку из meas_facts
таблицы, что определенно замедлит запрос, хотя, если набор данных, с которым вы работаете, не слишком большой, тогда вы даже не заметите разницу.
Разделение ваших измеренных фактов
Чтобы немного расширить последний раздел, вы можете разбить ваши измерения на отдельные таблицы, где, например, я покажу таблицы скорости и расстояния:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
а также
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Конечно, вы можете видеть, как это может быть распространено на другие измерения.
Вариант использования: Таким образом, это не даст вам огромную скорость запроса, возможно, только линейное увеличение скорости, когда вы запрашиваете один тип измерения. Это потому, что когда вы хотите посмотреть информацию о скорости, вам нужно только прочитать строки из speed_facts
таблицы, а не всю лишнюю, ненужную информацию, которая будет присутствовать в строке meas_facts
таблицы.
Таким образом, вам нужно прочитать огромные объемы данных только об одном типе измерения, вы можете получить некоторую выгоду. С вашим предложенным случаем 10 часов данных с интервалами в одну секунду вы будете читать только 36 000 строк, поэтому вы никогда не сможете получить существенную выгоду от этого. Однако, если вам нужно было посмотреть данные измерения скорости для 5000 поездок, которые длились около 10 часов, теперь вы смотрите на чтение 180 миллионов строк. Линейное увеличение скорости для такого запроса может принести некоторую выгоду, при условии, что вам нужно только получить доступ к одному или двум типам измерений одновременно.
Массивы / Магазин / и тосты
Вам, вероятно, не нужно беспокоиться об этой части, но я знаю случаи, когда это имеет значение. Если вам нужен доступ к ОГРОМНОМУ количеству данных временных рядов, и вы знаете, что вам нужен доступ ко всем этим в одном огромном блоке, вы можете использовать структуру, которая будет использовать таблицы TOAST , которая по существу хранит ваши данные в больших сжатых данных. сегменты. Это приводит к более быстрому доступу к данным, если ваша цель - получить доступ ко всем данным.
Одним из примеров реализации может быть
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
В этой таблице tstart
будет храниться отметка времени для первой записи в массиве, и каждая последующая запись будет значением чтения для следующей секунды. Это требует от вас управления соответствующей отметкой времени для каждого значения массива в части прикладного программного обеспечения.
Другая возможность
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
где вы добавляете свои значения измерений в виде (ключ, значение) пар (метка времени, измерение).
Вариант использования: эту реализацию, вероятно, лучше оставить кому-то, кому более комфортно работать с PostgreSQL, и только если вы уверены, что ваши шаблоны доступа должны быть шаблонами массового доступа.
Выводы?
Вау, это стало намного дольше, чем я ожидал, извини. :)
По сути, есть несколько вариантов, но вы, вероятно, получите наибольшую отдачу от своих денег, используя второй или третий, поскольку они соответствуют более общему случаю.
PS: Ваш первоначальный вопрос подразумевал, что вы будете массово загружать свои данные после того, как все они будут собраны. Если вы передаете данные в ваш экземпляр PostgreSQL, вам потребуется проделать дополнительную работу, чтобы справиться как с потреблением данных, так и с рабочей нагрузкой запросов, но мы оставим это в другой раз. ;)
Его 2019 и этот вопрос заслуживает обновленного ответа.
Используя ваш пример, сначала создайте простую таблицу в PostgreSQL
Шаг 1
Шаг 2
Эта мини-таблица неочевидна при выполнении запросов, хотя вы можете включить или исключить ее в своих запросах
SELECT create_hypertable ('trip', 'ts', chunk_time_interval => интервал '1 час', if_not_exists => TRUE);
То, что мы сделали выше, это возьмите нашу таблицу поездок, делите ее на таблицы мини-чанков каждый час на основе столбца «ts». Если вы добавите метку времени с 10:00 до 10:59, они будут добавлены к 1 чанку, но 11:00 будут вставлены в новый чанк, и это будет продолжаться бесконечно.
Если вы не хотите хранить данные бесконечно, вы также можете ОТБРАТЬ куски старше, чем, скажем, 3 месяца, используя
SELECT drop_chunks (интервал «3 месяца», «поездка»);
Вы также можете получить список всех чанков, созданных до даты, используя запрос типа
SELECT chunk_table, table_bytes, index_bytes, total_bytes FROM chunk_relation_size ('trip');
Это даст вам список всех мини-таблиц, созданных до даты, и вы можете выполнить запрос к последней мини-таблице, если хотите из этого списка
Вы можете оптимизировать свои запросы для включения, исключения чанков или работы только с последними N чанками и т. Д.
источник