Estou usando o Postgres 9.3 através do Heroku.
Eu tenho uma tabela, "tráfego", com mais de 1 milhão de registros com muitas inserções e atualizações todos os dias. Preciso executar operações SUM nesta tabela em diferentes intervalos de tempo e essas chamadas podem levar até 40 segundos e gostaria de ouvir sugestões sobre como melhorar isso.
Eu tenho o seguinte índice em vigor nesta tabela:
CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;
Aqui está um exemplo de instrução SELECT:
SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000'
E este é o EXPLAIN ANALYZE:
Aggregate (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
-> Index Scan using idx_traffic_partner_only on traffic (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms
http://explain.depesz.com/s/gGA
Essa pergunta é muito semelhante à outra no SE, mas essa usou um índice em dois intervalos de carimbo de data / hora da coluna e o planejador de índice para essa consulta tinha estimativas muito distantes. A principal sugestão foi criar um índice classificado de várias colunas, mas para índices de coluna única que não têm muito efeito. As outras sugestões foram usar os índices CLUSTER / pg_repack e GIST, mas ainda não os experimentei, pois gostaria de ver se há uma solução melhor usando índices regulares.
Otimizando consultas em vários timestamps (duas colunas)
Para referência, tentei os seguintes índices, que não foram usados pelo DB:
INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);
EDIT : Ran EXPLAIN (ANALISAR, VERBOSO, CUSTOS, TAMPÕES) e estes foram resultados:
Aggregate (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
Output: sum(clicks), sum(impressions)
Buffers: shared hit=47783 read=29803 dirtied=4
I/O Timings: read=184.936
-> Index Scan using idx_traffic_partner_only on public.traffic (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
Buffers: shared hit=47783 read=29803 dirtied=4
I/O Timings: read=184.936
Total runtime: 526.881 ms
http://explain.depesz.com/s/7Gu6
Definição da tabela:
CREATE TABLE traffic (
id serial,
uuid_self uuid not null,
uuid_partner uuid not null,
impressions integer NOT NULL DEFAULT 1,
clicks integer NOT NULL DEFAULT 0,
campaign_id integer,
dt_created DATE DEFAULT CURRENT_DATE NOT NULL,
dt_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)
id é a chave primária e uuid_self, uuid_partner e campaign_id são todas chaves estrangeiras. O campo dt_updated é atualizado com uma função postgres.
fonte
explain (buffers, analyze, verbose) ...
pode lançar mais luz.traffic
. Além disso: por que o segundoEXPLAIN
mostra uma queda de 42 s para 0,5 s? A primeira execução foi realizada com cache frio?id
? Alguma outra restrição? Eu vejo duas colunas que podem ser NULL. Qual é a porcentagem de valores NULL em cada um? O que você ganha por isso?SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Respostas:
Duas coisas que são muito estranhas aqui:
A consulta seleciona 300k linhas de uma tabela com mais de 1 milhão de linhas. Para 30% (ou qualquer coisa acima de 5% - depende do tamanho da linha e de outros fatores), normalmente não vale a pena usar um índice. Deveríamos ver uma varredura seqüencial .
A exceção seria verificações apenas de índice, que não vejo aqui. O índice de várias colunas @Craig sugerido seria a melhor opção se você obtiver verificações somente de índice. Com muitas atualizações como você mencionou, isso pode não funcionar, caso em que você está melhor sem as colunas adicionais - e apenas o índice que você já possui. Você poderá fazê- lo funcionar com configurações de vácuo automático mais agressivas para a tabela. Você pode ajustar parâmetros para tabelas individuais.
Enquanto o Postgres usará o índice, eu certamente esperaria ver uma varredura de índice de bitmap para muitas linhas, não uma varredura de índice simples, que normalmente é a melhor opção para uma baixa porcentagem de linhas. Assim que o Postgres espera vários acessos por página de dados (a julgar pelas estatísticas na tabela), ele normalmente muda para uma verificação de índice de bitmap.
A julgar por isso, eu suspeitaria que suas configurações de custo são inadequadas (e possivelmente também as estatísticas da tabela). Você pode ter definido
random_page_cost
e / ou muito baixo , em relação a . Siga os links e leia o manual.cpu_index_tuple_cost
seq_page_cost
Também se encaixaria na observação de que o cache frio é um fator grande, conforme elaboramos nos comentários. Você está acessando (partes de) tabelas que ninguém tocou há muito tempo ou está executando em um sistema de teste em que o cache ainda não está preenchido?
Caso contrário, você simplesmente não terá RAM suficiente disponível para armazenar em cache a maioria dos dados relevantes em seu banco de dados. Conseqüentemente, o acesso aleatório é muito mais caro que o acesso seqüencial quando os dados residem no cache. Dependendo da situação real, pode ser necessário ajustar para obter melhores planos de consulta.
Um outro fator deve ser mencionado para uma resposta lenta na primeira leitura: bits de dica . Leia detalhes no Wiki do Postgres e esta pergunta relacionada:
Ou a tabela está extremamente inchada ; nesse caso, uma varredura de índice faria sentido e eu me referiria à
CLUSTER
/pg_repack
na minha resposta anterior que você citou. (Ou apenasVACUUM FULL)
E investigue suasVACUUM
configurações. Essas são importantes commany inserts and updates every day
.Dependendo dos
UPDATE
padrões, considere tambémFILLFACTOR
abaixo de 100. Se você atualizar principalmente apenas as linhas recém-adicionadas, defina a menorFILLFACTER
após compactar sua tabela, para que apenas novas páginas mantenham espaço de manobra para atualizações.Esquema
Ajuste ligeiramente a sequência das colunas para salvar 8 bytes por linha (nos 99% dos casos em que
campaign_id
é NULL):Explicação detalhada e links para mais:
Medir:
fonte
Parece-me que você está consultando muitos dados em um grande índice, por isso é lento. Nada notavelmente errado lá.
Se você usa o PostgreSQL 9.3 ou 9.4, pode tentar ver se consegue uma varredura apenas de índice transformando-a em um tipo de índice de cobertura.
O PostgreSQL não possui índices de cobertura verdadeiros nem suporta termos de índice que são apenas valores, que não fazem parte da árvore b, portanto isso é mais lento e mais caro do que com esses recursos. Ainda pode ser uma vitória sobre uma varredura simples de índice se o vácuo for executado com frequência suficiente para manter o mapa de visibilidade atualizado.
Idealmente, o PostgreSQL suportaria campos de dados auxiliares em um índice como no MS-SQL Server ( essa sintaxe NÃO FUNCIONARá no PostgreSQL ):
fonte
random_page_cost
etc). Além disso, para fins de teste, veja apenas seset enable_indexscan = off
e, emset enable_seqscan = off
seguida, a re-execução força uma varredura apenas de índice e, em caso afirmativo, quais são as estimativas de custo da análise de explicação.