O CLUSTER
comando em uma tabela grande pode levar muito tempo e bloqueia leituras e gravações na tabela durante sua execução.
Não preciso que os dados em minha tabela sejam estritamente classificados em ordem de Ãndice, só quero que as linhas que são comumente consultadas juntas tenham mais probabilidade de estar nos mesmos blocos de banco de dados do que espalhadas uniformemente pela tabela (que é a distribuição que eles naturalmente devido à natureza da forma como a data é inserida na tabela).
Isso pode fazer uma grande diferença. No exemplo abaixo, a única diferença é que o insert
possui um adicional order by mod(g,10)
para que os dados de teste sejam pré-agrupados por host_id
. Muito menos blocos precisam ser lidos ao obter todos os dados para um arquivo host_id
.
Existe alguma maneira de obter esse tipo de agrupamento sem o bloqueio exclusivo e a sobrecarga de registro do cluster
comando?
create schema stack;
set search_path=stack;
--
create table foo(host_id integer, bar text default repeat('a',400));
insert into foo(host_id) select mod(g,10) from generate_series(1,500000) g;
create index nu_foo on foo(host_id);
explain analyze select count(bar) from foo where host_id=1;
/*
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=30188.66..30188.67 rows=1 width=404) (actual time=1129.858..1129.859 rows=1 loops=1)
-> Bitmap Heap Scan on foo (cost=919.27..30066.46 rows=48883 width=404) (actual time=253.149..1110.013 rows=50000 loops=1)
Recheck Cond: (host_id = 1)
Rows Removed by Index Recheck: 320257
-> Bitmap Index Scan on nu_foo (cost=0.00..907.04 rows=48883 width=0) (actual time=251.863..251.863 rows=50000 loops=1)
Index Cond: (host_id = 1)
Total runtime: 1129.893 ms
*/
--
drop table foo;
--
create table foo(host_id integer, bar text default repeat('a',400));
insert into foo(host_id) select mod(g,10) from generate_series(1,500000) g order by mod(g,10);
create index nu_foo on foo(host_id);
explain analyze select count(bar) from foo where host_id=1;
/*
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=7550.20..7550.21 rows=1 width=32) (actual time=24.397..24.397 rows=1 loops=1)
-> Bitmap Heap Scan on foo (cost=47.80..7543.95 rows=2500 width=32) (actual time=3.988..16.189 rows=50000 loops=1)
Recheck Cond: (host_id = 1)
-> Bitmap Index Scan on nu_foo (cost=0.00..47.17 rows=2500 width=0) (actual time=3.649..3.649 rows=50000 loops=1)
Index Cond: (host_id = 1)
Total runtime: 24.437 ms
*/
--
drop schema stack cascade;
Você pode fazer isso sem usar o
cluster
comando e ter a tabela bloqueada ou gerar WAL para a tabela inteira. O custo é que você precisa verificar a tabela regularmente.A ideia básica é:
dados de amostra do esquema de teste inicialmente 'parte-agrupados':
estatÃsticas iniciais de agrupamento:
análise inicial ( 2146,503 ms ):
exclua e insira novamente as linhas não agrupadas:
novas estatÃsticas de agrupamento:
nova análise ( 48,804 ms ):
limpar:
O acima é viável agora, mas é um pouco peculiar (precisa desligar o auto-vácuo para a mesa) e requer varredura completa regular da mesa. Acho que algo semelhante sem as desvantagens poderia ser incorporado ao postgres. Você precisaria de: