Anwendungsperformance bei parallelem Schreiben und commit

Hallo,

ich habe Performance-Probleme mit einer bei uns entwickelten Java-Anwendung. Die Anwendung läuft parallel (ca 80 Prozesse) auf mehreren Nodes eines Computerclusters, alle diese Prozesse berechnen Daten und schreiben diese recht häufig in kleinen Portionen per JDBC auf einen zentralen DB-Server (PostgreSQL 9.3 auf OpenSuSE, 12cores, 16GB RAM und config gemäß dem Artikel Tuning Your PostgreSQL Server daran angepasst, auch an die Anzahl der Verbindungen).
Anfangs ist die Performance der Prozesse gut, aber nach ein paar Stunden Berechnungszeit wird sie schlechter, solange noch alle Prozesse laufen. D.h., die Prozesse brauchen immer länger (anfangs 2 Minuten, am Ende mehr als 30 Minuten). Ich habe die Prozesse remote geprofiled, und sie verbringen ihre Zeit mehr und mehr in den Methoden, die in die DB schreiben (sie machen dann auch kaum noch CPU-Load auf ihren Nodes, da sie anscheinend nur warten). Das Load auf dem DB-Server ist aber ok laut Ganglia (~50% CPU-idle), Platte, Memory und Netzwerk auch im grünen Bereich, und bei dem Server sollte es auch so sein. Das Load auf dem DB-Server ändert sich auch nicht, wenn die Prozesse langsamer werden.

Die Indizes in der DB sind anscheinend passend gesetzt: denn wenn ich die Prozesse abbreche, die Datenbank aber so voll lasse, und dann manuell einen einzelnen Prozess starte, geht es auf einmal wieder schnell. Also braucht das Eintragen anscheinend nicht deshalb lange, weil der Server lange Sachen ohne Index suchen muss, die immer mehr werden, während die Daten dazukommen (denn der Index sowie der Füllstand der DB ändern sich ja durch das Abbrechen der anderen Prozesse auch nicht, es sollte also dann immer noch langsam sein). Bei fehlenden/falschen Indizes sollte der DB-Server außerdem rechnen wie blöde, also CPU-idle sehr nah an 0% haben (und nicht ~50%).

Mir scheint es eher an zu häufigem Commiten in der Anwendung zu liegen: in der Anwendung ist auto-commit für die Connection an und es werden sehr häufig sehr kleine Daten in die DB geschrieben (z.B. viele Einträge in n:m-Auflösungs-Tabellen, die nur 2 IDs pro Eintrag enthalten). Kann das das Problem sein?

Ich habe daher 2 Fragen:

1) Wie sollte ich vorgehen, um das Problem aufzuspüren? (Loggen lange dauernder Queries? Kann ich irgendwie feststellen, ob es am Commiten liegt?)

2) Macht es Sinn, in der Anwendung auto-commit auszustellen für die Verbindung, und nur wenige Male manuell zu commiten? (Momemtan, mit auto-commit an, wird in der Laufzeit von ca 2 Minuten bestimmt über 100 mal auto-committed, denke ich).

Vielen Dank für Vorschläge,

Tim

Schau Dir an, was das Problem ist. Tools wie atop helfen da. Vermutlich (nur eine Vermutung) bremst Dich das schreiben der WAL’s aus. Weniger Commits helfen ganz sicher, Dinge zu beschleunigen, machen das ganze natürlich auch anfälliger. Dort, wo ein Commit nötig ist, sollte es auch kommen. Loggen lang laufender Dinge: definitiv. Dazu gibt es passende Optionen.

Bei einem Kunden von uns kamen z.T. hunderte Inserts von parallelen Connections, jede mit Commit. Das war im atop schön zu sehen. WAL auf SSD gelegt half spürbar.

Ok, danke für die Antwort.

danke für den Hinweis auf atop, ich habe es gerade installiert. Momentan ist der Cluster belegt, sobald ich wieder Rechenzeit habe, werde ich das live ansehen. Loggen langer Queries ist auch aktiviert.

Die Prozesse bei uns verarbeiten recht kleine Eingabedateien (dafür aber > 100.000 von denen), immer eine Datei pro Prozess. Die Daten einer Datei will ich sowieso nur ganz oder gar nicht in der DB haben. Ich commit daher jetzt bei fehlerfreiem Beenden der Applikation (vor allen System.exit(0) calls), und wenn abgebrochen wird, wird ein Rollback versucht vor Abbruch. Bin gespannt, ob das schon reicht.

Aber noch eine Frage: kann es nicht zu Problemen mit Locks in der DB führen, wenn die Prozesse Daten schreiben und zu lange nicht committen? Sollte ich lieber zwischendurch ab und zu commiten? (Bei Fehlern kann ich auch trivial einen Eintrag in der DB entfernen statt ein Rollback zu machen, der Rest wird dann durch Löschweitergabe eh mit entfernt).

Hast vorher auch eine Transaktion gestartet?

Aber noch eine Frage: kann es nicht zu Problemen mit Locks in der DB führen, wenn die Prozesse Daten schreiben und zu lange nicht committen?

Wir sind ja nicht bei MySQL. Bei einem Kunden hab ich schon Transaktionen gesehen, die 14 Tage lang liefen. Das war aber auch ein Programmfehler von denen und sorgte in einer Umgebung mit Streaming Replication dann auch für Probleme. (lang laufende Transaktionen auf dem Slave. Da kann es passieren, daß z.B. das Vacuum auf dem Master Rows entsorgt, die der Slave noch sehen möchte…)

Hm, ja. Sinnloserweise hat der alte Code an einer Stelle, die oft aufgerufen wird, immer erst Auto-commit aus gemacht, dann einen Query gemacht und committed, dann auto-commit wieder an. Also sinnlose Transaktion, denn es gab nur den einen Query innerhalb der Transaktion.

Dies ist jetzt aber, wie gesagt. geändert. Auto-commit wird jetzt am Anfang einmal und für immer aus gemacht, dann werden viele Queries gemacht. Und ganz am Ende wird committed.

Das zu den Locks freut mich zu hören (hatte das tatsächlich gefragt, da ein Kollege was ähnliches parallel mit MySQL macht und dort Probleme mit Locks hatte).

Nach einigen Analysen und dem Loggen langer Queries zeigte sich, dass ein fehlerhafter Query in der Anwendung verantwortlich war. Durch falsche Klammerung im WHERE-Teil wurde leider ein Stringmatching (mit LIKE) nicht nur auf den aktuellen Datensatz, sondern auf allen in der DB ausgeführt. Daher wurde es auch immer länger, je mehr Datensätze in die DB kamen.

Warum der DB-Server dabei nicht mit vollem CPU-Load lief, ist mir allerdings rätselhaft – der Server hat zwar 12 Cores, aber die dürfte pgsql eigentlich alle nutzen können.

Dieser Thread hat sich damit erledigt.

Eine einzelne Abfrage (bzw. Verbindung) verwendet bei Postgres (derzeit) nur einen Thread, daher wird eine Abfrage auch nur einen Core verwenden.