PostgreSQL-Probleme in SaaS-Anwendungen folgen einem überschaubaren Muster. Fast immer geht es um eine von vier Ursachen: fehlende Indizes, zu viele Verbindungen, Abfragen in Schleifen oder Statistiken, die nicht mehr zur Datenlage passen.
Der Reihe nach, weil die Reihenfolge zählt: Wer mit Lesereplikaten anfängt, bevor die Indizes stimmen, verteilt das Problem statt es zu lösen.
Indizes: der Teil, der am meisten bringt
Der häufigste Engpass ist ein fehlender Index auf einer Spalte, nach der ständig gefiltert wird. In mandantenfähigen Systemen ist das fast immer die Mandantenspalte — sie steht in praktisch jeder Abfrage und wird trotzdem oft nicht indiziert, weil sie so selbstverständlich wirkt.
Bei zusammengesetzten Indizes zählt die Reihenfolge der Spalten. Ein Index über Mandant und Datum bedient Abfragen nach Mandant allein sowie nach Mandant und Datum. Umgekehrt bedient ein Index über Datum und Mandant die Abfrage nach Mandant allein nicht.
Wo Regeln zur Zeilensicherheit im Einsatz sind, gilt das doppelt: Die Regel fügt jeder Abfrage eine Bedingung hinzu. Ist die zugehörige Spalte nicht indiziert, wird jede Abfrage im System langsamer, nicht nur eine.
Der umgekehrte Fehler ist ebenfalls verbreitet: zu viele Indizes. Jeder Index verlangsamt Schreibvorgänge und belegt Speicher. Ungenutzte Indizes lassen sich in PostgreSQL über die Systemsichten identifizieren und sollten regelmässig aufgeräumt werden.
Verbindungen: der Engpass, der plötzlich kommt
PostgreSQL behandelt jede Verbindung als eigenen Prozess mit eigenem Speicherbedarf. Die Zahl gleichzeitiger Verbindungen ist deshalb begrenzt, und diese Grenze wird nicht langsam erreicht, sondern schlagartig.
Anwendungen in serverlosen Umgebungen erreichen sie besonders schnell, weil jede Instanz eigene Verbindungen aufbaut. Die Lösung ist eine Verbindungsbündelung zwischen Anwendung und Datenbank.
Dabei ist der Modus entscheidend. Im Transaktionsmodus wird eine Verbindung nach jeder Transaktion wieder freigegeben — effizient, aber unvereinbar mit allem, was Zustand über die Transaktion hinaus in der Verbindung ablegt: vorbereitete Anweisungen, Sitzungsvariablen, Ratschläge zu Sperren.
Genau hier entsteht ein gefährlicher Fehler in mandantenfähigen Systemen: Wird der Mandantenkontext als Sitzungsvariable gesetzt und die Verbindung anschliessend weitergereicht, kann der Kontext beim nächsten Vorgang noch stehen. Kontext gehört deshalb in die Transaktion.
Abfragen in Schleifen
Das Muster ist bekannt und tritt trotzdem in fast jedem gewachsenen System auf: eine Liste laden und anschliessend für jeden Eintrag eine weitere Abfrage stellen. Bei zehn Einträgen fällt es nicht auf, bei tausend schon.
Objektrelationale Abbildungsschichten machen das leicht, weil der Zugriff auf eine Beziehung wie ein Feldzugriff aussieht. Die Gegenmassnahme ist, die benötigten Beziehungen ausdrücklich mitzuladen — und in der Entwicklungsumgebung mitzuprotokollieren, wie viele Abfragen eine Seite auslöst.
Statistiken und Aufräumarbeiten
PostgreSQL wählt seinen Ausführungsplan anhand von Statistiken über die Datenverteilung. Ändert sich diese Verteilung stark — etwa nach einem Import —, kann ein zuvor guter Plan schlecht werden, ohne dass sich an der Abfrage etwas geändert hat.
Der automatische Aufräumprozess hält Statistiken und Speicher in Ordnung. Bei Tabellen mit sehr hoher Änderungsrate reichen die Standardeinstellungen manchmal nicht; dann lohnt es sich, die Einstellungen tabellenweise anzupassen statt global.
Vorberechnete Auswertungen
Auswertungen über grosse Datenmengen gehören nicht in den Anfrageweg. Materialisierte Sichten sind dafür das passende Werkzeug: Sie berechnen einmal und liefern danach schnell.
Der Preis ist Aktualität. Eine materialisierte Sicht ist so aktuell wie ihre letzte Auffrischung, und die Auffrischung selbst kostet. Für Auswertungen, bei denen ein Rückstand von Minuten akzeptabel ist, ist der Handel fast immer gut.
Wie man vorgeht, wenn es langsam ist
Zuerst messen, welche Abfragen tatsächlich Zeit verbrauchen — die Statistikerweiterung von PostgreSQL zeigt das nach Gesamtzeit, nicht nach Einzeldauer. Die Abfrage, die tausendmal je zwanzig Millisekunden braucht, ist meist wichtiger als die eine, die zwei Sekunden dauert.
Danach den Ausführungsplan der Kandidaten ansehen und prüfen, ob ein vollständiger Tabellendurchlauf stattfindet, wo ein Index greifen sollte. Erst wenn Indizes, Verbindungen und Abfragemuster stimmen, lohnt sich die Diskussion über grössere Maschinen oder Replikate.

