PostgreSQL Notifications mit Perl

© Maxim Kazmin, 123RF

Ans Licht gebracht

Eine SQL-Datenbank wird oft als passive Datenablage betrachtet, die nur zuständig für die Integrität der Daten ist. PostgreSQL kann aber auch aktiv externe Ereignisse auslösen. Mit Perl lässt sich dies für eigene Zwecke ausnutzen.
Der ADMIN 05/13 wirft einen Blick auf die frei verfügbaren Cloud-Frameworks von Eucalyptus über OpenNebula bis OpenStack. Gute Aussichten für skalierbare ... (mehr)

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.

Abbildung 3: Das Resultat: zwei Chat-Fenster im Browser mit PostgreSQL als Backend.

Was sind Notifikationen?

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

01 use common::sense;
02 use DBI;
03 use IO::Select;
04
05 my $db=DBI->connect('dbi:Pg:dbname=chat', 'ipp',
06                     undef, {RaiseError=>1});
07 $db->do('LISTEN '.$_) for (qw/foo bar/);
08
09 my $sel=IO::Select->new($db->{pg_socket});
10 while ($sel->can_read) {
11   warn localtime().": notifications\n";
12   while (my $note=$db->pg_notifies) {
13     warn "  @$note\n";
14   }
15 }

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

01 chat=> begin;
02 BEGIN
03 chat=> notify foo; notify bar; notify foo; notify foo; notify bax;
04 NOTIFY
05 NOTIFY
06 NOTIFY
07 NOTIFY
08 NOTIFY
09 chat=> select clock_timestamp();
10         clock_timestamp
11 -------------------------------
12  2013-04-30 14:24:16.067244+02
13 (1 row)
14
15 chat=> select pg_sleep(5);
16  pg_sleep
17 ----------
18
19 (1 row)
20
21 chat=> end;
22 COMMIT
23 chat=>

Sammelzustellung

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

01 use common::sense;
02 use DBI;
03 use IO::Select;
04
05 my $db=DBI->connect('dbi:Pg:dbname=chat', 'ipp',
06                     undef, {RaiseError=>1});
07 $db->do('LISTEN '.$_) for (qw/foo bar/);
08
09 my $sel=IO::Select->new($db->{pg_socket});
10
11 $db->do('BEGIN');
12 warn localtime().": start transaction\n";
13 while (1) {
14   if( $sel->can_read(10) ) {
15     warn localtime().": notifications\n";
16     while (my $note=$db->pg_notifies) {
17       warn "  @$note\n";
18     }
19     $db->disconnect;
20     exit 0;
21   } else {
22     warn localtime().": finish transaction\n";
23     $db->do('COMMIT');
24   }
25 }

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:

  • 14:46:16 Transaktion startet,
  • 14:46:18 Notifikation wird gesendet,
  • 14:46:26 Transaktion endet und Notifikation wird empfangen.

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.

Ähnliche Artikel

comments powered by Disqus

Artikel der Woche

Zertifikatsmanagement mit Certmonger

Zertifikate werden dazu verwendet, um Benutzer, Services und Hardware mit der Hilfe eines signierten Schlüssels zu verifizieren. Hierfür existieren Public-Key-Infrastrukturen (PKI). Aber wie gelangen die Zertifikate eigentlich auf das Ziel-Gerät? Der Open-Source-Tipp in diesem Monat beschreibt, wie Sie für diese Aufgabe das Tool certmonger verwenden können. (mehr)
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Linux-Backup

Welche Backup-Lösung setzen Sie für Linux im professionellen Umfeld ein?

  • keine
  • eigene Scripts
  • rsnapshot
  • rdiff-backup
  • Bacula
  • Bareos
  • Borg
  • Duplicity
  • Amanda
  • Burp
  • Clonezilla
  • Acronis
  • Arkeia
  • SEP sesam
  • Veeam
  • Redo Backup
  • Relax-and-Recover
  • andere kommerzielle Software
  • andere Open-Source-Software

Google+

Ausgabe /2017

Microsite