Não existe realmente a melhor maneira de armazenar dados de séries temporais, e isso honestamente depende de vários fatores. No entanto, vou me concentrar principalmente em dois fatores, sendo eles:
(1) Qual é a gravidade deste projeto que merece seu esforço para otimizar o esquema?
(2) Quais são os seus padrões de acesso de consulta realmente vai ser como?
Com essas perguntas em mente, vamos discutir algumas opções de esquema.
Mesa plana
A opção de usar uma mesa plana tem muito mais a ver com a pergunta (1) , onde, se este não for um projeto sério ou em larga escala, será muito mais fácil não pensar muito no esquema e basta usar uma mesa plana, como:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Não há muitos casos em que eu recomendaria este curso, apenas se este for um projeto minúsculo que não justifique grande parte do seu tempo.
Dimensões e fatos
Portanto, se você resolveu o problema da pergunta (1) e deseja um esquema com mais desempenho, esta é uma das primeiras opções a considerar. Inclui alguma normailização básica, mas extrai as quantidades 'dimensionais' das quantidades 'factuais' medidas.
Essencialmente, você desejará uma tabela para registrar informações sobre as viagens,
CREATE trips(
trip_id integer,
other_info text);
e uma tabela para registrar registros de data e hora,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
e, finalmente, todos os seus fatos medidos, com referências de chave estrangeira para as tabelas de dimensões (ou seja, meas_facts(trip_id)
referências trips(trip_id)
e meas_facts(tstamp_id)
referências tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
Isso pode não parecer muito útil a princípio, mas se você tiver, por exemplo, milhares de viagens simultâneas, todas poderão fazer medições uma vez por segundo, na segunda. Nesse caso, você precisaria registrar novamente o registro de data e hora a cada viagem, em vez de apenas usar uma única entrada na tstamps
tabela.
Caso de uso: esse caso será bom se houver muitas viagens simultâneas para as quais você está gravando dados e você não se importa de acessar todos os tipos de medição juntos.
Como o Postgres lê por linhas, sempre que você quiser, por exemplo, as speed
medidas em um determinado intervalo de tempo, você deve ler a linha inteira da meas_facts
tabela, o que definitivamente atrasará uma consulta, embora se o conjunto de dados com o qual você está trabalhando for não muito grande, você nem notaria a diferença.
Dividindo seus fatos medidos
Para estender um pouco mais a última seção, você pode dividir suas medidas em tabelas separadas, onde, por exemplo, mostrarei as tabelas de velocidade e distância:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
e
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Obviamente, você pode ver como isso pode ser estendido para outras medidas.
Caso de uso: isso não fornecerá uma velocidade tremenda para uma consulta, talvez apenas um aumento linear na velocidade quando você estiver consultando sobre um tipo de medição. Isso ocorre porque quando você deseja procurar informações sobre velocidade, precisa ler apenas as linhas da speed_facts
tabela, em vez de todas as informações desnecessárias e extras que estariam presentes em uma linha da meas_facts
tabela.
Portanto, você precisa ler grandes quantidades de dados sobre apenas um tipo de medição, para obter alguns benefícios. Com o caso proposto de 10 horas de dados em intervalos de um segundo, você leria apenas 36.000 linhas, para que nunca encontrasse um benefício significativo ao fazer isso. No entanto, se você estivesse procurando dados de medição de velocidade para 5.000 viagens que duravam cerca de 10 horas, agora está lendo 180 milhões de linhas. Um aumento linear na velocidade de uma consulta desse tipo pode trazer algum benefício, desde que você precise acessar apenas um ou dois tipos de medição por vez.
Matrizes / HStore / & TOAST
Você provavelmente não precisa se preocupar com esta parte, mas eu sei de casos em que isso importa. Se você precisa acessar enormes quantidades de dados de séries temporais e sabe que precisa acessar todos eles em um grande bloco, pode usar uma estrutura que utilizará as tabelas TOAST , que essencialmente armazenam seus dados em arquivos compactados maiores segmentos. Isso leva a um acesso mais rápido aos dados, desde que seu objetivo seja acessar todos os dados.
Um exemplo de implementação pode ser
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
Nesta tabela, tstart
armazenaria o registro de data e hora da primeira entrada na matriz e cada entrada subsequente seria o valor de uma leitura para o próximo segundo. Isso requer que você gerencie o registro de data e hora relevante para cada valor da matriz em um software aplicativo.
Outra possibilidade é
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
onde você adiciona seus valores de medição como (chave, valor) pares de (carimbo de data e hora, medição).
Caso de uso: Esta é uma implementação provavelmente melhor para alguém que se sinta mais confortável com o PostgreSQL e somente se você tiver certeza de que seus padrões de acesso precisam ser padrões de acesso em massa.
Conclusões?
Uau, isso ficou muito mais tempo do que eu esperava, desculpe. :)
Basicamente, existem várias opções, mas você provavelmente obterá o melhor retorno usando a segunda ou terceira, pois elas se encaixam no caso mais geral.
PS: sua pergunta inicial implicava que você carregaria seus dados em massa depois que todos fossem coletados. Se você estiver transmitindo os dados para sua instância do PostgreSQL, precisará trabalhar mais para lidar com a ingestão de dados e a carga de trabalho de consulta, mas deixaremos isso para outro momento. ;)