PostGIS Intermittent INDEX Performance

8

Ich habe eine Tabelle mit ungefähr 55 Millionen Datenpunkten (Punkt ist eine Geometrie mit SRID 4326) und für meine Abfrage muss ich diese mit einer Flächentabelle (derzeit ~ 1800 Bereiche) verknüpfen, die eine Vielzahl verschiedener Bereiche enthält, die von großen Polygonen reichen ( 2000 km²) bis ziemlich klein (klein ca. 100 km²).

Durch die vom Benutzer ausgewählte anfängliche Abfrage werden die anfänglichen 55 Millionen Punkte auf etwa 300.000 Punkte eingegrenzt, abhängig vom ausgewählten Datumsbereich usw. Dann ist die Verknüpfung abgeschlossen und hängt davon ab, welchen Bereichssatz sie nach Abschluss der Abfrage ausgewählt haben. Dadurch wird sie normalerweise auf ~ 150.000 eingegrenzt.

Das Problem, das ich habe, ist, dass die Abfrage manchmal nur zum Stillstand kommt und anstatt der erwarteten ~ 25 Sekunden bis zu ~ 18 Minuten dauern kann. Zu diesem Zeitpunkt müssen Sie normalerweise eine VAKUUMANALYSE durchführen und dann einige Abfragen ausführen, bevor sie sich wieder verhalten. Zu diesem Zeitpunkt wurden keine Daten hinzugefügt, aktualisiert oder aus den Daten- oder Bereichstabellen entfernt.

Ich habe mit allem herumgespielt, was mir einfällt, und dies scheint immer noch ohne Konstanz zu geschehen. Sowohl die data.point-Spalte als auch die area.polygon-Spalte enthalten GIST INDEXES.

Ich fand, dass das Entfernen des INDEX aus der Spalte data.point die Dinge etwas stabiler zu machen schien, aber normalerweise ~ 35 Sekunden langsamer ist. Das Entfernen eines INDEX scheint jedoch eine sehr schlechte Wahl zu sein, da es nicht helfen sollte, nicht zu behindern?

Ich verwende PostgreSQL 9.1.4 mit PostGIS 1.5

Hier ist die Abfrage, die ich ausführe

    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)

ERKLÄREN

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

Meine Tabellen erstellen

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";

Hoffe, dass alles einen gewissen Sinn ergibt, wenn Sie etwas anderes wissen müssen, fragen Sie bitte.

Eine kurze Zusammenfassung ist wirklich, dass die INDEXES einige Male zu funktionieren scheinen, andere jedoch nicht.

Könnte jemand etwas vorschlagen, was ich versuchen könnte, um herauszufinden, was passiert?

Danke im Voraus.

BEARBEITEN:

Ein anderes Beispiel

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) 

Führen Sie eine Kopie der Tabelle mit Punktindex aus

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

Führen Sie eine Kopie der Tabelle ohne Punktindex aus

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

Wie Sie sehen, ist die Abfrage erheblich langsamer, wenn der Punktindex verwendet wird.

EDIT 2 (Pauls vorgeschlagene Abfrage):

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

ERKLÄREN

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
quelle
Sie sprechen von "der Abfrage", als ob es jedes Mal genau das gleiche SQL ist. Ist es?
Paul Ramsey
1
Wie wäre es, wenn Sie zusätzlich zu dem, was Paul oben gesagt hat, die Ergebnisse einer EXPLAIN der Abfrage veröffentlichen? postgresql.org/docs/8.1/static/sql-explain.html
Kelso
@PaulRamsey Nein, die Abfrage ist nicht jedes Mal dieselbe, wie gesagt, sie hängt von den Eingaben ab, die der Benutzer auswählt. Allerdings habe ich meine Frage damit nicht enttäuscht, da es keinen Unterschied macht, was diese Filter sind. Nun, es wirkt sich nur so aus, wie Sie es erwarten würden, wenn es aufgrund strenger Einschränkungen weniger Zeilen gibt, ist es schneller und umgekehrt umgekehrt. Aber es wird auf die gleiche Weise beeinflusst, wie es manchmal sehr langsam laufen kann, bis die VACCUM-ANALYSE durchgeführt wird, wie oben erläutert, unabhängig davon, um welche Abfrage es sich handelt.
Mark Davidson
@Kelso Sorry, ich habe vergessen hinzuzufügen, dass ich in Kürze eine EXPLAIN hinzufügen werde.
Mark Davidson
Habe eine EXPLAIN und ein paar andere Bits hinzugefügt.
Mark Davidson

Antworten:

4

Es könnte hilfreich sein, den Planer effektiv zu zwingen, das zu tun, was Sie wollen. In diesem Fall wird die Polygontabelle vor dem Ausführen der räumlichen Verknüpfung mit der Punktetabelle untergeordnet. Möglicherweise können Sie den Planer mithilfe der "WITH" -Syntax überlisten:

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

Das Problem beim Versuch, diese Spiele zu spielen, besteht darin, dass Sie die Annahme "Meine Polygonliste ist immer selektiver als meine anderen Abfrageteile" in Ihre Aussage eintragen. Dies gilt möglicherweise nicht für alle Parametrisierungen Ihrer Abfrage oder für alle Anwendungen einer bestimmten Abfrage über ein heterogen verteiltes Dataset.

Aber es könnte funktionieren.

UPDATE : Dies geht noch weiter, wenn Sie davon ausgehen, dass Sie die Selektivität Ihrer Klauseln im Voraus kennen. Dieses Mal nehmen wir auch die Attributauswahl in der Punktetabelle heraus und führen sie separat vor dem räumlichen Join aus:

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
quelle
Meine Frage wurde mit Ihrer vorgeschlagenen Abfrage und dem EXPLAIN-Ergebnis paul aktualisiert. Scheint besser zu funktionieren, aber immer noch nicht annähernd so gut wie ohne Index. Ich schaue auf die Abfrage und frage mich, ob dies damit zu tun hat, dass versucht wird, herauszufinden, ob sich alle Punkte in den Polygonen befinden, bevor die Lesedatenzeit und die Datensätze eingegrenzt werden, oder verstehe ich die EXPLAIN falsch?
Mark Davidson
Können Sie klären, aus welcher Tabelle Lesedatenzeit und Datensatz-ID stammen?
Paul Ramsey
Egal, ich sehe es in der DDL. Das Problem ist jetzt ziemlich klar, denke ich, und es liegt an der Join-Selektivität, die viel zu selektiv zurückkommt. Ich hatte nicht bemerkt, dass Ihre anderen Klauseln die Punktetabelle unterteilen. Verschieben Sie die nicht-räumlichen Filter ebenfalls in eine WITH-Klausel.
Paul Ramsey
Dies sieht sehr vielversprechend aus, nachdem der Rest der nicht-räumlichen Abfragen in die WITH-Klausel aufgenommen wurde. Ich war in den letzten 2 Tagen krank von der Arbeit, hatte also keine Chance, 100% ig zu bestätigen, dass es das Problem löst, aber ich werde Ihnen das Kopfgeld als all Ihre Ratschläge sowohl hier als auch für die Postgis-Benutzer gewähren Liste war sehr hilfreich. Ich melde mich wieder, sobald ich es sicher weiß.
Mark Davidson
2

Wenn Sie sich die Erklärung für die Kopie der Tabelle mit Punktindex bei der ersten Bearbeitung ansehen, sehen Sie, dass Sie diesen Index für die Tabelle ohne Punktindex vermissen:

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

Können Sie bestätigen, dass der Index vorhanden ist?

- BEARBEITEN -

Nach einigem weiteren Studium Ihrer Frage (übrigens keine einfache) habe ich die folgenden Vorschläge zu machen.

  1. Löschen Sie den Index "data_datasetid_readingDatetime_index", da Sie die beiden Spalten bereits getrennt indiziert haben. Dies spart Platz, verbessert die Einfügeleistung und vereinfacht den Abfrageplanerjob, indem eine Variable aus der Gleichung entfernt wird
  2. Cluster die Tabelle gegen den Index "data_readingDatetime_index". Clustering ist bei bereichsbasierten Abfragen effektiver. Sie scheinen keine Datensatz-ID mit bereichsbezogenen Bedingungen abzufragen

    ALTER TABLE data CLUSTER ON data_readingDatetime_index;
  3. Führen Sie das eigentliche Clustering durch. Der Befehl im vorherigen Element gruppiert Ihre Tabelle nicht, sondern drückt lediglich Ihren Wunsch aus, dass die Tabelle, wenn sie geclustert werden soll, in diesem Index geclustert werden soll. Cluster es mit:

     CLUSTER data;
  4. Analysieren Sie die Tabelle nach dem Clustering, damit die Statistiken (anhand derer der Planer entscheidet, welche Strategie er wählen soll) das neue Layout auf der Festplatte notieren:

     VACUUM ANALYZE data;

    Da die Daten jetzt gegen die Lesezeit organisiert sind, wird der Planer eine Strategie bevorzugen, bei der der Index data_readingDatetime_index verwendet wird. Da der EXPLAIN-Plan bei jeder Verwendung am schnellsten zu sein scheint, verbessert sich die Leistung möglicherweise und schwankt möglicherweise weniger

Denken Sie nicht, dass der Planer die Strategie in Abhängigkeit von den Filtern nicht ändern wird (auch wenn die Filter immer gleich sind und sich nur ihre Werte ändern).

Es gibt ein Beispiel in dem sehr empfohlenen PostregSQL 9.0-Hochleistungsbuch, in dem das Ändern einer Bedingung von select ... von table t, wobei v <5 bis v <6 den Plan vom Index-Scan zum vollständigen Table-Scan umschaltete.

Unicoletti
quelle
Wenn Sie sich seine dritte Erklärung ansehen, sehen Sie "-> Bitmap-Index-Scan bei" data_new2_datasetid_readingDatetime_index "(Kosten = 0,00..2554,16 Zeilen = 90972 Breite = 0) (tatsächliche Zeit = 4,605..4,605 ​​Zeilen = 2247 Schleifen = 35 ) "Hier kommt der Index für diese Spalte tatsächlich ins Spiel, sobald der räumliche Index aus der Gleichung herausgenommen wird.
Paul Ramsey
genau mein Standpunkt. Es ist dort in der schnellen Abfrage, nicht in der langsamen (die langsame ist die im Erklärungsplan vor der dritten, die Sie erwähnen). Könnte es sein, dass beim Kopieren der Tabelle der Index verloren gegangen ist?
Unicoletti
Nein, wie oben erwähnt, überspringt der Planer diesen Index zugunsten des räumlichen, da der räumliche Index (fälschlicherweise) dem Planer eine sehr hohe Selektivität meldet. Es existiert also, es wird nicht verwendet.
Paul Ramsey
@unicoletti Vielen Dank für Ihre Eingabe. Sie werden sicherlich Vorschläge machen und bald über die Ergebnisse berichten. Ich habe tatsächlich das PostgreSQL 9.0 High Performance-Buch. Ich stimme dem Lesen sehr zu. Ich muss es selbst lesen, um sicherzustellen, dass ich jede kleine Leistungssteigerung bekomme, die ich kann.
Mark Davidson