Percebi que o desempenho de uma consulta envolvendo uma coluna jsonb variou significativamente entre as execuções de VACUUM ANALYZE durante o teste. Eu recebo planos de execução completamente diferentes aparentemente aleatoriamente depois de analisar a tabela.
Estou usando o Postgres 9.6 aqui. A configuração para meus testes é a seguinte, estou inserindo uma única chave "x" na coluna jsonb "params" com valores entre 1 e 6, sendo 1 o valor mais raro e 6 o mais comum. Eu também tenho uma coluna int regular "single_param" que contém a mesma distribuição de valores para comparação.:
CREATE TABLE test_data (
id serial,
single_param int,
params jsonb
);
INSERT INTO test_data
SELECT
generate_series(1, 1000000) AS id,
floor(log(random() * 9999999 + 1)) AS single_param,
json_build_object(
'x', floor(log(random() * 9999999 + 1))
) AS params;
CREATE INDEX idx_test_btree ON test_data (cast(test_data.params->>'x' AS int));
CREATE INDEX idx_test_gin ON test_data USING GIN (params);
CREATE INDEX ON test_data(id)
CREATE INDEX ON test_data(single_param)
A consulta que estou testando é uma consulta típica para paginar resultados, estou classificando por id e limitando a saída às primeiras 50 linhas.
SELECT * FROM test_data where (params->>'x')::int = 1 ORDER BY id DESC LIMIT 50;
Eu recebo uma das duas saídas de análise de explicação aleatoriamente após a execução VACUUM ANALYZE
:
Limit (cost=0.42..836.59 rows=50 width=33) (actual time=39.679..410.292 rows=10 loops=1)
-> Index Scan Backward using test_data_id_idx on test_data (cost=0.42..44317.43 rows=2650 width=33) (actual time=39.678..410.283 rows=10 loops=1)
Filter: (((params ->> 'x'::text))::integer = 1)
Rows Removed by Filter: 999990"
Planning time: 0.106 ms
Execution time: 410.314 ms
ou
Limit (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.034 rows=10 loops=1)
-> Sort (cost=8.45..8.46 rows=1 width=33) (actual time=0.032..0.032 rows=10 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_test_btree on test_data (cost=0.42..8.44 rows=1 width=33) (actual time=0.007..0.016 rows=10 loops=1)
Index Cond: (((params ->> 'x'::text))::integer = 1)
Planning time: 0.320 ms
Execution time: 0.052 ms
A diferença é que a estimativa para o número de colunas correspondentes à cláusula where é diferente entre os dois planos. Na primeira a estimativa é de 2650 linhas, na segunda 1 linha enquanto o número real é de 10 linhas.
A seguinte versão da consulta que pode potencialmente usar o índice GIN parece usar uma estimativa padrão para a coluna json de 1%, o que também resulta no plano de consulta incorreto como acima:
SELECT * FROM test_data where params @> '{"x": 1}' ORDER BY id DESC LIMIT 50;
Minha suposição original era que o Postgres não teria nenhuma estatística na coluna jsonb e sempre usaria uma estimativa como faz para a consulta usando o @>
operador. Mas para a consulta que é escrita poder usar o índice btree que criei, ela usa estimativas diferentes. Às vezes, esses são bons o suficiente, e às vezes são ruins.
De onde vêm essas estimativas? Eu acho que eles são algum tipo de estatística que o Postgres cria com o índice. Para estatísticas de coluna existe a opção de coletar estatísticas mais precisas, existe algo assim para essas estatísticas aqui? Ou alguma outra maneira de fazer com que o Postgres escolha o melhor plano no meu caso?
Atualmente (versão 9.6), o Postgres não possui estatísticas sobre os componentes internos de tipos de documentos como
json
,jsonb
,xml
ouhstore
. (Tem havido discussão se e como mudar isso.) Em vez disso, o planejador de consultas do Postgres usa estimativas de frequência padrão constantes (como você observou).No entanto , existem estatísticas separadas para índices funcionais como o seu
idx_test_btree
. O manual tem essa dica para você:O volume de estatísticas coletadas depende da configuração geral de
default_statistics_target
, que pode ser anulada com uma configuração por coluna. A configuração da coluna cobre automaticamente os índices dependentes.A configuração padrão de
100
é conservadora. Para seu teste com 1 milhão de linhas, se a distribuição de dados for desigual, pode ajudar a aumentá-la substancialmente. Verificando isso mais uma vez, descobri que você pode realmente ajustar o destino das estatísticas por coluna de índice comALTER INDEX
, que atualmente não está documentado. Veja a discussão relacionada em pgsql-docs.Os nomes padrão para colunas de índice não são exatamente intuitivos, mas você pode procurá-los com:
Deve resultar no nome do tipo
int4
como nome da coluna de índice para o seu caso.A melhor configuração para
STATISTICS
depende de vários fatores: distribuição de dados, tipo de dados, frequência de atualização, características de consultas típicas, ...Internamente, isso define o valor de
pg_attribute.attstattarget
, e o significado exato disso é ( por documentação ):Em seguida, execute
ANALYZE
se você não quiser esperar o autovacuum entrar em ação:Você deve
ANALYZE
a tabela, pois não podeANALYZE
indexar diretamente. Verifique com (antes e depois se quiser verificar o efeito):Tente sua consulta novamente...
Relacionado: