Como sou um desenvolvedor jovem e não sou muito habilidoso no uso de bancos de dados (PostgreSQL 9.3), tive alguns problemas com um projeto, onde realmente precisava de ajuda.
Meu projeto é sobre a coleta de dados de dispositivos (até 1000 ou mais), em que cada dispositivo envia um bloco de dados por segundo, o que gera cerca de 3 milhões de linhas por hora.
Atualmente, tenho uma tabela grande onde armazeno os dados recebidos de cada dispositivo:
CREATE TABLE data_block(
id bigserial
timestamp timestamp
mac bigint
)
Como existem vários tipos de dados que um bloco de dados pode (ou não pode) incluir, existem outras tabelas que referenciam a data_block
tabela.
CREATE TABLE dataA(
data_block_id bigserial
data
CONSTRAINT fkey FOREIGN KEY (data_block_id) REFERENCES data_block(id);
);
CREATE TABLE dataB(...);
CREATE TABLE dataC(...);
CREATE INDEX index_dataA_block_id ON dataA (data_block_id DESC);
...
É possível que em um bloco de dados haja 3x dadosA, 1x dadosB, mas nenhum dadoC.
Os dados serão mantidos por algumas semanas, então eu vou ter ~ 5 bilhões de linhas nesta tabela. No momento, tenho ~ 600 milhões de linhas na tabela e minhas consultas demoram muito tempo. Decidi fazer um índice over timestamp
e mac
, porque minhas instruções select sempre consultam ao longo do tempo e, muitas vezes, também ao longo do tempo + mac.
CREATE INDEX index_ts_mac ON data_block (timestamp DESC, mac);
... mas minhas consultas ainda levam anos. Por exemplo, consultei dados para um dia e um mac:
SELECT * FROM data_block
WHERE timestamp>'2014-09-15'
AND timestamp<'2014-09-17'
AND mac=123456789
Index Scan using index_ts_mac on data_block (cost=0.57..957307.24 rows=315409 width=32) (actual time=39.849..334534.972 rows=285857 loops=1)
Index Cond: ((timestamp > '2014-09-14 00:00:00'::timestamp without time zone) AND (timestamp < '2014-09-16 00:00:00'::timestamp without time zone) AND (mac = 123456789))
Total runtime: 334642.078 ms
Fiz um vácuo total antes da execução da consulta. Existe uma maneira elegante de resolver esse problema com grandes tabelas para fazer uma consulta <10seg?
Eu li sobre particionamento, mas isso não funcionará com minhas referências de dataA, dataB, dataC a data_block_id, certo? Se funcionasse de alguma forma, devo fazer partições com o tempo ou com o mac?
Mudei meu índice para outra direção. Primeiro MAC, depois carimbo de data e hora, e obtém muito desempenho.
CREATE INDEX index_mac_ts ON data_block (mac, timestamp DESC);
Ainda assim, as consultas demoram> 30s. Especialmente quando eu faço um LEFT JOIN
com minhas tabelas de dados. Aqui está uma EXPLAIN ANALYZE
consulta com o novo índice:
EXPLAIN ANALYZE SELECT * FROM data_block WHERE mac = 123456789 AND timestamp < '2014-10-05 00:00:00' AND timestamp > '2014-10-04 00:00:00'
Bitmap Heap Scan on data_block (cost=1514.57..89137.07 rows=58667 width=28) (actual time=2420.842..32353.678 rows=51342 loops=1)
Recheck Cond: ((mac = 123456789) AND (timestamp < '2014-10-05 00:00:00'::timestamp without time zone) AND (timestamp > '2014-10-04 00:00:00'::timestamp without time zone))
-> Bitmap Index Scan on index_mac_ts (cost=0.00..1499.90 rows=58667 width=0) (actual time=2399.291..2399.291 rows=51342 loops=1)
Index Cond: ((mac = 123456789) AND (timestamp < '2014-10-05 00:00:00'::timestamp without time zone) AND (timestamp > '2014-10-04 00:00:00'::timestamp without time zone))
Total runtime: 32360.620 ms
Infelizmente meu hardware é estritamente limitado. Estou usando um Intel i3-2100 @ 3.10Ghz, 4GB RAM. Minhas configurações atuais são as seguintes:
default_statistics_target = 100
maintenance_work_mem = 512MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 4GB
work_mem = 512MB
wal_buffers = 16MB
checkpoint_segments = 32
shared_buffers = 2GB
max_connections = 20
random_page_cost = 2
Trabalhei em um aplicativo que tinha bilhões de leituras de medidores elétricos e executou a maioria das consultas em menos de 10 segundos.
Nosso ambiente era diferente. Microsoft SQL Server em uma máquina de classe de servidor (4 núcleos, 24 GB de memória). Alguma chance de atualizar para um servidor?
Um grande problema é que a ingestão das leituras, uma de cada vez, teve um grande impacto no desempenho do banco de dados. A gravação de dados exigia bloqueios e consultas aguardaria. Você pode fazer inserções em lotes?
Com seu esquema, você terá 4 tabelas muito grandes. Será importante que todas as suas junções usem índices nas duas tabelas. Uma varredura de tabela levará uma eternidade. É possível mesclá-los a 1 tabela com campos capazes nulos?
fonte
Você está atingindo os limites de escalabilidade inerentes ao Postgres (ou qualquer outro RDBMS).
Lembre-se de que um índice RDBMS é uma árvore B. Uma árvore B é O (log n) para ambos os casos, médio e pior. Isso faz com que seja uma escolha agradável, segura e previsível para valores razoáveis de N. É interrompido quando N fica muito grande.
Os bancos de dados NoSQL são (na maior parte) tabelas de hash. Uma tabela de hash é O (1) no caso médio e O (n) no pior caso. Supondo que você possa evitar o pior caso, ele funciona muito bem com valores muito grandes de N.
Além disso, é fácil paralelizar uma tabela de hash e uma árvore b não. Isso torna as tabelas de hash mais adequadas para uma arquitetura de computação distribuída.
Quando você começa a chegar a bilhões de tabelas de linhas, é hora de considerar mudar de RDBMS para NoSQL. Cassandra provavelmente seria uma boa escolha para o seu caso de uso.
fonte