Eu tenho o que acredito ser um conjunto de dados de série temporal (corrija-me se estiver errado) que possui vários valores associados.
Um exemplo seria modelar um carro e rastrear seus vários atributos durante uma viagem. Por exemplo:
carimbo de data/hora | velocidade | distância percorrida | temperatura | etc
Qual seria a melhor maneira de armazenar esses dados para que um aplicativo da Web possa consultar com eficiência os campos para encontrar máximos, mínimos e plotar cada conjunto de dados ao longo do tempo?
Comecei uma abordagem ingênua de analisar o despejo de dados e armazenar em cache os resultados para que nunca precisassem ser armazenados. Depois de brincar um pouco com isso, no entanto, parece que esta solução não seria dimensionada a longo prazo devido a restrições de memória e, se o cache fosse limpo, todos os dados precisariam ser reanalisados e armazenados novamente em cache.
Além disso, supondo que os dados sejam rastreados a cada segundo com a rara possibilidade de conjuntos de dados de mais de 10 horas, geralmente é recomendável truncar o conjunto de dados por amostragem a cada N segundos?
Não há realmente uma 'melhor maneira' de armazenar dados de séries temporais e, honestamente, depende de vários fatores. No entanto, vou me concentrar principalmente em dois fatores, sendo eles:
(1) Quão sério é este projeto que merece seu esforço para otimizar o esquema?
(2) Como serão seus padrões de acesso de consulta ?
Com essas questões 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 questão (1) , onde se não for um projeto sério ou de grande escala, você achará muito mais fácil não pensar muito no esquema, e basta usar uma mesa plana, como:
Não há muitos casos em que eu recomendaria este curso, apenas se for um projeto pequeno que não justifique muito do seu tempo.
Dimensões e Fatos
Portanto, se você superou o obstáculo da pergunta (1) e deseja um esquema com mais desempenho, essa é uma das primeiras opções a serem consideradas. Inclui alguma normailização básica, mas extraindo as quantidades 'dimensionais' das quantidades 'factuais' medidas.
Essencialmente, você vai querer uma tabela para registrar informações sobre as viagens,
e uma tabela para registrar timestamps,
e, finalmente, todos os seus fatos medidos, com referências de chave estrangeira às tabelas de dimensão (ou seja ,
meas_facts(trip_id)
referênciastrips(trip_id)
emeas_facts(tstamp_id)
referênciaststamps(tstamp_id)
)Isso pode não parecer muito útil a princípio, mas se você tiver, por exemplo, milhares de viagens simultâneas, todas elas podem estar fazendo medições uma vez por segundo, no segundo. Nesse caso, você teria que registrar novamente o carimbo de data/hora para cada viagem, em vez de usar apenas uma única entrada na
tstamps
tabela.Caso de uso: Este caso será bom se houver muitas viagens simultâneas para as quais você está registrando dados e você não se importa em acessar todos os tipos de medição juntos.
Como o Postgres lê por linhas, sempre que você quiser, por exemplo, as
speed
medições em um determinado intervalo de tempo, você deve ler toda a linha dameas_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, então 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:
e
Claro, você pode ver como isso pode ser estendido para as outras medições.
Caso de uso: portanto, isso não proporcionará 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 pesquisar informações sobre velocidade, precisa apenas ler as linhas da
speed_facts
tabela, em vez de todas as informações extras e desnecessárias que estariam presentes em uma linha dameas_facts
tabela.Portanto, se você precisar ler grandes quantidades de dados sobre apenas um tipo de medição, poderá obter algum benefício. Com o caso proposto de 10 horas de dados em intervalos de um segundo, você leria apenas 36.000 linhas, portanto, nunca encontraria um benefício significativo em fazer isso. No entanto, se você estivesse olhando os dados de medição de velocidade para 5.000 viagens que duraram cerca de 10 horas, agora você está olhando para a leitura de 180 milhões de linhas. Um aumento linear na velocidade para tal consulta pode trazer algum benefício, desde que você só precise acessar um ou dois dos tipos de medição por vez.
Arrays/HStore/ & TOAST
Você provavelmente não precisa se preocupar com essa parte, mas conheço casos em que isso importa. Se você precisa acessar ENORMES quantidades de dados de séries temporais e sabe que precisa acessar tudo isso em um bloco enorme, pode usar uma estrutura que fará uso das tabelas TOAST , que basicamente armazena seus dados em formatos maiores e compactados 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 poderia ser
Nessa tabela,
tstart
armazenaria o carimbo de data/hora da primeira entrada na matriz e cada entrada subseqüente seria o valor de uma leitura para o próximo segundo. Isso requer que você gerencie o carimbo de data/hora relevante para cada valor de matriz em um software aplicativo.Outra possibilidade é
onde você adiciona seus valores de medição como pares (chave, valor) de (carimbo de data/hora, medição).
Caso de uso: provavelmente é melhor deixar essa implementação para alguém que esteja mais familiarizado 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 longo do que eu esperava, desculpe. :)
Essencialmente, há várias opções, mas você provavelmente obterá o maior retorno possível usando a segunda ou a 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á fazer algum trabalho adicional para lidar com a ingestão de dados e a carga de trabalho da consulta, mas deixaremos isso para outro momento. ;)
É 2019 e esta pergunta merece uma resposta atualizada.
Tomando seu exemplo, primeiro crie uma tabela simples no PostgreSQL
Passo 1
Passo 2
Esta minitabela não é óbvia quando você executa consultas, embora você possa incluí-la ou excluí-la em suas consultas
SELECT create_hypertable('trip', 'ts', chunk_time_interval => intervalo '1 hora', if_not_exists => TRUE);
O que fizemos acima foi pegar nossa tabela de trip, dividi-la em tabelas de mini chunks a cada hora com base na coluna 'ts'. Se você adicionar um timestamp de 10:00 a 10:59, eles serão adicionados a 1 bloco, mas 11:00 serão inseridos em um novo bloco e isso continuará infinitamente.
Se você não deseja armazenar dados infinitamente, também pode DROP blocos com mais de 3 meses usando
SELECT drop_chunks(intervalo '3 meses', 'viagem');
Você também pode obter uma lista de todos os blocos criados até a data usando uma consulta como
SELECT chunk_table, table_bytes, index_bytes, total_bytes FROM chunk_relation_size('trip');
Isso lhe dará uma lista de todas as mini-tabelas criadas até a data e você poderá executar uma consulta na última mini-tabela se quiser desta lista
Você pode otimizar suas consultas para incluir, excluir blocos ou operar apenas nos últimos N blocos e assim por diante