Als ich vor circa 15 Jahren SQL lernte, wunderte ich mich über so ulkige SQL-Befehle wie »LISTEN
«
und »UNLISTEN
«
in der PostgreSQL-Dokumentation. Damals konnte ich damit nichts anfangen, und so vergaß ich sie wieder für eine ganze Weile. Vor ein paar Jahren fiel mir dann ein Programm auf, das ausgesprochen viel CPU-Zeit verbrauchte. Es war ein Daemon, der eine Aktion starten sollte, sobald sich in der Datenbank ein bestimmter Zustand einstellte. Dieser trat selten ein, sodass ich erwartete, dass das Programm fast keine CPU-Zeit verbraucht.
In Wirklichkeit sah es aber anders aus: In einem regelmäßigen Zyklus wurde die Datenbank gefragt, ob der gewünschte Zustand erreicht ist. Wenn ja, wurde die Aktion gestartet, wenn nein, der nächste Schleifendurchlauf. So verbrauchte der Zyklus 100 Prozent der CPU-Kapazität. Im Rechenzentrum war es bestimmt recht warm.
In diesem Artikel möchte ich anhand eines kleinen Beispiels ein in ähnlichen Situationen nützliches Feature vorstellen, das im Bewusstsein der Anwender oft ein Schattendasein fristet. Es geht um Notifikationen in PostgreSQL. Als Beispiel dient eine webbasierte Chat-Anwendung mit Apache und Mod-Perl (Abbildung 3). Ich werde dabei auf der in [1] vorgestellten »query
«
-Funktion aufbauen und als Seiteneffekt eine Methode aufzeigen, wie man in einer Mod-Perl-Anwendung zeitnah feststellen kann, wenn der Browser die Verbindung beendet.
Die Beispiele in diesem Artikel wurden mit PostgreSQL 8.4, einem Apache aus der 2.2-Serie und Perl 5.12 erstellt – alles schon gut abgehangene Software. Sie brauchen also nicht die neueste Distribution. Mod-Perl sollte mindestens in Version 2.0.5 vorliegen. Als MPM des HTTPD kommt »prefork
«
zum Einsatz.
Notifikationen sind Meldungen, die von einem Client der Datenbank zum anderen geschickt werden können. Sie sind asynchron in dem Sinn, dass der Empfänger nicht genau weiß, zu welchem Zeitpunkt der Sender die Meldung schickte. Das einzige, was Empfänger und Sender wissen müssen, um über eine Notifikation zu kommunizieren, ist ihr Name. Empfänger müssen sich registrieren und dabei den Namen angeben, den Sender beim Abschicken verwenden.
Ähnlich zu Signalen unter Unix/Linux muss die Datenbank nicht alle gesendeten Notifikationen einzeln zustellen. Angenommen, ein Empfänger hat sich für die Notifikation »A
«
registriert. Nun schickt ein Sender in schneller Folge mehrere Notifikationen »A
«
. Dann stellt die Datenbank mindestens einmal »A
«
zu, aber nicht unbedingt alle. Senden mehrere Datenbankprozesse »A
«
, wird von jedem Prozess mindestens ein »A
«
zugestellt. Seit PostgreSQL Version 9 können Notifikationen neben dem Namen zusätzlichen Inhalt übertragen. Hier stellt die Datenbank sicher, dass von jedem Senderprozess mindestens einmal die Kombination aus Name und Inhalt zugestellt wird.
Notifikationen sind aber auch synchron in dem Sinn, dass sie, egal zu welchem Zeitpunkt in einer Transaktion versendet, erst beim Commit wirklich zugestellt werden. Empfänger erhalten Notifikationen nur außerhalb von Transaktionen. Dies demonstriert das in Listing 1 abgedruckte Skript.
Listing 1
recv0.pl
Nach dem Öffnen der Datenbankverbindung registriert es sich mit dem »LISTEN
«
-Kommando in Zeile 7 für die beiden Notifikationen »foo
«
und »bar
«
. In Zeile 10 wartet das Programm auf Input von der Datenbank. Eigentlich läuft gar keine Abfrage. Wie kann dann Input auftreten? Sobald jedoch ein anderer Datenbankprozess eine Notifikation schickt, wird sie vom Server gesendet. Daher kann also Input ankommen.
Mit der »pg_notifies
«
-Funktion in Zeile 12 werden dann alle bis dahin aufgelaufenen Notifikationen gelesen. Die Funktion liefert eine Notifikation pro Aufruf, deshalb die innere While-Schleife. Die Notifikation selbst ist ein Paar aus dem Namen und dem Absender-PID. Für Postgres ab Version 9 können hier noch Nutzdaten auftauchen.
Das Skript wird auf der Kommandozeile gestartet und liefert zunächst keinen Output. Mit dem zu Postgres gehörenden Kommandozeilenprogramm »psql
«
versenden wir nun ein paar Notifikationen:
chat=> notify foo; notify bar; notify foo; notify foo; notify bax; NOTIFY NOTIFY NOTIFY NOTIFY NOTIFY chat=>
Es werden fünf Benachrichtigungen versandt: einmal »bar
«
, dreimal »foo
«
und einmal »bax
«
. Obwohl es keinen Empfänger für »bax
«
gibt, ist das Versenden kein Fehler. Die Notifikation geht in diesem Fall einfach verloren.
Tue Apr 30 14:12:41 2013: notifications foo 12015 Tue Apr 30 14:12:41 2013: notifications bar 12015 Tue Apr 30 14:12:41 2013: notifications foo 12015 Tue Apr 30 14:12:41 2013: notifications foo 12015
Bemerkenswert hier ist, dass jede Notifikation einzeln zugestellt wird. Jeder »pg_notifies
«
-Aufruf liefert genau eine Nachricht. Dass das kein Widerspruch zu meiner obigen Aussage ist, zeigt das Experiment in Listing 2. Hier werden die Notifikationen in einer Transaktion versandt. Zu Beginn wird zusätzlich die aktuelle Zeit ausgegeben und nach dem Versand fünf Sekunden gewartet.
Listing 2
Test
Der Empfänger gibt jetzt nur noch folgende Zeilen aus:
Tue Apr 30 14:24:33 2013: notifications bar 12015 foo 12015
Die drei Foo-Notifikationen sind zu einer zusammengefasst. Außerdem beträgt die Zeitdifferenz zwischen dem Absenden und dem Empfang deutlich mehr als fünf Sekunden. Das heißt, die Notifikationen werden erst beim »COMMIT
«
der Transaktion wirklich verschickt. Wird die Transaktion abgebrochen (»ROLLBACK
«
), kommen keine Notifikationen an.
Listing 3 demonstriert dies. Zeile 12 startet eine Transaktion und gibt anschließend einen Zeitstempel aus. Nun wartet das Programm maximal zehn Sekunden auf Input von der Datenbank. Im Fall eines Timeouts wird die Transaktion beendet (Zeile 23) und wieder in Zeile 14 gewartet. Kommt Input an, werden die Notifikationen ausgelesen und das Programm beendet.
Listing 3
recv1.pl
Kurz nach dem Start des Programms sende ich mit »psql
«
eine Notifikation und gebe die aktuelle Zeit aus:
chat=> notify foo; select clock_timestamp(); NOTIFY clock_timestamp ------------------------------- 2013-04-30 14:46:18.638144+02
Das Skript gibt folgendes aus:
Tue Apr 30 14:46:16 2013: start transaction Tue Apr 30 14:46:26 2013: finish transaction Tue Apr 30 14:46:26 2013: notifications foo 12015
Der zeitliche Ablauf ist somit folgender:
Transaktionen können lange dauern. In realen Anwendungen ist also die Frage zu stellen, ob eine Datenbankverbindung für normale SQL-Kommandos und Notifikationen gemeinsam benutzt werden kann, oder ob es nicht besser wäre, mit zwei Verbindungen zu arbeiten.