ÍNDICE Intermitente PostGIS Rendimiento

8

Tengo una tabla que contiene aproximadamente 55 millones de puntos de datos (el punto es una geometría con SRID 4326) y para mi consulta necesito unir esto a una tabla de área (actualmente tiene ~ 1800 áreas) que contiene una variedad de diferentes que van desde polígonos grandes ( 2000 km cuadrados) a bastante pequeño (pequeño siendo unos 100 km cuadrados).

La consulta inicial seleccionada por el usuario reduce los 55 millones de puntos iniciales a alrededor de ~ 300,000 puntos dependiendo del rango de fechas, etc. que seleccionen. Luego, la unión se realiza y depende del conjunto de áreas que hayan seleccionado para usar una vez que se complete la consulta, esto normalmente lo reduce a ~ 150,000.

El problema que tengo es que algunas veces la consulta simplemente se detiene y, en lugar de tomar los ~ 25 segundos esperados, puede tomar hasta ~ 18 minutos. En este punto, generalmente tiene que hacer un ANÁLISIS DE VACÍO y luego ejecutar algunas consultas antes de que comience a comportarse nuevamente. No se han agregado, actualizado o eliminado datos de las tablas de datos o áreas en este momento.

He jugado con todo lo que puedo pensar y esto todavía parece seguir sucediendo sin constancia. Tanto la columna data.point como la columna area.polygon tienen ÍNDICES GIST en ellas.

Descubrí que eliminar el ÍNDICE de la columna data.point parecía hacer las cosas un poco más estables, pero normalmente es más lento ~ 35 segundos normalmente. Sin embargo, eliminar un ÍNDICE parece ser una muy mala elección, ya que ¿no debería ayudar a no obstaculizar?

Estoy usando PostgreSQL 9.1.4 con PostGIS 1.5

Aquí está la consulta que estoy ejecutando

    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)

EXPLIQUE

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

Mis tablas de creación

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 todo tenga sentido si necesita saber algo más, por favor pregunte.

Un breve resumen es realmente que los ÍNDICES parecen funcionar algunas veces pero no otras.

¿Alguien podría sugerir algo que yo pueda tratar de averiguar qué está pasando?

Gracias por adelantado.

EDITAR:

Otro ejemplo

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) 

Ejecutar en copia de tabla con índice de puntos

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

Ejecutar en copia de tabla sin índice de puntos

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 puede ver, la consulta es significativamente más lenta cuando se utiliza el índice de puntos.

EDITAR 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');

EXPLIQUE

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
Mark Davidson
fuente
Hablas de "la consulta" como si fuera exactamente el mismo SQL cada vez. ¿Lo es?
Paul Ramsey
1
Además de lo que Paul ha dicho anteriormente, ¿qué hay de publicar los resultados de una EXPLICACIÓN de la consulta? postgresql.org/docs/8.1/static/sql-explain.html
Kelso
@PaulRamsey No, la consulta no es la misma cada vez, ya que dije que depende de las entradas que seleccione el usuario. Sin embargo, no empañé mi pregunta con eso, ya que no hace una diferencia en lo que son esos filtros, bueno, solo lo afecta de la manera que esperarías si hay menos filas debido a restricciones apretadas, es más rápido y viceversa. viceversa Pero se ve afectado de la misma manera, ya que algunas veces puede comenzar a funcionar muy lentamente hasta hacer el ANÁLISIS DE VACÍO como expliqué anteriormente, sin importar cuál sea la consulta.
Mark Davidson el
@ Kelso Lo siento, olvidé agregar que agregaré un EXPLICAR en breve.
Mark Davidson el
He agregado un EXPLICAR y algunos otros bits.
Mark Davidson el

Respuestas:

4

Forzar eficazmente al planificador a hacer lo que desea podría ayudar. En este caso, subconfigurando la tabla de polígonos antes de ejecutar la unión espacial con la tabla de puntos. Es posible que pueda burlar al planificador con la sintaxis "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');

El problema al intentar jugar estos juegos es que está codificando en su declaración la suposición "mi lista de polígonos siempre será más selectiva que mis otras porciones de consulta". Lo que podría no ser cierto para todas las parametrizaciones de su consulta, o para todas las aplicaciones de una consulta en particular en un conjunto de datos distribuido heterogéneamente.

Pero podría funcionar.

ACTUALIZACIÓN : Esto va aún más lejos en el camino peligroso de suponer que conoce la selectividad de sus cláusulas de antemano, esta vez también tomamos la selección de atributos en la tabla de puntos y la hacemos por separado antes de la unión 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);
Paul Ramsey
fuente
Actualicé mi pregunta con su consulta sugerida y el resultado EXPLAIN paul. Parece funcionar mejor, pero aún no es tan bueno como sin el índice. Estoy mirando la consulta y me pregunto si tiene que ver con el hecho de que está tratando de averiguar si todos los puntos están en los polígonos antes de reducir el tiempo de lectura y los conjuntos de datos, o ¿estoy malinterpretando el EXPLICAR?
Mark Davidson el
¿Puede aclarar de qué tabla datadatetime y datasetid provienen?
Paul Ramsey
No importa, lo veo en el DDL. El problema ahora es bastante claro, creo, y se debe a que la selectividad de unión vuelve a ser demasiado selectiva. No me había dado cuenta de que tus otras cláusulas están subconjustando la tabla de puntos. Empuje los filtros no espaciales hacia arriba en una cláusula WITH también.
Paul Ramsey
Esto se ve muy prometedor Paul después de empujar el resto de las consultas no espaciales a la cláusula WITH. He estado fuera del trabajo durante los últimos 2 días, así que no he tenido la oportunidad de confirmar al 100% que está resolviendo el problema, pero le otorgaré la recompensa como su consejo tanto aquí como en los usuarios de postgis La lista ha sido muy útil. Informaré una vez que esté seguro.
Mark Davidson el
2

Al observar la explicación para Ejecutar en copia de tabla con índice de puntos en el primer EDITAR, parece que falta este índice en la tabla sin índice de puntos:

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

¿Puedes confirmar que el índice está ahí?

- EDITAR -

Después de estudiar un poco más su pregunta (no es fácil, por cierto) tengo las siguientes sugerencias para hacer.

  1. suelte el índice "data_datasetid_readingDatetime_index" ya que ya ha indexado las dos columnas por separado. Esto le ahorrará espacio, mejorará el rendimiento de inserción y simplificará el trabajo del planificador de consultas al eliminar una variable de la ecuación
  2. agrupe la tabla contra el índice "data_readingDatetime_index". La agrupación es más efectiva con consultas basadas en rango. Parece que no consulta datasetid con condiciones basadas en rango

    ALTER TABLE data CLUSTER ON data_readingDatetime_index;
  3. Realice la agrupación real. El comando del elemento anterior no agrupa su tabla, simplemente expresa su deseo de que si la tabla se agrupara, quisiera que se agrupara en ese índice. Agrúpelo con:

     CLUSTER data;
  4. analice la tabla después de agruparla para que las estadísticas (utilizadas por el planificador para decidir qué estrategia elegir) tengan en cuenta el nuevo diseño en el disco:

     VACUUM ANALYZE data;

    ahora, dado que los datos se organizan en función de la fecha de lectura, el planificador favorecerá una estrategia en la que se use el índice data_readingDatetime_index y, siempre que se use, el plan de explicación parece ser el más rápido, entonces quizás el rendimiento mejorará y fluctuará menos

Como dije en el comentario a la respuesta anterior de Paul, no piense que el planificador no cambiará la estrategia dependiendo de los filtros (incluso si los filtros son siempre los mismos y solo cambian sus valores).

Hay un ejemplo en el libro altamente recomendado PostregSQL 9.0 High Performance donde cambiar una condición de select ... de la tabla t donde v <5 a v <6 cambió el plan de exploración de índice a exploración de tabla completa.

unicoletti
fuente
Si observa su tercera explicación, verá "-> Escaneo de índice de mapa de bits en" data_new2_datasetid_readingDatetime_index "(costo = 0.00..2554.16 filas = 90972 ancho = 0) (tiempo real = 4.605..4.605 filas = 2247 bucles = 35 ) ", que es donde el índice en esa columna realmente entra en juego una vez que el índice espacial se saca de la ecuación.
Paul Ramsey
exactamente mi punto. está allí en la consulta rápida, no en la lenta (la lenta es la que está en el plan de explicación antes de la tercera que mencionas). ¿Podría ser que al copiar la tabla se perdió el índice?
unicoletti
No, como se señaló anteriormente, el planificador está omitiendo ese índice a favor del espacial, porque el espacial informa (incorrectamente) una selectividad muy alta al planificador. Entonces existe, no se está utilizando.
Paul Ramsey
@unicoletti Muchas gracias por su aporte, sin duda le daremos sugerencias e informaremos sobre los resultados pronto. De hecho, tengo el libro PostgreSQL 9.0 High Performance totalmente de acuerdo, recomiendo leer, sé que necesito leerlo más para asegurarme de obtener cada pequeño impulso de rendimiento que pueda.
Mark Davidson el