Eu tenho uma tabela de cerca de 3,25 milhões de linhas com o seguinte formato no Postgres 9.4.1
CREATE TABLE stats
(
id serial NOT NULL,
type character varying(255) NOT NULL,
"references" jsonb NOT NULL,
path jsonb,
data jsonb,
"createdAt" timestamp with time zone NOT NULL,
CONSTRAINT stats_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
O type
é uma string simples com no máximo 50 caracteres.
A references
coluna é um objeto com uma lista de valores-chave. Basicamente, qualquer lista de valores de chave simples e apenas 1 nível de profundidade, os valores são sempre strings. Poderia ser
{
"fruit": "plum"
"car": "toyota"
}
ou pode ser
{
"project": "2532"
}
O createdAt
timestamp nem sempre é gerado a partir do banco de dados (mas será por padrão se um valor não for fornecido)
No momento, estou usando a tabela apenas com dados de teste. Nesses dados, cada linha tem uma project
chave como referência. Portanto, há 3,25 milhões de linhas com uma chave de projeto. Existem exatamente 400.000 valores distintos para a project
referência. Existem apenas 5 valores distintos para o type
campo, isso provavelmente não passaria de algumas centenas em produção.
Então estou tentando indexar a tabela para realizar a seguinte query rapidamente:
SELECT
EXTRACT(EPOCH FROM (MAX("createdAt") - MIN("createdAt")))
FROM
stats
WHERE
stats."references"::jsonb ? 'project' AND
(
stats."type" = 'event1' OR
(
stats."type" = 'event2' AND
stats."createdAt" > '2015-11-02T00:00:00+08:00' AND
stats."createdAt" < '2015-12-03T23:59:59+08:00'
)
)
GROUP BY stats."references"::jsonb->> 'project'
A consulta retorna a distância de tempo entre dois eventos com base em duas linhas de estatísticas que têm a mesma referência. Neste caso project
. Sempre há apenas 1 linha para cada valor type
selecionado reference
, mas também pode não haver linhas, caso em que o resultado retornado é 0 (isso é calculado posteriormente em uma parte diferente de uma consulta maior).
Eu criei um índice nas colunas createdAt
type
e references
, mas o plano de execução da consulta parece estar fazendo uma verificação completa.
O índice
CREATE INDEX "stats_createdAt_references_type_idx"
ON stats
USING btree
("createdAt", "references", type COLLATE pg_catalog."default");
Plano de execução:
HashAggregate (cost=111188.31..111188.33 rows=1 width=38)
(actual time=714.499..714.499 rows=0 loops=1)
Group Key: ("references" ->> 'project'::text)
-> Seq Scan on stats (cost=0.00..111188.30 rows=1 width=38)
(actual time=714.498..714.498 rows=0 loops=1)
Filter: (
(("references" ? 'project'::text)
AND ((type)::text = 'event1'::text)) OR
(((type)::text = 'event2'::text)
AND ("createdAt" > '2015-11-02 05:00:00+13'::timestamp with time zone)
AND ("createdAt" < '2015-12-04 04:59:59+13'::timestamp with time zone)))
Rows Removed by Filter: 3258680
Planning time: 0.163 ms
Execution time: 714.534 ms
Eu realmente não estou tão informado sobre planos de execução de indexação e consulta, então se alguém pudesse me indicar a direção certa, seria ótimo.
Editar
Conforme observado por Erwin, parece que mesmo se eu tivesse os índices corretos, uma verificação da tabela ainda ocorreria, pois a parte da tabela retornada da consulta é muito grande. Isso significa que, para esse conjunto de dados, esse é o tempo de consulta mais rápido que posso obter? Estou assumindo que se eu adicionasse mais 60 milhões de linhas não relacionadas sem uma referência de projeto, ele poderia usar um índice (se eu tivesse os índices corretos), mas não vejo como isso poderia acelerar a consulta adicionando mais dados. Talvez eu esteja perdendo alguma coisa.
De acordo com sua explicação atual, os índices não vão ajudar muito (se for o caso) com sua consulta atual.
Esse também é o número total de linhas, portanto, esse predicado é
true
para (quase) todas as linhas ... e não é nada seletivo. Mas não há nenhum índice útil para ajsonb
coluna"references"
. Incluí-lo no índice btree("createdAt", "references", type)
é inútil.Mesmo se você tivesse um índice GIN geralmente mais útil
"reference"
como:... Postgres ainda não teria estatísticas úteis sobre chaves individuais dentro da
jsonb
coluna.Sua consulta seleciona tudo de um tipo e uma fração desconhecida de outro tipo. Isso é uma estimativa de 20 a 40% de todas as linhas . Uma varredura sequencial certamente será o plano mais rápido. Os índices começam a fazer sentido para cerca de 5% de todas as linhas ou menos .
Para testar, você pode forçar um possível índice definindo para fins de depuração em sua sessão :
Redefinir com:
Você verá consultas mais lentas...
Você agrupa por valores de projeto:
E:
São 8 linhas por projeto em média. Dependendo das frequências de valor, ainda temos que recuperar cerca de 3 a 20% de todas as linhas se apenas escolhermos mínimo e máximo por projeto em uma subconsulta LATERAL ...
Experimente este índice, faz mais sentido do que o que você tem agora:
Postgres ainda pode cair para uma varredura sequencial ...
Mais pode ser feito com um esquema normalizado / critérios mais seletivos / uma consulta mais inteligente que seleciona apenas o mínimo e o máximo
"createdAt"
...Consulta
Eu escreveria sua consulta assim:
Notas
Não lance aqui:
stats."references"
::jsonb? 'project'A coluna
jsonb
já está, você não ganha nada. Se o predicado for seletivo, o uso do índice pode ser proibido pela conversão.Seus predicados
"createdAt"
provavelmente estão incorretos nos limites inferior e superior. Para incluir dias inteiros, considere minha alternativa sugerida.references
é uma palavra reservada , então você sempre deve colocá-la entre aspas duplas. Não o use como identificador. Semelhante para nomes de casos CaMeL com aspas duplas como"createdAt"
qualquer um. Permitido, mas propenso a erros, complicação desnecessária.type
Nada disso parece fazer sentido.
varchar(255)
em si quase nunca faz sentido . 255 caracteres é um limite arbitrário sem significado no Postgres.integer
colunatype_id
(referindo-se a uma pequenatype
tabela) que ocupa apenas 4 bytes por linha e torna os índices menores e mais rápidos.Idealmente, você teria uma
project
tabela, listando todos os projetos e outra coluna FK inteira pequenaproject_id
em formatostats
. Faria qualquer consulta mais rápida. E para critérios seletivos, consultas muito mais rápidas seriam possíveis – mesmo sem a normalização sugerida. Ao longo destas linhas:Otimize a consulta GROUP BY para recuperar o registro mais recente por usuário