Naprawdę nie ma jednego „najlepszego sposobu” na przechowywanie danych szeregów czasowych, i to szczerze zależy od wielu czynników. Jednak skupię się przede wszystkim na dwóch czynnikach, a mianowicie na:
(1) Jak poważny jest ten projekt, że zasługuje na Twój wysiłek w celu optymalizacji schematu?
(2) Jakie naprawdę będą wzorce dostępu do zapytań ?
Mając na uwadze te pytania, omówmy kilka opcji schematu.
Płaski stół
Opcja użycia płaskiego stołu ma o wiele więcej wspólnego z pytaniem (1) , w którym jeśli nie jest to projekt poważny lub na dużą skalę, łatwiej będzie ci nie myśleć zbyt dużo o schemacie, i wystarczy użyć płaskiego stołu, ponieważ:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Nie ma wielu przypadków, w których poleciłbym ten kurs, tylko jeśli jest to mały projekt, który nie gwarantuje dużej ilości czasu.
Wymiary i fakty
Jeśli więc udało Ci się usunąć przeszkodę wynikającą z pytania (1) i chcesz uzyskać schemat wydajności, jest to jedna z pierwszych opcji do rozważenia. Obejmuje ona podstawową normalizację, ale wyodrębnianie wielkości „wymiarowych” ze zmierzonych wielkości „faktycznych”.
Zasadniczo będziesz chciał, aby stolik zapisywał informacje o podróżach,
CREATE trips(
trip_id integer,
other_info text);
oraz tabelę do rejestrowania znaczników czasu,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
i na koniec wszystkie zmierzone fakty, z kluczami obcymi do tabel wymiarów (tj. meas_facts(trip_id)
referencje trips(trip_id)
i meas_facts(tstamp_id)
referencje tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
To może nie wydawać się początkowo pomocne, ale jeśli masz na przykład tysiące równoczesnych podróży, wtedy wszyscy mogą wykonywać pomiary raz na sekundę, na sekundę. W takim przypadku trzeba będzie ponownie rejestrować znacznik czasu za każdym razem, zamiast tylko jednego wpisu w tstamps
tabeli.
Przypadek użycia: ten przypadek będzie dobry, jeśli istnieje wiele równoczesnych podróży, dla których rejestrowane są dane, i nie przeszkadza ci dostęp do wszystkich rodzajów pomiarów jednocześnie.
Ponieważ Postgres czyta wiersze, w dowolnym momencie, na przykład, speed
pomiary w danym przedziale czasowym, musisz odczytać cały wiersz z meas_facts
tabeli, co zdecydowanie spowolni zapytanie, chociaż jeśli zestaw danych, z którym pracujesz, to nie jest zbyt duży, wtedy nawet nie zauważysz różnicy.
Podział mierzonych faktów
Aby jeszcze bardziej rozszerzyć ostatnią sekcję, możesz rozdzielić swoje pomiary na osobne tabele, gdzie na przykład pokażę tabele prędkości i odległości:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
i
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Oczywiście możesz zobaczyć, jak można to rozszerzyć na inne pomiary.
Przypadek użycia: nie da to ogromnego przyspieszenia zapytania, być może tylko liniowego wzrostu prędkości, gdy pytasz o jeden typ pomiaru. Dzieje się tak dlatego, że jeśli chcesz sprawdzić informacje o prędkości, musisz tylko odczytać wiersze z speed_facts
tabeli, a nie wszystkie dodatkowe niepotrzebne informacje, które byłyby obecne w rzędzie meas_facts
tabeli.
Musisz więc odczytywać ogromne ilości danych tylko o jednym rodzaju pomiaru, możesz uzyskać pewne korzyści. Przy proponowanym przypadku 10 godzin danych w jednosekundowych odstępach odczytujesz tylko 36 000 wierszy, więc tak naprawdę nigdy nie znajdziesz znaczącej korzyści. Jeśli jednak chcesz przeglądać dane pomiaru prędkości dla 5000 przejazdów, które trwały około 10 godzin, teraz możesz odczytać 180 milionów wierszy. Liniowy wzrost prędkości dla takiego zapytania może przynieść pewne korzyści, o ile wystarczy dostęp do jednego lub dwóch rodzajów pomiarów na raz.
Tablice / HStore / & TOAST
Prawdopodobnie nie musisz się martwić o tę część, ale znam przypadki, w których ma to znaczenie. Jeśli potrzebujesz dostępu do OGROMNYCH ilości danych szeregów czasowych i wiesz, że musisz uzyskać dostęp do wszystkich w jednym ogromnym bloku, możesz użyć struktury, która wykorzysta tabele TOAST , które zasadniczo przechowują twoje dane w większych, skompresowanych segmenty. Prowadzi to do szybszego dostępu do danych, o ile Twoim celem jest dostęp do wszystkich danych.
Jednym z przykładów może być implementacja
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
W tej tabeli tstart
przechowywany byłby znacznik czasu dla pierwszego wpisu w tablicy, a każdy kolejny wpis byłby wartością odczytu dla następnej sekundy. Wymaga to zarządzania odpowiednim znacznikiem czasu dla każdej wartości tablicy w oprogramowaniu aplikacyjnym.
Inną możliwością jest
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
gdzie dodajesz wartości pomiaru jako pary (klucz, wartość) (znacznik czasu, pomiar).
Przypadek użycia: Jest to implementacja, którą prawdopodobnie lepiej pozostawić osobie, która jest wygodniejsza w korzystaniu z PostgreSQL i tylko wtedy, gdy masz pewność, że wzorce dostępu muszą być wzorcami dostępu zbiorczego.
Wnioski
Wow, to trwało dłużej niż się spodziewałem, przepraszam. :)
Zasadniczo istnieje wiele opcji, ale prawdopodobnie uzyskasz największy huk za swoje pieniądze, używając drugiej lub trzeciej, ponieważ pasują one do bardziej ogólnego przypadku.
PS: Twoje początkowe pytanie sugerowało, że będziesz masowo ładować swoje dane po ich zebraniu. Jeśli przesyłasz strumieniowo dane do instancji PostgreSQL, będziesz musiał wykonać dalszą pracę, aby obsłużyć zarówno pobieranie danych, jak i obciążenie zapytaniami, ale zostawimy to na inny czas. ;)