Tenho uma tabela com esta estrutura:
ticker VARCHAR NOT NULL,
interval VARCHAR NOT NULL,
ts TIMESTAMP WITHOUT TIME ZONE NOT NULL,
price FLOAT8 NOT NULL,
UNIQUE (ticker, interval, ts)
Existem 40 tickers (que eventualmente serão estendidos para cerca de 130) e 8 intervalos. Novas linhas (40 * 8) são adicionadas a cada 10 segundos como uma cópia em massa, o que representa 115 mil linhas/hora. Eles são escritos uma vez e nunca modificados.
As operações de leitura são sempre feitas para intervalos de tempo bastante grandes (vários dias) e solicitam um ticker e 3 intervalos para ele, usando isto:
SELECT * FROM exchange.{tableName}
WHERE ticker = '{ticker}' AND \"interval\" IN ({intervalsText})
AND ts >= '{fromTime.Format}' AND ts < '{toTime.Format}'
ORDER BY ts
Minha pergunta aqui é se seria benéfico agrupar todos os intervalos em uma única linha por ticker. Assim:
ticker VARCHAR NOT NULL,
ts TIMESTAMP WITHOUT TIME ZONE NOT NULL,
price_interval0 FLOAT8 NOT NULL,
price_interval1 FLOAT8 NOT NULL,
...
price_interval7 FLOAT8 NOT NULL,
UNIQUE (ticker, ts)
Isso significa 8x menos linhas na tabela, um índice menor, mas cada consulta pode precisar carregar a linha inteira para retornar apenas 3 valores e descartar 5.
Eu não sei como o Postgres organiza os dados internamente e se uma linha inteira seria recuperada de uma só vez (que é minha suposição) e então partes dela seriam extraídas, etc ...
Qualquer conselho seria muito apreciado.
Com o tempo, isso vai ser um monte de linhas!
Fundamentos
Sim, armazenar 8
float8
em uma única linha superará 8 linhas com 1float8
cada uma por um tiro longo, em armazenamento e desempenho.Mas você pode fazer mais...
Design de mesa
Para otimizar o armazenamento e o desempenho :
db<>fiddle aqui - incluindo todos
Explicação e auxiliares
Uma entrada a cada 10 segundos chega a 6*60*24 = 8640 intervalos de tempo distintos por dia. A
smallint
com seu intervalo de -2^15 a 2^15 pode facilmente manter isso.É claro que não armazenamos o nome completo do ticker todas as vezes. Uma coluna FK smallint cobre facilmente 40 - 130 tickers distintos e faz referência a uma
ticker
tabela. Normalmente melhor para armazenamento e desempenho:O dia como
date
(4 bytes), um intervalo de temposmallint
(2 bytes) e umsmallint
para o ID do ticker, dispostos nesta sequência ocupam 8 bytes sem preenchimento de alinhamento!Infelizmente, não podemos otimizar o índice PK perfeitamente ao mesmo tempo e incorrer em 8 bytes de preenchimento de alinhamento. A única mancha na otimização de armazenamento.
Por conveniência, você pode adicionar um
VIEW
para obter dados bonitos:Como você pode ver, essa expressão produz seu carimbo de data/hora original:
A conversão reversa será usada na consulta abaixo:
Valores monetários como um "preço" não devem ser armazenados como número de ponto flutuante. Isso é uma arma de pé carregada. Usar
numeric
. Ou, como estamos otimizando para armazenamento e desempenho, umainteger
representação de Cents normalmente funciona melhor. E isso é apenas 4 bytes em vez de 8 bytes parafloat8
. (numeric
depende do comprimento real, normalmente maior). Ver:É possível inserir dados não formatados para o tipo de dados dinheiro no PostgreSQL
PostgreSQL: Qual tipo de dados deve ser usado para moeda?
Armazenar
Isso vai ocupar:
(Sua ideia original para a linha composta ocuparia (24 + 4 + (min. 8) + 8 + 8*8) = 108 bytes ou mais por linha.)
Além de sobrecarga mínima por página de dados de 8kb e nenhuma sobrecarga para tuplas mortas (nunca atualizadas).
Detalhes:
O índice PK seria menor (16 em vez de 24 bytes por tupla) se pudéssemos fazê-lo em
(the_date, timeslot, ticker_id)
. Mas precisamos dele para(ticker_id, the_date, timeslot)
dar suporte à sua consulta de maneira ideal. Igualdade antes do intervalo . Ver:Consulta
Sua consulta se torna:
Ou curta:
Observe o uso da comparação de valores ROW! Ver:
atuação
Isso é perfeitamente suportado pelo índice PK no
(ticker_id, the_date, timeslot)
. Nenhum outro índice necessário. Você recebe um plano como: