Melhor design de banco de dados e tabela para bilhões de linhas de dados [fechado]


74

Estou escrevendo um aplicativo que precisa armazenar e analisar grandes quantidades de dados elétricos e de temperatura.

Basicamente, preciso armazenar grandes quantidades de medições horárias de uso de eletricidade nos últimos anos e nos próximos anos para dezenas de milhares de locais e depois analisar os dados de uma maneira não muito complexa.

As informações que preciso armazenar (por enquanto) são ID de local, carimbo de data e hora (data e hora), temperatura e uso de eletricidade.

Sobre a quantidade de dados que precisa ser armazenada, é uma aproximação, mas algo nesse sentido:
mais de 20.000 locais, 720 registros por mês (medições horárias, aproximadamente 720 horas por mês), 120 meses (há 10 anos) ) e muitos anos no futuro. Cálculos simples produzem os seguintes resultados:

20 000 locais x 720 registros x 120 meses (10 anos atrás) = 1 728 000 000 registros .

Como são os registros anteriores, novos registros serão importados mensalmente, ou seja, aproximadamente 20 000 x 720 = 14 400 000 novos registros por mês .

O total de locais também aumentará constantemente.

Em todos esses dados, as seguintes operações precisarão ser executadas:

  1. Recupere os dados para uma determinada data E período: todos os registros para um determinado ID de local entre as datas 01.01.2013 e 01.01.2017 e entre 07:00 e 13:00.
  2. Operações matemáticas simples para uma determinada data E intervalo de tempo, por exemplo, temperatura MIN, MAX e AVG e uso de eletricidade para um determinado ID de local por 5 anos entre as 07:00 e as 13:00.

Os dados serão gravados mensalmente, mas serão lidos por centenas de usuários (pelo menos) constantemente, portanto a velocidade de leitura é significativamente mais importante.

Não tenho experiência com bancos de dados NoSQL, mas, pelo que reuni, eles são a melhor solução para usar aqui. Eu li nos bancos de dados NoSQL mais populares, mas como eles são bastante diferentes e também permitem uma arquitetura de tabela muito diferente, não pude decidir qual é o melhor banco de dados a ser usado.

Minhas principais escolhas foram Cassandra e MongoDB, mas desde que eu tenho um conhecimento muito limitado e nenhuma experiência real quando se trata de grandes dados e NoSQL, não tenho muita certeza. Também li que o PostreSQL também lida bem com essas quantidades de dados.

Minhas perguntas são as seguintes:

  1. Devo usar um banco de dados NoSQL para quantidades tão grandes de dados. Caso contrário, posso manter o MySQL?
  2. Qual banco de dados devo usar?
  3. Devo manter a data e a hora em colunas separadas e indexadas (se possível) para recuperar e processar os dados rapidamente por determinados períodos de data e hora, ou isso pode ser feito mantendo o registro de data e hora em uma única coluna?
  4. Uma abordagem de modelagem de dados de séries temporais é apropriada aqui e, se não, você poderia me dar dicas para um bom design de tabela?

Obrigado.


29
2017. Embora não seja pequeno, isso não é particularmente uma GRANDE quantidade de dados para o hardware adequado. E eu odeio dizer, mas até agora o que você tem lá parece dados relacionais.
TomTom

6
Armazenei tabelas com várias TB com dezenas de bilhões de linhas no MS SQL Server 2008-2014 usando uma boa chave (data da época), compactação, particionamento e assegurando que minhas consultas / índices estejam alinhados com a partição. Eu tive que mudar para o NoSQL (Hadoop) quando comecei a obter petabytes de dados para analisar e indexar de maneira diferente. O NoSQL deve ter outras considerações e, nesse caso, não parece se encaixar.
Ali Razeghi

3
@AliRazeghi Hadoop não tem nada a ver com SQL ou NoSQL - é apenas um mecanismo de armazenamento. Existem muitas interfaces SQL suportadas pelo Hadoop por aí.
Mustaccio 17/10/19

3
Quais são as suas restrições quanto a: dinheiro para gastar em software / licenças?
user3067860

11
Quando você tem dinheiro infinito, sugiro comprar um dispositivo SAP HANA. É ótimo para agregações em grandes conjuntos de dados. Mas você provavelmente não tem dinheiro infinito.
Philipp

Respostas:


90

É exatamente isso que faço todos os dias, exceto que, em vez de usar os dados horários, uso os dados de 5 minutos. Eu faço o download de cerca de 200 milhões de registros todos os dias, portanto, o valor que você fala aqui não é um problema. Os dados de 5 minutos têm cerca de 2 TB de tamanho e eu tenho dados meteorológicos que remontam 50 anos a um nível horário por local. Então, deixe-me responder a perguntas com base na minha experiência:

  1. Não use o NoSQL para isso. Os dados são altamente estruturados e se encaixam perfeitamente em um banco de dados relacional.
  2. Pessoalmente, uso o SQL Server 2016 e não tenho problemas ao aplicar cálculos nesse volume de dados. Ele estava originalmente em uma instância do PostgreSQL quando iniciei meu trabalho e não conseguia lidar com o volume de dados como em uma pequena instância da AWS.
  3. Eu altamente recomendo extrair a parte de hora da data e armazená-lo separado da própria data. Acredite em mim, aprenda com meus erros!
  4. Eu armazeno a maioria dos dados em lista (DATE, TIME, DATAPOINT_ID, VALUE), mas não é assim que as pessoas desejam interpretar os dados. Esteja preparado para algumas consultas horrendas em relação aos dados e grandes quantidades de rotação. Não tenha medo de criar uma tabela não normalizada para conjuntos de resultados que são muito grandes para serem computados em tempo real.

Dica geral: eu armazeno a maioria dos dados entre dois bancos de dados, o primeiro são dados de séries temporais diretas e é normalizado. Meu segundo banco de dados está muito normalizado e contém dados pré-agregados. Tão rápido quanto meu sistema, eu não sou cego para o fato de que os usuários nem querem esperar 30 segundos para que um relatório seja carregado - mesmo que eu pessoalmente pense que 30 segundos para processar 2 TB de dados são extremamente rápidos.

Para explicar por que recomendo armazenar a hora separada da data, aqui estão alguns motivos pelos quais faço dessa maneira:

  1. A maneira como os dados elétricos são apresentados é por hora final- portanto, 01:00 é, na verdade, a média da energia elétrica da hora anterior e 00:00 é Hora de término 24. (Isso é importante porque você precisa procurar duas datas para incluir o valor de 24 horas - o dia em que você estão procurando mais a primeira marca do dia seguinte.) No entanto, os dados meteorológicos são realmente apresentados de uma maneira avançada (real e prevista para a próxima hora). Na minha experiência com esses dados, os consumidores desejam analisar o efeito do clima no preço / demanda de energia. Se você usasse uma comparação de datas diretas, compararia o preço médio da hora anterior com a temperatura média da hora seguinte, mesmo que os carimbos de hora sejam os mesmos.DATETIME coluna.
  2. Atuação. Eu diria que pelo menos 90% dos relatórios que gero são gráficos, normalmente plotando o preço em relação à hora para uma única data ou para um intervalo de datas. Ter que dividir o tempo da data pode reduzir a velocidade da consulta usada para gerar o relatório, dependendo do período que você deseja ver. Não é incomum que os consumidores desejem ver uma única data, ano após ano nos últimos 30 anos (de fato, para o clima, isso é necessário para gerar os 30 anos normais) - isso pode ser lento. Claro que você pode otimizar sua consulta e adicionar índices, e confie em mim. Eu tenho alguns índices insanos que eu preferiria não ter, mas isso faz o sistema funcionar rápido.
  3. Produtividade. Eu odeio ter que escrever o mesmo pedaço de código mais de uma vez. Eu costumava armazenar a data e a hora na mesma coluna, até precisar escrever a mesma consulta várias vezes para extrair a parte da hora. Depois de um tempo, fiquei cansado de ter que fazer isso e o extraí para sua própria coluna. Quanto menos código você precisar escrever, menor a chance de ocorrer um erro. Além disso, ter que escrever menos código significa que você pode obter seus relatórios mais rapidamente, ninguém quer esperar o dia inteiro por relatórios.
  4. Usuários finais. Nem todos os usuários finais são usuários avançados (ou seja, sabem escrever SQL). Ter os dados já armazenados em um formato que eles possam trazer para o Excel (ou outra ferramenta similar) com o mínimo de esforço fará de você um herói no escritório. Se os usuários não puderem acessar ou manipular os dados facilmente, eles não usarão seu sistema. Acredite, eu projetei o sistema perfeito há alguns anos e ninguém o utilizou por esse motivo. O design do banco de dados não consiste apenas em aderir a um conjunto predefinido de regras / diretrizes, mas em tornar o sistema utilizável.

Como eu disse acima, tudo isso é baseado na minha experiência pessoal e, deixe-me dizer, foram alguns anos difíceis e muitas redesigns para chegar onde estou agora. Não faça o que fiz, aprenda com meus erros e certifique-se de envolver os usuários finais do seu sistema (ou desenvolvedores, autores de relatórios etc.) ao tomar decisões sobre seu banco de dados.


Tive sorte ao usar o Epoch date, mas sua recomendação é interessante para o seu caso de uso. Obrigado por compartilhar.
Ali Razeghi

4
Eu discordo de muito disso. Nada disso é uma preocupação real com um banco de dados moderno, como demonstrado com números reais aqui . Se os usuários dos dados forem estúpidos demais para usar o sql, será necessário criar uma interface para eles - não altere o esquema. Extraindo a hora é uma má idéia
Evan Carroll

11
Como é o seu hardware?
Kennes

11
@kennes físico, 16 núcleos, 256 GB de RAM, unidade OS de 100 GB, SSD local de 500 GB com dados TempDB, SAN híbrida com cache SSD de 8 TB e 40 TB de discos de eixo com capacidade para 100.000 iops / s. A implementação do banco de dados usa o ColumnStore, a compactação, as tabelas na memória, o particionamento e uma instância tabular do SSAS.
Mr.Brownstone

11
Esse é um hardware incrível, dependendo de quantos usuários você atende. Como essa é uma resposta de pseudo-otimização, acho que incluir sua tecnologia é útil. Fiquei em choque ao saber que você pode triturar 2 TB em 30 segundos - isso é incrivelmente rápido. Meu julgamento pessoal à parte, acho que seria útil para futuras pessoas que procuram otimizar dados de séries temporais!
Kennes

57

Índices PostgreSQL e BRIN

Teste você mesmo. Este não é um problema em um laptop de 5 anos com um ssd.

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,  -- fake location ids in the range of 1-20000
    now() AS tsin,                   -- static timestmap
    97.5::numeric(5,2) AS temp,      -- static temp
    x::int AS usage                  -- usage the same as id not sure what we want here.
  FROM generate_series(1,1728000000) -- for 1.7 billion rows
    AS gs(x);

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
 Planning time: 0.099 ms
 Execution time: 1343954.446 ms
(3 rows)

Foram necessários 22 minutos para criar a tabela. Em grande parte, porque a tabela é de 97GB modestos. Em seguida, criamos os índices,

CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);    
VACUUM ANALYZE electrothingy;

Demorou um bom tempo para criar os índices também. Embora sejam BRIN, têm apenas 2 a 3 MB e armazenam-se facilmente em memória RAM. Ler 96 GB não é instantâneo, mas não é um problema real para o meu laptop com a sua carga de trabalho.

Agora, consultamos.

explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
         Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
         Rows Removed by Index Recheck: 16407
         Heap Blocks: lossy=128
         ->  Bitmap Index Scan on electrothingy_id_idx  (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
               Index Cond: ((id >= 1000000) AND (id <= 1001000))
 Planning time: 0.238 ms
 Execution time: 42.373 ms
(9 rows)

Atualizar com timestamps

Aqui, geramos uma tabela com registros de data e hora diferentes para satisfazer a solicitação de indexação e pesquisa em uma coluna de registro de data e hora, a criação demora um pouco mais porque to_timestamp(int)é substancialmente mais lenta do que now()(armazenada em cache para a transação)

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,
    -- here we use to_timestamp rather than now(), we
    -- this calculates seconds since epoch using the gs(x) as the offset
    to_timestamp(x::int) AS tsin,
    97.5::numeric(5,2) AS temp,
    x::int AS usage
  FROM generate_series(1,1728000000)
    AS gs(x);

                                                               QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
 Planning time: 0.607 ms
 Execution time: 7147449.908 ms
(3 rows)

Agora podemos executar uma consulta em um valor de carimbo de data e hora ,,

explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
                                                                        QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
         Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
         Rows Removed by Index Recheck: 18047
         Heap Blocks: lossy=768
         ->  Bitmap Index Scan on electrothingy_tsin_idx  (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
               Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
 Planning time: 0.140 ms
 Execution time: 83.321 ms
(9 rows)

Resultado:

 count |  min  |  max  
-------+-------+-------
 86401 | 97.50 | 97.50
(1 row)

Assim, em 83,321 ms, podemos agregar 86.401 registros em uma tabela com 1,7 bilhões de linhas. Isso deve ser razoável.

Hora final

Também é muito fácil calcular o término da hora, truncar os carimbos de data e hora e simplesmente adicionar uma hora.

SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
  count(*),
  min(temp),
  max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
  AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
          tsin          | count |  min  |  max  
------------------------+-------+-------+-------
 1974-01-01 01:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 02:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 03:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 04:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 05:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 06:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 07:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 08:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 09:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 10:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 11:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 12:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 13:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 14:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 15:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 16:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 17:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 18:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 19:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 20:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 21:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 22:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 23:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-02 00:00:00-06 |  3600 | 97.50 | 97.50
(24 rows)

Time: 116.695 ms

É importante observar que ele não está usando um índice na agregação, embora possa. Se essa é a sua consulta típica, provavelmente você quer um BRIN date_trunc('hour', tsin)nela. date_truncExiste um pequeno problema que não é imutável, então você deve primeiro envolvê-la para fazê-lo.

Particionamento

Outro ponto importante de informação no PostgreSQL é que o PG 10 traz DDL de particionamento . Assim, você pode, por exemplo, criar partições facilmente para cada ano. Dividindo seu banco de dados modesto em bancos de dados menores, pequenos. Ao fazer isso, você poderá usar e manter índices btree em vez do BRIN, o que seria ainda mais rápido.

CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
    FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');

Como queiras.


13

Me surpreende que ninguém aqui tenha mencionado benchmarking - até que o @EvanCarroll veio com sua excelente contribuição!

Se eu fosse você, passaria algum tempo (e sim, eu sei que é uma mercadoria preciosa!) Configurando sistemas, executando o que você pensa que será (obtenha a entrada do usuário final aqui!), Digamos, suas 10 perguntas mais comuns.

Meus próprios pensamentos:

As soluções NoSQL podem funcionar muito bem em casos de uso específicos, mas geralmente são inflexíveis para consultas ad-hoc. Para uma visão divertida do NoSQL de Brian Aker - ex-arquiteto-chefe do MySQL, veja aqui !

Concordo com @ Mr.Brownstone que seus dados são eminentemente adequados a uma solução relacional (e essa opinião foi confirmada por Evan Carroll )!

Se eu me comprometer com qualquer despesa, seria com a minha tecnologia de disco! Eu gastaria qualquer dinheiro que tivesse à minha disposição em NAS ou SAN ou talvez em alguns discos SSD para armazenar meus dados agregados raramente gravados!

Primeiro, eu examinaria o que tenho disponível agora . Execute alguns testes e mostre os resultados aos tomadores de decisão. Você já tem um proxy na forma de trabalho da CE ! Porém, um teste rápido ou dois reunidos em seu próprio hardware seria mais convincente!

Então pense em gastar dinheiro! Se você vai gastar dinheiro, observe primeiro o hardware, e não o software. AFAIK, você pode contratar a tecnologia de disco por um período de teste ou, melhor ainda, apresentar algumas provas de conceito na nuvem.

Minha primeira chamada pessoal para um projeto como esse seria o PostgreSQL. Isso não quer dizer que eu descartaria uma solução proprietária, mas as leis da física e dos discos são as mesmas para todos! "Você não pode melhorar as leis da física Jim" :-)


6

Se você ainda não o fez, dê uma olhada em DBMS de séries temporais, pois ele é otimizado para armazenar e consultar dados em que o foco principal é o tipo de data / hora. Normalmente, os bancos de dados de séries temporais são usados ​​para gravar dados nos intervalos de minuto / segundo / sub-segundo, portanto, não tenho certeza se ainda é apropriado para incrementos de hora em hora. Dito isto, parece que vale a pena examinar esse tipo de DBMS. Atualmente, o InfluxDB parece ser o banco de dados de séries temporais mais estabelecido e amplamente utilizado.


11
O que é um exemplo de DBMS de séries temporais?
bispo

2
Dê uma olhada aqui .
Vérace 18/10

4

Claramente, este não é um problema NoSQL, mas eu sugeriria que, embora uma solução RDBMS funcionasse, acho que uma abordagem OLAP se ajustaria muito melhor e, dados os intervalos de dados muito limitados envolvidos, sugeriria fortemente a investigação do uso de um banco de dados baseado em coluna em vez de uma linha com base. Pense dessa maneira: você pode ter 1,7 bilhão de dados, mas ainda precisa de apenas 5 bits para indexar todos os valores possíveis de hora ou dia do mês.

Tenho experiência com um domínio de problemas semelhante em que o Sybase IQ (agora SAP IQ) é usado para armazenar até 300 milhões de contadores por hora de dados de gerenciamento de desempenho de equipamentos de telecomunicações, mas duvido que você tenha o orçamento para esse tipo de solução. Na arena do código aberto, o MariaDB ColumnStore é um candidato muito promissor, mas eu recomendaria também investigar o MonetDB.

Como o desempenho da consulta é um fator importante para você, considere como as consultas serão formuladas. É aqui que o OLAP e o RDBMS mostram suas maiores diferenças: - com o OLAP, você normaliza o desempenho da consulta, para não reduzir a repetição, reduzir o armazenamento ou mesmo reforçar a consistência. Portanto, além do registro de data e hora original (você lembrou de capturar o fuso horário, espero?), Tenha um campo separado para o registro de data e hora UTC, outros para a data e hora e ainda mais para o ano, mês, dia, hora, minuto e deslocamento UTC. Se você tiver informações adicionais sobre locais, fique à vontade para mantê-lo em uma tabela de locais separada que possa ser procurada sob demanda e fique à vontade para manter a chave dessa tabela em seu registro principal, mas mantenha o nome completo da localização em sua tabela principal, como bem, afinal,

Como sugestão final, use tabelas separadas para dados agregados populares e use tarefas em lote para preenchê-los. Dessa forma, você não precisará repetir o exercício para todo e qualquer relatório que use um valor agregado e faça consultas que sejam comparadas entre atual e histórico ou histórico para histórico muito mais fácil e muito, muito mais rápido.


Você também pode considerar o Greenplum como uma loja colunar se estiver olhando para eles! Como um "bônus" - é baseado no PostgreSQL!
Vérace 19/10/19

Eu tive uma boa experiência com o HP Vertica. Tínhamos uma única tabela com 9 colunas com 130 bilhões de linhas, sem muito ajuste. Apenas funcionou.
ThatDataGuy
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.