Como armazenar dados de séries temporais


22

Acredito que seja um conjunto de dados de séries temporais (por favor, corrija-me se estiver errado) que possua vários valores associados.

Um exemplo seria modelar um carro e rastrear seus vários atributos durante uma viagem. Por exemplo:

timestamp | 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 no máximo, min 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 eles nunca precisassem ser armazenados. Depois de brincar um pouco, no entanto, parece que essa 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 analisados ​​e armazenados em cache novamente.

Além disso, supondo que os dados sejam rastreados a cada segundo com a rara possibilidade de mais de 10 horas de conjuntos de dados, é geralmente recomendado truncar o conjunto de dados amostrando a cada N segundos?

Respostas:


31

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 tstampstabela.

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 speedmedidas em um determinado intervalo de tempo, você deve ler a linha inteira da meas_factstabela, 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_factstabela, em vez de todas as informações desnecessárias e extras que estariam presentes em uma linha da meas_factstabela.

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, tstartarmazenaria 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. ;)


Uau, obrigado pela resposta detalhada, Chris! Vou olhar para o uso da opção 2 ou 3.
guest82 17/07/2015

Boa sorte para você!
Chris

Uau, eu votaria nesta resposta 1000 vezes, se pudesse. Obrigado pela explicação detalhada.
Kikocorreoso 5/05

1

Seu 2019 e esta pergunta merecem uma resposta atualizada.

  • Se a abordagem é a melhor ou não, é algo que eu deixarei para você avaliar e testar, mas aqui está uma abordagem.
  • Use uma extensão de banco de dados chamada timescaledb
  • Esta é uma extensão instalada no PostgreSQL padrão e lida com vários problemas encontrados ao armazenar razoavelmente bem as séries temporais.

Tomando seu exemplo, primeiro crie uma tabela simples no PostgreSQL

Passo 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
) 

Passo 2

  • Transforme isso no que é chamado de hipertabela no mundo do timescaledb.
  • Em palavras simples, é uma tabela grande que é continuamente dividida em tabelas menores de algum intervalo de tempo, digamos, um dia em que cada minitabela é chamada de pedaço
  • Essa minitabela não é óbvia quando você executa consultas, embora possa incluí-la ou excluí-la em suas consultas

    SELECT create_hypertable ('trip', 'ts', chunk_time_interval => intervalo '1 hora', se não existir = = TRUE);

  • O que fizemos acima é pegar nossa tabela de viagens, dividi-la em mini tabelas de blocos a cada hora, com base na coluna 'ts'. Se você adicionar um carimbo de data e hora de 10:00 a 10:59, eles serão adicionados a 1 pedaço, mas 11:00 serão inseridos em um novo pedaço e isso continuará infinitamente.

  • Se você não deseja armazenar dados infinitamente, também pode desdobrar pedaços mais antigos que três meses usando

    SELECT drop_chunks (intervalo '3 meses', 'viagem');

  • Você também pode obter uma lista de todos os pedaços criados até a data usando uma consulta como

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

  • Isso fornecerá uma lista de todas as minitabelas criadas até a data e você poderá executar uma consulta na última minitabela, se desejar dessa lista

  • Você pode otimizar suas consultas para incluir, excluir blocos ou operar apenas nos últimos N blocos e assim por diante

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.