Otimizando consultas em um intervalo de timestamps (uma coluna)

8

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.

Evan Appleby
fonte
explain (buffers, analyze, verbose) ...pode lançar mais luz.
Craig Ringer
Uma informação essencial está faltando aqui: a definição exata da tabela de traffic. Além disso: por que o segundo EXPLAINmostra uma queda de 42 s para 0,5 s? A primeira execução foi realizada com cache frio?
Erwin Brandstetter
Apenas adicionei a definição da tabela à pergunta. Sim, o período de 42 a 0,5 segundos provavelmente ocorreu devido a um cache frio, mas como existem muitas atualizações, isso provavelmente seria uma ocorrência bastante comum. Acabei de executar o EXPLAIN ANALYZE novamente e desta vez foram necessários 56s. Eu corri mais uma vez e desceu para 0,4s.
Evan Appleby
É seguro assumir que existe uma restrição de PK 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;
precisa
Sim, o ID tem uma restrição de PK e uuid_self, uuid_partner e campaign_id têm restrições de FK. Campaign_id é 99% + NULL e dt_updated é 0% NULL.
precisa saber é o seguinte

Respostas:

3

Duas coisas que são muito estranhas aqui:

  1. 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.

  2. 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_coste / 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_repackna minha resposta anterior que você citou. (Ou apenasVACUUM FULL)E investigue suasVACUUMconfigurações. Essas são importantes commany inserts and updates every day.

Dependendo dos UPDATEpadrões, considere também FILLFACTORabaixo de 100. Se você atualizar principalmente apenas as linhas recém-adicionadas, defina a menor FILLFACTER após compactar sua tabela, para que apenas novas páginas mantenham espaço de manobra para atualizações.

Esquema

campaign_idé 99% + NULL e dt_updatedé 0% NULL.

Ajuste ligeiramente a sequência das colunas para salvar 8 bytes por linha (nos 99% dos casos em que campaign_idé NULL):

CREATE TABLE traffic (
    uuid_self       uuid not null REFERENCES ... ,
    uuid_partner    uuid not null REFERENCES ... ,
    id              serial PRIMARY KEY,
    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      TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
);

Explicação detalhada e links para mais:

Medir:

Erwin Brandstetter
fonte
Obrigado pela sugestão. Atualmente, dependo da aspiração automática integrada configurada pelo Heroku e a tabela de tráfego é aspirada quase todos os dias. Examinarei mais sobre como alterar as estatísticas da tabela e o Fator de preenchimento e usar pg_repack e reportar.
Evan Appleby
2

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.

CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created, clicks, impressions)
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

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 ):

-- This will not work in PostgreSQL (at least 9.5)
-- it's an example of what I wish did work. Don't
-- comment to say it doesn't work.
--
CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created)
INCLUDING (clicks, impressions) -- auxillary data columns
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;
Craig Ringer
fonte
Obrigado pela sugestão. Tentei o índice de cobertura e o banco de dados o desconsiderou e ainda utilizava o outro índice. Você sugeriria a remoção do outro índice e o uso apenas do índice de cobertura (ou, alternativamente, o uso de vários índices de cobertura para cada situação que exija)? Também adicionei o EXPLAIN (ANALISAR, VERBOSO, CUSTOS, TAMPÕES) na pergunta original.
precisa saber é o seguinte
Ímpar. Talvez o planejador não seja inteligente o suficiente para escolher uma varredura apenas de índice, caso veja mais de um agregado, mas eu pensaria que sim. Tente brincar com os parâmetros de custo ( random_page_costetc). Além disso, para fins de teste, veja apenas se set enable_indexscan = offe, em set enable_seqscan = offseguida, 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.
Craig Ringer