Як зберігати дані часових рядів


22

У мене є те, що я вважаю, що це набір даних часових рядів (будь ласка, виправте мене, якщо я помиляюся), що має купу пов'язаних значень.

Прикладом може бути моделювання автомобіля та відстеження різних його ознак під час подорожі. Наприклад:

позначка часу | швидкість | пройдена відстань | температура | тощо

Що було б найкращим способом зберігання цих даних, щоб веб-додаток міг ефективно запитувати поля, щоб знайти максимум, хвилини та графік кожного набору даних у часі?

Я почав наївно підходити до аналізу даних і кешування результатів, щоб вони ніколи не повинні були зберігатися. Однак, погравши з ним трохи, схоже, що це рішення не дозволить масштабувати довгостроковість через обмеження пам’яті, і якщо кеш слід очистити, то всі дані потрібно буде переаналізувати та повторно кешувати.

Крім того, якщо припустити, що дані відслідковуються щосекунди з рідкісною можливістю 10+ годин наборів даних, чи радимо взагалі обрізати набір даних шляхом вибірки кожні N секунд?

Відповіді:


31

Дійсно немає жодного "найкращого способу" зберігання даних часових рядів, і це, чесно, залежить від ряду факторів. Однак я сфокусуюся на двох чинниках, головним чином:

(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 мільйонів рядків. Лінійне збільшення швидкості для такого запиту може принести певну користь, якщо вам потрібно отримати доступ лише до одного або двох типів вимірювань одночасно.

Масиви / HStore / & TOAST

Можливо, вам не потрібно турбуватися про цю частину, але я знаю випадки, коли це має значення. Якщо вам потрібно отримати доступ до ОГРОМНОЇ кількості даних часових рядів, і ви знаєте, що вам потрібно отримати доступ до всіх в одному величезному блоці, ви можете використовувати структуру, яка використовуватиме таблиці 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, вам потрібно буде виконати деяку подальшу роботу, щоб обробити як ваш прийом даних, так і навантаження запиту, але ми залишимо їх на інший час. ;)


О, дякую за детальну відповідь, Кріс! Я розглядаю варіант 2 або 3.
guest82

Удачі тобі!
Кріс

Нічого собі, я би проголосував за цю відповідь 1000 разів, якби міг. Дякую за детальне пояснення.
kikocorreoso

1

Його 2019 рік і це питання заслуговує на оновлену відповідь.

  • Незалежно від того, підхід є найкращим чи ні, я залишу вас на тестування та тестування, але ось такий підхід.
  • Використовуйте розширення бази даних під назвою timescaledb
  • Це розширення, встановлене на стандартному PostgreSQL, і обробляє декілька проблем, що виникають під час зберігання часових рядів

Беручи свій приклад, спочатку створіть просту таблицю в PostgreSQL

Крок 1

CREATE TABLE IF NOT EXISTS trip (
    ts TIMESTAMPTZ NOT NULL PRIMARY KEY,
    speed REAL NOT NULL,
    distance REAL NOT NULL,
    temperature REAL NOT NULL
) 

Крок 2

  • Перетворіть це на те, що називається гіпертабелем у світі тимчасової шкали.
  • Простими словами, це велика таблиця, яка безперервно ділиться на менші таблиці з деяким часовим інтервалом, скажімо, на день, коли кожну міні-таблицю називають шматочком
  • Ця міні-таблиця не очевидна, коли ви запускаєте запити, хоча ви можете включити або виключити її у своїх запитах

    SELECT create_hypertable ('поїздка', 'ts', chunk_time_interval => інтервал '1 година', if_not_exists => ІСТИНА);

  • Що ми зробили вище, це взяти наш стіл подорожей, розділити його на міні-шматки таблиці щогодини на основі стовпця "ts". Якщо додати позначку часу від 10:00 до 10:59, вони будуть додані до 1 шматка, але 11:00 буде вставлено в новий шматок, і це триватиме нескінченно.

  • Якщо ви не хочете зберігати дані нескінченно, ви також можете DROP шматки старше, ніж 3 місяці, використовуючи

    SELECT drop_chunks (інтервал «3 місяці», «поїздка»);

  • Ви також можете отримати список усіх фрагментів, створених до дати, використовуючи такий запит

    SELECT chunk_table, table_bytes, index_bytes, total_bytes ОТ chunk_relation_size ('trip');

  • Це дасть вам список усіх міні-таблиць, створених до дати, і ви можете запустити запит на останній міні-таблиці, якщо ви хочете зі цього списку

  • Ви можете оптимізувати ваші запити, щоб вони включали, виключали шматки або працювали лише над останніми N фрагментами тощо

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.