Chapter 7. Performance Tipps

Table of Contents
7.1. Kleine Tabellen mit großen Geometrien
7.1.1. Problembeschreibung
7.1.2. Umgehungslösung
7.2. CLUSTER auf die geometrischen Indizes
7.3. Vermeidung von Dimensionsumrechnungen
7.4. Tunen der Konfiguration
7.4.1. Startup/Inbetriebnahme
7.4.2. Runtime/Laufzeit

7.1. Kleine Tabellen mit großen Geometrien

7.1.1. Problembeschreibung

Aktuelle PostgreSQL Versionen (inklusive 9.6) haben eine Schwäche des Optimizers in Bezug auf TOAST Tabellen. TOAST Tabellen bieten eine Art "Erweiterungsraum", der benutzt wird um große Werte (im Sinne der Datengröße), welche nicht in die üblichen Datenspeicherseiten passen (wie lange Texte, Bilder oder eine komplexe Geometrie mit vielen Stützpunkten) auszulagern, siehe the PostgreSQL Documentation for TOAST für mehr Information).

Das Problem tritt bei Tabellen mit relativ großen Geometrien, aber wenigen Zeilen auf (z.B. eine Tabelle welche die europäischen Ländergrenzen in hoher Auflösung beinhaltet). Dann ist die Tabelle selbst klein, aber sie benützt eine Menge an TOAST Speicherplatz. In unserem Beispiel hat die Tabelle um die 80 Zeilen und nutzt dafür nur 3 Speicherseiten, während die TOAST Tabelle 8225 Speicherseiten benützt.

Stellen Sie sich nun eine Abfrage vor, die den geometrischen Operator && verwendet, um ein Umgebungsrechteck mit nur wenigen Zeilen zu ermitteln. Der Abfrageoptimierer stellt fest, dass die Tabelle nur 3 Speicherseiten und 80 Zeilen aufweist. Er nimmt an, das ein sequentieller Scan bei einer derart kleinen Tabelle wesentlich schneller abläuft als die Verwendung eines Indizes. Und so entscheidet er den GIST Index zu ignorieren. Normalerweise stimmt diese Annahme. Aber in unserem Fall, muss der && Operator die gesamte Geometrie von der Festplatte lesen um den BoundingBox-Vergleich durchführen zu können, wodurch auch alle TOAST-Speicherseiten gelesen werden.

Um zu sehen, ob dieses Problem auftritt, können Sie den "EXPLAIN ANALYZE" Befehl von PostgreSQL anwenden. Mehr Information und die technischen Feinheiten entnehmen Sie bitte dem Thread auf der Postgres Performance Mailing List: http://archives.postgresql.org/pgsql-performance/2005-02/msg00030.php

und einem neueren Thread über PostGIS https://lists.osgeo.org/pipermail/postgis-devel/2017-June/026209.html

7.1.2. Umgehungslösung

Die PostgreSQL Entwickler versuchen das Problem zu lösen, indem sie die Abschätzung der Abfragen TOAST-gewahr machen. Zur Überbrückung zwei Workarounds:

Der erste Workaround besteht darin den Query Planer zu zwingen, den Index zu nutzen. Setzen Sie "SET enable_seqscan TO off;" am Server bevor Sie die Abfrage ausführen. Dies zwingt den Query Planer grundsätzlich dazu sequentielle Scans, wann immer möglich, zu vermeiden. Womit der GIST Index wie üblich verwendet wird. Aber dieser Parameter muss bei jeder Verbindung neu gesetzt werden, und er verursacht das der Query Planer Fehleinschätzungen in anderen Fällen macht. Daher sollte "SET enable_seqscan TO on;" nach der Abfrage ausgeführt werden.

Der zweite Workaround besteht darin, den sequentiellen Scan so schnell zu machen wie der Query Planer annimmt. Dies kann durch eine zusätzliche Spalte, welche die BBOX "zwischenspeichert" und über die abgefragt wird, erreicht werden. In Unserem Beispiel sehen die Befehle dazu folgendermaßen aus:

SELECT AddGeometryColumn('myschema','mytable','bbox','4326','GEOMETRY','2');
UPDATE mytable SET bbox = ST_Envelope(ST_Force2D(the_geom));

Nun ändern Sie bitte Ihre Abfrage so, das der && Operator gegen die bbox anstelle der geom_column benutzt wird:

SELECT geom_column
FROM mytable
WHERE bbox && ST_SetSRID('BOX3D(0 0,1 1)'::box3d,4326);

Selbstverständlich muss man die BBOX synchron halten. Die transparenteste Möglichkeit dies zu erreichen wäre über Trigger. Sie können Ihre Anwendung derart abändern, das die BBOX Spalte aktuell bleibt oder ein UPDATE nach jeder Änderung durchführen.

7.2. CLUSTER auf die geometrischen Indizes

Für Tabelle die hauptsächlich read-only sind und bei denen ein einzelner Index für die Mehrheit der Abfragen verwendet wird, bietet PostgreSQL den CLUSTER Befehl. Dieser Befehl ordnet alle Datenzeilen in derselben Reihenfolge an wie die Kriterien bei der Indexerstellung, was zu zwei Performance Vorteilen führt: Erstens wird für die Index Range Scans die Anzahl der Suchabfragen über die Datentabelle stark reduziert. Zweitens, wenn sich der Arbeitsbereich auf einige kleine Intervale des Index beschränkt ist das Caching effektiver, da die Datenzeilen über weniger data pages verteilt sind. (Sie dürfen sich nun eingeladen fühlen, die Dokumentation über den CLUSTER Befehl in der PostgreSQL Hilfe nachzulesen.)

Die aktuelle PostgreSQL Version erlaubt allerdings kein clustern an Hand von PostGIS GIST Indizes, da GIST Indizes NULL Werte einfach ignorieren. Sie erhalten eine Fehlermeldung wie:

lwgeom=# CLUSTER my_geom_index ON my_table;
ERROR: cannot cluster when index access method does not handle null values
HINT: You may be able to work around this by marking column "the_geom" NOT NULL.

Wie die HINT Meldung mitteilt, kann man diesen Mangel umgehen indem man eine "NOT NULL" Bedingung auf die Tabelle setzt:

lwgeom=# ALTER TABLE my_table ALTER COLUMN the_geom SET not null;
ALTER TABLE

Dies funktioniert natürlich nicht, wenn Sie tatsächlich NULL Werte in Ihrer Geometriespalte benötigen. Außerdem müssen Sie die obere Methode zum Hinzufügen der Bedingung verwenden. Die Verwendung einer CHECK Bedingung wie "ALTER TABLE blubb ADD CHECK (geometry is not null);" wird nicht klappen.

7.3. Vermeidung von Dimensionsumrechnungen

Manchmal kann es vorkommen, das Sie 3D- oder 4D-Daten in Ihrer Tabelle haben, aber immer mit den OpenGIS compliant ST_AsText() oder ST_AsBinary() Funktionen, die lediglich 2D Geometrien ausgeben, zugreifen. Dies geschieht indem intern die ST_Force2D() Funktion aufgerufen wird, welche einen wesentlichen Overhead für große Geometrien aufweist. Um diesen Overhead zu vermeiden kann es praktikabel sein diese zusätzlichen Dimensionen ein für alle mal im Voraus zu löschen:

UPDATE mytable SET the_geom = ST_Force2D(the_geom);
VACUUM FULL ANALYZE mytable;

Beachten Sie bitte, falls Sie die Geometriespalte über AddGeometryColumn() hinzugefügt haben, das dadurch eine Bedingung auf die Dimension der Geometrie gesetzt ist. Um dies zu Überbrücken löschen Sie die Bedingung. Vergessen Sie bitte nicht den Eintrag in die geometry_columns Tabelle zu erneuern und die Bedingung anschließend erneut zu erzeugen.

Bei großen Tabellen kann es vernünftig sein, diese UPDATE in mehrere kleinere Portionen aufzuteilen, indem man das UPDATE mittels WHERE Klausel und eines Primärschlüssels, oder eines anderen passenden Kriteriums, beschränkt und ein einfaches "VACUUM;" zwischen den UPDATEs aufruft. Dies verringert den Bedarf an temporären Festplattenspeicher drastisch. Außerdem, falls die Datenbank gemischte Dimensionen der Geometrie aufweist, kann eine Einschränkung des UPDATES mittels "WHERE dimension(the_geom)>2" das wiederholte Schreiben von Geometrien, welche bereits in 2D sind, vermeiden.

7.4. Tunen der Konfiguration

Die Feinabstimmung von PostGIS ist dem Tunen für jeglichen PostgreSQL workload sehr ähnlich. Die einzige zusätzliche Anmerkung, die Sie im Kopf behalten müssen ist, das Geometrien und Raster groß sind, so das auf den Arbeitsspeicher bezogene Optimierungen im allgemeinen einen größeren Einfluß auf PostGIS haben als andere Arten von PostgreSQL Abfragen.

Für allgemeine Details zur PostgreSQL Optimierung, siehe Tuning your PostgreSQL Server.

Für PostgreSQL 9.4+ kann dies alles auf der Serverebene gesetzt werden, ohne das postgresql.conf oder postgresql.auto.conf angerührt werden müssen, indem man den ALTER SYSTEM.. Befehl nützt.

ALTER SYSTEM SET work_mem = '256MB';
-- dies erzwingt die non-startup Konfiguration für neue Verbindungen
SELECT pg_reload_conf();
-- zeige aktuelle Einstellungen
-- benutzen Sie bitte SHOW ALL um alle Einstellungen anzuzeigen
SHOW work_mem;

Zusätzlich zu diesen Einstellungen verfügt PostGIS über einige eigene Einstellungen welche unter Section 8.22, “Grand Unified Custom Variables (GUCs)” aufgeführt sind.

7.4.1. Startup/Inbetriebnahme

Folgende Einstellungen werden in der postgresql.conf konfiguriert:

constraint_exclusion

  • Default: partition

  • Dies wird im allgemeinen zur Tabellenpartitionierung verwendet. Die Standardeinstellung ist "partition". Dies ist ideal für PostgreSQL 8.4 oder höher, da es den Query Planer veranlasst nur jene Tabellen, die sich in einer vererbten Hierarchie befinden, in Hinblick auf Bedingungen zu analysieren.

shared_buffers

  • Standard: ~128MB in PostgreSQL 9.6

  • Auf ca. 25% bis 40% des verfügbaren RAM setzen. Unter Windows können Sie nicht so hoch ansetzen.

max_worker_processes Diese Einstellung ist erst ab PostgreSQL 9.4+ verfügbar. Ab PostgreSQL 9.6+ hat diese Einstellung eine zusätzliche Bedeutung, da sie die maximale Anzahl von Prozessen bestimmt, die bei parallel ablaufenden Abfragen verwendet werden können.

  • Default: 8

  • Bestimmt die maximale Anzahl von Hintergrundprozessen die das System unterstützen kann. Dieser Parameter kann nur beim Serverstart gesetzt werden.

7.4.2. Runtime/Laufzeit

work_mem (jener Arbeitsspeicher der für Sortieroperationen und komplexe Abfragen benutzt wird)

  • Default: 1-4MB

  • Nach oben setzen für große Datenbanken, komplexe Abfragen und wenn große Mengen an Arbeitsspeicher zur Verfügung stehen.

  • Nach unten setzen bei vielen gleichzeitigen Anwendern oder wenn wenig Arbeitsspeicher zur Verfügung steht

  • Falls sie viel Arbeitsspeicher aber wenig Entwickler zur Verfügung haben:

    SET work_mem TO '256MB';

maintenance_work_mem (wird für VACUUM, CREATE INDEX, etc. genutzt)

  • Default: 16-64MB

  • Ist im allgemeinen zu niedrig gesetzt - bindet I/O, sperrt Objekte während es den Speicher auslagert

  • Es werden 32MB bis 1GB auf Produktionsservern mit viel Hauptspeicher empfohlen, was allerdings von der Anzahl der Anwender abhängt. Falls Sie eine Menge an Arbeitsspeicher und wenige Entwickler haben:

    SET maintenance_work_mem TO '1GB';

max_parallel_workers_per_gather

Diese Enstellung ist erst ab PostgreSQL 9.6+ verfügbar und wirkt sich erst ab PostGIS 2.3+ aus, da nur PostGIS 2.3+ parallel laufende Abfragen unterstützt. Wenn der Parameter auf einen höheren Wert als 0 gestellt wird können manche Abfragen, wie jene die Relationsfunktionen wie ST_Intersects verwenden, mehrere Prozesse benutzen und mehr als doppelt so schnell ablaufen. Falls Sie eine große Anzahl an Prozessoren zur Verfügung haben, sollten Sie diesen Wert auf die Anzahl der Prozessoren setzen. Stellen Sie bitte auch sicher die max_worker_processes auf zumindest die gleiche Anzahl zu erhöhen.

  • Default: 0

  • Setzt die maximale Anzahl von Workern, die durch einen einzelnen Gather Knoten aufgerufen werden können. Parallele Worker werden aus einem Pool von Prozessen, welche von max_worker_processes aufgespannt sind, entnommen. Beachten Sie bitte, das die angeforderte Anzahl von Workern zur Laufzeit nicht tatsächlich zur Verfügung stehen kann. Falls dies auftritt wird der Plan mit weniger Workern als erwartet ausgeführt, was leistungsschwach sein kann. Setzt man den Wert auf 0, was der Standardeinstellung entspricht, wird die parallele Ausführung von Abfragen unterbunden.