Vamos fazer algumas suposições:
Tenho uma tabela assim:
a | b
---+---
a | -1
a | 17
...
a | 21
c | 17
c | -3
...
c | 22
Fatos sobre meu set:
O tamanho de toda a tabela é de ~ 10 10 linhas.
Eu tenho ~ 100k linhas com valor
a
na colunaa
, semelhante para outros valores (por exemploc
).Isso significa ~ 100k valores distintos na coluna 'a'.
A maioria das minhas consultas lerá todos ou a maioria dos valores de um determinado valor em a, por exemplo
select sum(b) from t where a = 'c'
.A tabela é escrita de forma que os valores consecutivos estejam fisicamente próximos (ou está escrito em ordem, ou assumimos que
CLUSTER
foi usado naquela tabela e colunaa
).A tabela raramente é atualizada, estamos apenas preocupados com a velocidade de leitura.
A tabela é relativamente estreita (digamos ~25 bytes por tupla, + 23 bytes de sobrecarga).
Agora a pergunta é: que tipo de índice devo usar? Meu entendimento é:
BTree Meu problema aqui é que o índice BTree será enorme, pois até onde eu sei, ele armazenará valores duplicados (é necessário, pois não pode assumir que a tabela está classificada fisicamente). Se o BTree for enorme, acabo tendo que ler tanto o índice quanto as partes da tabela para as quais o índice aponta. (Podemos usar
fillfactor = 100
para diminuir um pouco o tamanho do índice.)BRIN Meu entendimento é que posso ter um pequeno índice aqui às custas de ler páginas inúteis. Usar um pequeno
pages_per_range
significa que o índice é maior (o que é um problema com o BRIN já que preciso ler todo o índice), ter um grandepages_per_range
significa que vou ler muitas páginas inúteis. Existe uma fórmula mágica para encontrar um bom valorpages_per_range
que leve em conta esses trade-offs?GIN/GiST Não tenho certeza se eles são relevantes aqui, pois são usados principalmente para pesquisa de texto completo, mas também ouvi dizer que eles são bons em lidar com chaves duplicadas. Um
GIN
ouGiST
índice ajudaria aqui?
Outra questão é, o Postgres usará o fato de que uma tabela é CLUSTER
ed (supondo que não haja atualizações) no planejador de consulta (por exemplo, por busca binária pelas páginas inicial/final relevantes)? Um pouco relacionado, posso apenas armazenar todas as minhas colunas em um BTree e descartar a tabela completamente (ou conseguir algo equivalente, acredito que sejam índices clusterizados no SQL Server)? Existe algum índice híbrido BTree/BRIN que ajudaria aqui?
Prefiro evitar o uso de matrizes para armazenar meus valores, pois minha consulta acabará menos legível dessa maneira (eu entendo que isso reduziria o custo dos 23 bytes por sobrecarga de tupla, reduzindo o número de tuplas).
Não necessariamente — Ter um índice btree que está 'cobrindo' será o tempo de leitura mais rápido, e se isso é tudo que você quer (ou seja, se você pode pagar pelo armazenamento extra), então é sua melhor aposta.
Se você não pode arcar com a sobrecarga de armazenamento de um índice btree de cobertura, o BRIN é ideal para você, porque você já possui clustering (isso é crucial para que o BRIN seja útil). Os índices BRIN são minúsculos , portanto, todas as páginas provavelmente estarão na memória se você escolher um valor adequado de
pages_per_range
.Nenhuma fórmula mágica, mas comece com um tamanho
pages_per_range
um pouco menor do que a média (em páginas) ocupada peloa
valor médio. Você provavelmente está tentando minimizar: (número de páginas BRIN verificadas)+(número de páginas heap verificadas) para uma consulta típica. ProcureHeap Blocks: lossy=n
no plano de execuçãopages_per_range=1
e compare com outros valores parapages_per_range
— ou seja, veja quantos blocos de heap desnecessários estão sendo verificados.Pode valer a pena considerar o GIN, mas provavelmente não o GiST - no entanto, se o agrupamento natural for realmente bom, o BRIN provavelmente será uma aposta melhor.
Aqui está uma comparação de amostra entre os diferentes tipos de índice para dados fictícios um pouco como o seu:
tabela e índices:
tamanhos de relação:
cobrindo btree:
btree simples:
BRIN pages_per_range=4:
BRIN pages_per_range=2:
GIN:
dbfiddle aqui
Além de btree e brin que parecem as opções mais sensatas, algumas outras opções exóticas que podem valer a pena investigar - elas podem ser úteis ou não no seu caso:
INCLUDE
índices . Eles estarão - esperançosamente - na próxima versão principal (10) do Postgres, por volta de setembro de 2017. Um índice em(a) INCLUDE (b)
tem a mesma estrutura que um índice em,(a)
mas inclui nas páginas folha, todos os valores deb
(mas não ordenados). O que significa que você não pode usá-lo, por exemplo, paraSELECT * FROM t WHERE a = 'a' AND b = 2 ;
. O índice pode ser usado, mas enquanto um(a,b)
índice encontrará as linhas correspondentes com uma única busca, o índice de inclusão terá que passar pelos valores (possivelmente 100K como no seu caso) que correspondema = 'a'
e verificar osb
valores.Por outro lado, o índice é um pouco menor que o
(a,b)
índice e você não precisa da ordemb
para que sua consulta calculeSUM(b)
. Você também pode ter, por exemplo,(a) INCLUDE (b,c,d)
que pode ser usado para consultas semelhantes às suas que agregam em todas as 3 colunas.Índices filtrados (parciais) . Uma sugestão que pode parecer um pouco louca * no início:
Um índice para cada
a
valor. No seu caso, cerca de 100 mil índices. Embora isso pareça muito, considere que cada índice será muito pequeno, tanto em tamanho (número de linhas) quanto em largura (já que armazenará apenasb
valores). Em todos os outros aspectos, porém, ele (os 100K índices juntos) atuará como um índice b-tree(a,b)
enquanto usa o espaço de um(b)
índice.A desvantagem é que você mesmo terá que criá-los e mantê-los, cada vez que um novo valor de
a
for adicionado à tabela. Como sua tabela é bastante estável, sem muitas (ou nenhuma) inserções/atualizações, isso não parece ser um problema.Tabelas de resumo. Como a tabela é bastante estável, você sempre pode criar e preencher uma tabela de resumo com os agregados mais comuns necessários (
sum(b), sum(c), sum(d), avg(b), count(distinct b)
, etc). Ele será pequeno (apenas 100 mil linhas) e só precisará ser preenchido uma vez e atualizado somente quando as linhas forem inseridas/atualizadas/excluídas na tabela principal.*: ideia copiada desta empresa que executa 10 milhões de índices em seu sistema de produção: The Heap: Running 10 Million Postgresql Indexes In Production (e contando) .