Desempenho de índice intermitente PostGIS


8

Eu tenho uma tabela que contém aproximadamente 55 milhões de pontos de dados (point é uma geometria com SRID 4326) e, para minha consulta, preciso associá-la a uma tabela de área (atualmente tem ~ 1800 áreas) que contém uma variedade de diferentes que variam de polígonos grandes ( 2000 km quadrados) a relativamente pequeno (pequeno sendo cerca de 100 km quadrados).

A consulta inicial selecionada pelo usuário reduz os 55 milhões de pontos iniciais para cerca de ~ 300.000 pontos, dependendo do período, etc. Em seguida, a junção é feita e depende da área que eles selecionaram para usar quando a consulta for concluída, normalmente a reduz para ~ 150.000.

O problema que estou tendo é que, algumas vezes, a consulta é interrompida e, em vez de levar os ~ 25 segundos esperados, pode levar até ~ 18 minutos. Neste ponto, é necessário fazer uma ANÁLISE DE VÁCUO e executar algumas consultas antes que ele comece a se comportar novamente. Nenhum dado foi adicionado, atualizado ou removido das tabelas de dados ou áreas neste momento.

Eu brinquei com tudo o que consigo pensar e isso ainda parece continuar acontecendo sem constância também. Tanto a coluna data.point como a coluna area.polygon possuem GIST INDEXES nelas.

Descobri que remover o INDEX da coluna data.point parecia tornar as coisas um pouco mais estáveis, mas fica mais lento ~ 35 segundos normalmente. No entanto, remover um INDEX parece ser uma péssima escolha, pois não deve estar ajudando a não dificultar?

Estou usando o PostgreSQL 9.1.4 com o PostGIS 1.5

Aqui está a consulta que estou executando

    select * FROM data, area WHERE st_intersects (data.point, area.polygon) AND 
(readingdatetime BETWEEN '1948-01-01' AND '2012-11-19') AND datasetid IN(3) AND
 "polysetID" = 1 AND area.id IN(28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11)

EXPLICAR

Nested Loop  (cost=312.28..336.59 rows=5 width=2246) (actual time=1445.973..11557.824 rows=12723 loops=1)
  Join Filter: _st_intersects(data.point, area.polygon)
  ->  Index Scan using "area_polysetID_index" on area  (cost=0.00..20.04 rows=1 width=1949) (actual time=0.017..0.229 rows=35 loops=1)
        Index Cond: ("polysetID" = 1)
        Filter: (id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
  ->  Bitmap Heap Scan on data  (cost=312.28..316.29 rows=1 width=297) (actual time=328.771..329.136 rows=641 loops=35)
        Recheck Cond: ((point && area.polygon) AND (datasetid = 3))"
        Filter: ((readingdatetime >= '1948-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-11-19 00:00:00'::timestamp without time zone))
        ->  BitmapAnd  (cost=312.28..312.28 rows=1 width=0) (actual time=328.472..328.472 rows=0 loops=35)
              ->  Bitmap Index Scan on data_point_index  (cost=0.00..24.47 rows=276 width=0) (actual time=307.115..307.115 rows=1365770 loops=35)
                    Index Cond: (point && area.polygon)
              ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..284.37 rows=12856 width=0) (actual time=1.522..1.522 rows=19486 loops=35)
                    Index Cond: (datasetid = 3)
Total runtime: 11560.879 ms

Minhas tabelas de criação

CREATE TABLE data
(
  id bigserial NOT NULL,
  datasetid integer NOT NULL,
  readingdatetime timestamp without time zone NOT NULL,
  value double precision NOT NULL,
  description character varying(255),
  point geometry,
  CONSTRAINT "DATAPRIMARYKEY" PRIMARY KEY (id ),
  CONSTRAINT enforce_dims_point CHECK (st_ndims(point) = 2),
  CONSTRAINT enforce_geotype_point CHECK (geometrytype(point) = 'POINT'::text OR point IS NULL),
  CONSTRAINT enforce_srid_point CHECK (st_srid(point) = 4326)
);

CREATE INDEX data_datasetid_index ON data USING btree (datasetid);
ALTER TABLE data CLUSTER ON data_datasetid_index;

CREATE INDEX "data_datasetid_readingDatetime_index" ON data USING btree (datasetid , readingdatetime );
CREATE INDEX data_point_index ON data USING gist (point);

CREATE INDEX "data_readingDatetime_index" ON data USING btree (readingdatetime );

CREATE TABLE area
(
  id serial NOT NULL,
  polygon geometry,
  "polysetID" integer NOT NULL,
  CONSTRAINT area_primary_key PRIMARY KEY (id )
)

CREATE INDEX area_polygon_index ON area USING gist (polygon);
CREATE INDEX "area_polysetID_index" ON area USING btree ("polysetID");
ALTER TABLE area CLUSTER ON "area_polysetID_index";

Espero que tudo faça algum sentido, se precisar saber mais alguma coisa, por favor, pergunte.

Um breve resumo é realmente que os ÍNDICES parecem funcionar algumas vezes, mas não outras.

Alguém poderia sugerir algo que eu poderia tentar descobrir o que está acontecendo?

Desde já, obrigado.

EDITAR:

Outro exemplo

select * FROM data, area WHERE st_intersects ( data.point, area.polygon) AND 
(readingdatetime BETWEEN '2009-01-01' AND '2012-01-19') AND datasetid IN(1,3) AND
 "polysetID" = 1 AND area.id IN(28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 

Executar na cópia da tabela com índice de pontos

Nested Loop  (cost=0.00..1153.60 rows=35 width=2246) (actual time=86835.883..803363.979 rows=767 loops=1)
  Join Filter: _st_intersects(data.point, area.polygon)
  ->  Index Scan using "area_polysetID_index" on area  (cost=0.00..20.04 rows=1 width=1949) (actual time=0.021..16.287 rows=35 loops=1)
        Index Cond: ("polysetID" = 1)
        Filter: (id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
  ->  Index Scan using data_point_index on data  (cost=0.00..1133.30 rows=1 width=297) (actual time=17202.126..22952.706 rows=33 loops=35)
        Index Cond: (point && area.polygon)
        Filter: ((readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone) AND (datasetid = ANY ('{1,3}'::integer[])))
Total runtime: 803364.120 ms

Executar na cópia da tabela sem índice de pontos

Nested Loop  (cost=2576.91..284972.54 rows=34 width=2246) (actual time=181.478..235.608 rows=767 loops=1)
  Join Filter: ((data_new2.point && area.polygon) AND _st_intersects(data_new2.point, area.polygon))
  ->  Index Scan using "area_polysetID_index" on area  (cost=0.00..20.04 rows=1 width=1949) (actual time=0.149..0.196 rows=35 loops=1)
        Index Cond: ("polysetID" = 1)
        Filter: (id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
  ->  Bitmap Heap Scan on data_new2  (cost=2576.91..261072.36 rows=90972 width=297) (actual time=4.808..5.599 rows=2247 loops=35)
        Recheck Cond: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
        ->  Bitmap Index Scan on "data_new2_datasetid_readingDatetime_index"  (cost=0.00..2554.16 rows=90972 width=0) (actual time=4.605..4.605 rows=2247 loops=35)
              Index Cond: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
Total runtime: 235.723 ms

Como você pode ver, a consulta é significativamente mais lenta quando o índice de pontos está sendo usado.

EDIT 2 (consulta sugerida por Pauls):

WITH polys AS (
  SELECT * FROM area
  WHERE "polysetID" = 1 AND area.id IN(28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11)
)
SELECT * 
FROM polys JOIN data ON ST_Intersects(data.point, polys.polygon)
WHERE datasetid IN(1,3) 
AND (readingdatetime BETWEEN '2009-01-01' AND '2012-01-19');

EXPLICAR

Nested Loop  (cost=20.04..1155.43 rows=1 width=899) (actual time=16691.374..279065.402 rows=767 loops=1)
  Join Filter: _st_intersects(data.point, polys.polygon)
  CTE polys
    ->  Index Scan using "area_polysetID_index" on area  (cost=0.00..20.04 rows=1 width=1949) (actual time=0.016..0.182 rows=35 loops=1)
          Index Cond: ("polysetID" = 1)
          Filter: (id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
  ->  CTE Scan on polys  (cost=0.00..0.02 rows=1 width=602) (actual time=0.020..0.358 rows=35 loops=1)
  ->  Index Scan using data_point_index on data  (cost=0.00..1135.11 rows=1 width=297) (actual time=6369.327..7973.201 rows=33 loops=35)
        Index Cond: (point && polys.polygon)
        Filter: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
Total runtime: 279065.540 ms

Você fala sobre "a consulta" como se fosse exatamente o mesmo SQL todas as vezes. É isso?
Paul Ramsey

1
Além do que Paulo disse acima, que tal postar os resultados de um EXPLAIN da consulta? postgresql.org/docs/8.1/static/sql-explain.html
Kelso

@PaulRamsey Não, a consulta não é a mesma a cada vez, pois eu disse que depende das entradas que o usuário seleciona. No entanto, eu não atrapalhei minha pergunta com isso, pois não faz diferença o que esses filtros são, mas afeta apenas da maneira que você esperaria se houvesse menos linhas devido a restrições estreitas, que é mais rápido e mais vício versa. Mas ele é afetado da mesma maneira que, algumas vezes, pode começar a correr muito devagar até fazer a VACCUM ANALYZE, como explico acima, independentemente da consulta.
Mark-Davidson #

@ Kelso Desculpe, esqueci de adicionar que eu adicionarei um EXPLAIN muito em breve.
Mark-Davidson #

Adicionaram um EXPLAIN e mais alguns bits.
Mark # # # # # Davidson

Respostas:


4

Forçar efetivamente o planejador a fazer o que você deseja pode ajudar. Nesse caso, sub-define a tabela de polígonos antes de executar a junção espacial com a tabela de pontos. Você pode superar o planejador usando a sintaxe "WITH":

WITH polys AS (
  SELECT * FROM area
  WHERE area.id in IN(28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11)
)
SELECT * 
FROM polys JOIN data ON ST_Intersects(data.point, polys.polygon)
WHERE datasetid IN(3) 
AND (readingdatetime BETWEEN '1948-01-01' AND '2012-11-19');

O problema ao tentar jogar esses jogos é que você está codificando em sua declaração a suposição "minha lista de polígonos será sempre mais seletiva do que minhas outras partes da consulta". O que pode não ser verdadeiro para todas as parametrizações da sua consulta ou para todos os aplicativos de uma consulta específica em um conjunto de dados distribuído heterogeneamente.

Mas pode funcionar.

UPDATE : Isso vai ainda mais longe no caminho perigoso de supor que você conhece a seletividade de suas cláusulas de antemão, desta vez também pegamos a seleção de atributos na tabela de pontos e a fazemos separadamente antes da junção espacial:

WITH polys AS (
  SELECT * FROM area
  WHERE area.id in IN(28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11)
),
WITH points AS (
  SELECT * FROM data
  WHERE datasetid IN(3) 
  AND (readingdatetime BETWEEN '1948-01-01' AND '2012-11-19')
)
SELECT * 
FROM polys JOIN points ON ST_Intersects(points, polys.polygon);

Atualizei minha pergunta com sua consulta sugerida e o resultado EXPLAIN paul. Parece ter um desempenho melhor, mas ainda não é tão bom quanto sem o índice. Eu estou olhando para a consulta e me perguntando se isso tem a ver com o fato de que ela está tentando descobrir se todos os pontos estão nos polígonos antes de diminuir o tempo de leitura e os conjuntos de dados ou estou entendendo mal o EXPLAIN?
Mark-Davidson #

Você pode esclarecer de que tabela o período de leitura e data e o conjunto de dados vêm?
Paul Ramsey

Deixa pra lá, eu vejo isso no DDL. O problema agora está bem claro, eu acho, e a questão da seletividade de junção é muito seletiva. Eu não tinha notado que suas outras cláusulas estão subconjuntos da tabela de pontos. Coloque os filtros não espaciais em uma cláusula WITH também.
Paul Ramsey

Isso parece paul muito promissor depois de empurrar o restante das consultas não espaciais para a cláusula WITH. Estou sem trabalho nos últimos 2 dias, então não tive chance de confirmar 100% que está resolvendo o problema, mas vou recompensá-lo com todos os seus conselhos aqui e nos usuários do postgis lista tem sido muito útil. Irá relatar uma vez que eu tenho certeza.
MarkVidson

2

Observando a explicação para a execução na cópia da tabela com índice de pontos na primeira edição, parece que você está perdendo esse índice na tabela sem índice de pontos:

CREATE INDEX "data_readingDatetime_index" ON data USING btree (readingdatetime );

Você pode confirmar que o índice está lá?

- EDITAR -

Depois de estudar mais a sua pergunta (não é fácil, aliás), tenho as seguintes sugestões a fazer.

  1. solte o índice "data_datasetid_readingDatetime_index", pois você já indexou as duas colunas separadamente. Isso economizará espaço, melhorará o desempenho da pastilha e simplificará o trabalho do planejador de consultas, retirando uma variável da equação
  2. agrupar a tabela novamente com o índice "data_readingDatetime_index". O armazenamento em cluster é mais eficaz com consultas baseadas em intervalo. Parece que você não consulta o conjunto de dados com condições baseadas em intervalo

    ALTER TABLE data CLUSTER ON data_readingDatetime_index;
  3. Execute o armazenamento em cluster real. O comando no item anterior não agrupa sua tabela, apenas expressa seu desejo de que, se a tabela fosse agrupada, você desejasse que ela fosse agrupada nesse índice. Agrupe-o com:

     CLUSTER data;
  4. analise a tabela depois de agrupá-la para que as estatísticas (usadas pelo planejador para decidir qual estratégia escolher) anote o novo layout no disco:

     VACUUM ANALYZE data;

    Agora, como os dados são organizados em relação ao período de leitura e data, o planejador favorece uma estratégia na qual o índice data_readingDatetime_index é usado e, uma vez que sempre que usado, o plano de explicação parece ser o mais rápido, então talvez o desempenho melhore e flutue menos

Como eu disse no comentário à resposta de Paul acima, não pense que o planejador não mudará de estratégia dependendo dos filtros (mesmo que os filtros sejam sempre os mesmos e apenas seus valores mudem).

Há um exemplo no manual de alto desempenho do PostregSQL 9.0, altamente recomendado, no qual alterar uma condição de select ... da tabela t em que v <5 a v <6 alternou o plano de varredura de índice para varredura de tabela completa.


Se você olhar para a terceira explicação, verá "-> Verificação de índice de bitmap em" data_new2_datasetid_readingDatetime_index "(custo = 0.00..2554.16 linhas = 90972 width = 0) (tempo real = 4.605..4.605 linhas = 2247 loops = 35 ) ", que é onde o índice dessa coluna entra em ação quando o índice espacial é retirado da equação.
Paul Ramsey

exatamente o meu ponto. está lá na consulta rápida, não na lenta (a mais lenta sendo a do plano de explicação antes da terceira mencionada). Será que, ao copiar a tabela, o índice se perdeu?
Unicoletti

Não, como observado acima, o planejador está pulando esse índice em favor do espacial, porque o espacial está (incorretamente) relatando uma seletividade muito alta ao planejador. Então existe, não está sendo usado.
Paul Ramsey

@unicoletti Muito obrigado por sua opinião, certamente lhe dará sugestões e informará os resultados em breve. Na verdade, eu tenho o livro de alto desempenho do PostgreSQL 9.0, concordo totalmente, recomendo a leitura, pois sei que preciso ler mais para garantir que eu receba cada pequeno aumento de desempenho possível.
Mark-Davidson #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.