Replizieren der ersten Datenbank

In diesem Beispiel werden wir versuchen eine neu angelegte "pgbench"
Datenbank zu replizieren.
Die Grundlagen zur Replikation einer bestehenden Datenbank werden ebenfalls
erläutert wir empfehlen jedoch, Slony-I anhand einer brandneuen,
nicht produktiven Datenbank kennenzulernen.

Die Slony-I Replikationsengine basiert auf Triggern, daher ist es möglich
Datenbanken (oder auch Teile davon) zu replizieren die innerhalb der selben
"postmaster" Instanz laufen.

In diesem Beispiel wird nun die auf localhost (Master) laufende "pgbench"
Datenbank auf die ebenfalls auf localhost laufende Slave "pgbench" Datenbank
repliziert.
Bezüglich der bestehenden PostgreSQL Installation werden einen Reihe von
Annahmen getroffen:

1. "tcpip_socket" ist auf "true" gesetzt in Ihrer postgresql.conf
2. localhost ist auf "trust" gesetzt in der pg_hba.conf

Der REPLICATIONUSER muss ein PostgreSQL Superuser sein. Typischerweise ist dies
"postgres" oder "pgsql".

Zusätzlich sollten folgende Umgebungsvariablen in Ihrer Shell gesetzt werden:

CLUSTERNAME=slony_example
MASTERDBNAME=pgbench
SLAVEDBNAME=pgbenchslave
MASTERHOST=localhost
SLAVEHOST=localhost
REPLICATIONUSER=pgsql
PGBENCHUSER=pgbench

Hier sind ein paar Beispiele um diese Variablen in verschiedenen Shells
zu setzen:

bash/sh:
	export CLUSTERNAME=slony_example
(t)csh:
	setenv CLUSTERNAME slony_example


Erzeugen des "pgbench" Benutzers:

createuser -A -D $PGBENCHUSER

Vorbereiten der Datenbanken:

createdb -O $PGBENCHUSER -h $MASTERHOST $MASTERDBNAME
createdb -O $PGBENCHUSER -h $SLAVEHOST $SLAVEDBNAME

pgbench -i -s 1 -U $PGBENCHUSER -h $MASTERHOST $MASTERDBNAME

Da Slony-I die Existenz von pl/pgSQL in den beiden Datenbanken
voraussetzt, sollte dieses nun installiert werden.
Falls pl/pgSQL bereits früher in der template1 Datenbank installiert wurde
kann dieser Schritt jedoch übersprungen werden.

createlang -h $MASTERHOST plpgsql $MASTERDBNAME

Slony-I kopiert zum jetzigen Zeitpunkt keine Tabellendefinitionen von der
Masterdatenbank, wenn ein Slave hinzugefügt wird.
Daher ist es nötig diese Daten manuell zu importieren.
Im folgenden Beispiel wird pg_dump dafür verwendet.

pg_dump -s -U $REPLICATIONUSER -h $MASTERHOST $MASTERDBNAME | psql -U $REPLICATIONUSER -h $SLAVEHOST $SLAVEDBNAME

Um zu demonstrieren wie ein Slony-I Slave im laufenden Betrieb hinzugefügt
werden kann wird nun "pgbench" gestartet.
Wenn sie die pgbench Applikation im Vordergrund eines eigenen Terminalfensters
ablaufen lassen können Sie es jederzeit stoppen und danach erneut mit anderen
Parametern starten.
Die entsprechenden Umgebungsvariablen müssen natürlich erneut exportiert werden
sodass sie auch in dieser Sitzung verfügbar sind.

Ein typisches Aufruf von "pgbench" könnte so aussehen:

pgbench -s 1 -c 5 -t 1000 -U $PGBENCHUSER -h $MASTERHOST $MASTERDBNAME

Dieser Aufruf wird pgbench mit 5 parallelen Clients, wobei jeder 1000
Transaktionen durchführt, starten.
Dieser Vorgang wird die pgbench Datenbank die auf localhost läuft und den
entsprechenden PGBENCHUSER verwenden. 

Konfiguration der Datenbanken für die Replikation

Das Erzeugen der Konfigurationstabellen, Stored Procedures, Trigger und der
Konfiguration geschieht mit Hilfe des slonik Tools. Es handelt sich hierbei um 
ein spezialisiertes Skriptinghilfsmittel, welches vorallem eine Reihe von
Stored Procedures in den Master- bzw. Slavedatenbanken aufruft.
Das Skript zum Erzeugen der initialen Konfiguration für diese einfache
Master-Slave Installation unserer pgbench Datenbank sieht wie folgt aus:

#!/bin/sh

slonik <<_EOF_
	#--
    # Festlegung des Namespaces den das Replikationssystem verwendet. In unserem
    # Beispiel "slony_example"
	#--
	cluster name = $CLUSTERNAME;

	#--
    # Die "admin conninfo"'s werden von slonik dazu verwendet um der Reihe nach
    # zu jedem Knoten auf jeder Seite des Clusters zu verbinden. Die Syntax
    # entspricht der von PQconnectdb in der C-API.
	# --
	node 1 admin conninfo = 'dbname=$MASTERDBNAME host=$MASTERHOST user=$REPLICATIONUSER';
	node 2 admin conninfo = 'dbname=$SLAVEDBNAME host=$SLAVEHOST user=$REPLICATIONUSER';

	#--
    # Initialisierung des ersten Knotens, die ID dieses Knotens muss 1 sein.
    # Dadurch wird ein Schema _$CLUSTERNAME erzeugt welches alle 
    # Replikationssystem-spezifischen Objekte enthält.
	#--
	init cluster ( id=1, comment = 'Master Node');
 
	#--
    # Da die history Tabelle keine primären Schlüssel oder einen anderen
    # eindeutigen Constraint enthält, der zu Identifizierung einer
    # Tabellenreihe dienen könnte, muss ein entsprechender hinzugefügt werden.
    # Das folgende Kommando fügt eine bigint Spalte mit der Bezeichnung
    # _Slony-I_$CLUSTERNAME_rowID zur Tabelle hinzu.
    # Diese Spalte wird einen Standardwert von
    # nextval('_$CLUSTERNAME.s1_rowid_seq'), sowie UNIQUE und NOT NULL
    # Einschränkungen, besitzen.
    # Alle bereits bestehenden Zeilen werden mit einer Nummer initialisiert. 
	#--
	table add key (node id = 1, fully qualified name = 'public.history');

	#--
    # Slony-I teilt Tabellen in "Sets" ein. Die kleinste Einheit zu der ein
    # Knoten subskribieren kann ist ein Set.
    # Die folgenden Kommandos erzeugen ein Set welches alle 4 pgbench
    # Tabellen beinhaltet. Der Master bzw. die Quelle dieses Sets ist
    # der Knoten 1.
    # Es ist für jede Tabelle die repliziert werden soll, nötig ein
    # entsprechendes "set add table()" zu verwenden.
	#--
	create set (id=1, origin=1, comment='All pgbench tables');
	set add table (set id=1, origin=1, id=1, fully qualified name = 'public.accounts', comment='accounts table');
	set add table (set id=1, origin=1, id=2, fully qualified name = 'public.branches', comment='branches table');
	set add table (set id=1, origin=1, id=3, fully qualified name = 'public.tellers', comment='tellers table');
	set add table (set id=1, origin=1, id=4, fully qualified name = 'public.history', comment='history table', key = serial);

	#--
    # Erzeugung des zweiten Knotens (des Slaves). Weiters folgen die nötigen
    # Informationen wie die beiden Knoten miteinander kommunizieren sollen.
	#--

	store node (id=2, comment = 'Slave node');
	store path (server = 1, client = 2, conninfo='dbname=$MASTERDBNAME host=$MASTERHOST user=$REPLICATIONUSER');
	store path (server = 2, client = 1, conninfo='dbname=$SLAVEDBNAME host=$SLAVEHOST user=$REPLICATIONUSER');
	store listen (origin=1, provider = 1, receiver =2);
	store listen (origin=2, provider = 2, receiver =1);
_EOF_


Sollte pgbench jetzt nicht mehr laufen, sollte es erneut gestartet werden.

Zu diesem Zeitpunkt existieren nun zwei komplett vorbereitete Datenbanken.
Eine davon ist die Masterdatenbank in dem pgbench fleissig auf Daten zugreift
und diese kontinuierlich verändert.
Es ist nun an der Zeit die Replikationsdaemons zu starten.

Der Befehl zum starten der Replikationengine auf dem $MASTERHOST ist:

slon "$CLUSTERNAME dbname=$MASTERDBNAME user=$REPLICATIONUSER host=$MASTERHOST"

Zum Start des Replikationssystems auf dem Knoten 2 (also dem Slave):

slon "$CLUSTERNAME dbname=$SLAVEDBNAME user=$REPLICATIONUSER host=$SLAVEHOST"

Obwohl der "slon" Prozess nun auf beiden Knoten läuft, und beide Instanzen 
eine Reihe von Diagnosezeilen und anderer Ausgaben erzeugen, werden noch
keine Daten repliziert.
Die Informationen die angezeigt werden, dienen einzig zur Syncronisation der
Clusterkonfiguration zwischen den beiden "slon" Prozessen.

Zum tatsächlichen Starten der Replikation der vier pgbench-Tabellen vom
Master mit der Node id 1 zum Slave mit der Node id 2, sollte folgendes
Skript ausgeführt werden.

#!/bin/sh
slonik <<_EOF_
    # ----
    # Festlegung des Namespaces für das Replikationssystem
    # ----
    cluster name = $CLUSTERNAME;

    # ----
    # "admin conninfo"'s werden vom slonik-Programm zur Verbindung
    # zu den Datenbanken der einzelnen Knoten verwendet.
    # Dies entspricht den PQconnectdb Argumenten die für Verbindungen
    # vom Arbeitsplatz des Administrators - wo slonik ausgeführt
    # wird - aus nötig sind.
    # ----
    node 1 admin conninfo = 'dbname=$MASTERDBNAME host=$MASTERHOST user=$REPLICATIONUSER';
    node 2 admin conninfo = 'dbname=$SLAVEDBNAME host=$SLAVEHOST user=$REPLICATIONUSER';

    # ----
    # Knoten 2 subskribiert Set 1
    # ----
    subscribe set ( id = 1, provider = 1, receiver = 2, forward = no);
_EOF_


Wenige Sekunden später wird der auf $SLAVEHOST laufende Replikationsdaemon
beginnen, den aktuellen Inhalt der 4 zu replizierenden Tabellen, zu kopieren.
Während diesem Vorgang wird klarerweise die pgbench Anwendung weiterhin die
Datenbank modifizieren. 
Sobald der initiale Kopiervorgang abgeschlossen ist, wird der
Replikationsdaemon auf dem $SLAVEHOST beginnen "aufzuholen" indem er beginnt
das sich ansammelnde Replikationslog abzuarbeiten.
Dieser Vorgang geschied Schrittweise, jeweils werden 10 Sekunden an
Anwendungsarbeit repliziert.
Abhängig von der Leistungsfähigkeit der beiden involvierten Systeme, der
aktuellen Transaktionslast, der Grösse sowie des Tunings der Datenbanken,
kann dieser Vorgang eine Sache von Minuten, Stunden oder auch einer Ewigkeit
sein. 

Sie haben nun erfolgreich Ihr erstes Master-Slave Replikationssystem
installiert, die beiden Datenbanken enthalten nun identische Daten.
Soweit die Theorie. In der Praxis schadet es nicht zu überprüfen ob die
beiden Datensets wirklich identisch sind.

Das folgende Skript erzeugt sortierte Dumps der beiden Datenbanken und 
vergleicht diese. Stellen Sie sicher das "pgbench" seinen Testlauf beendet
hat und die slon-Instanzen aufgeholt haben bevor Sie dieses Skript ausführen.


#!/bin/sh
echo -n "**** comparing sample1 ... "
psql -U $REPLICATIONUSER -h $MASTERHOST $MASTERDBNAME >dump.tmp.1.$$ <<_EOF_
    select 'accounts:'::text, aid, bid, abalance, filler
        from accounts order by aid;
    select 'branches:'::text, bid, bbalance, filler
        from branches order by bid;
    select 'tellers:'::text, tid, bid, tbalance, filler
        from tellers order by tid;
    select 'history:'::text, tid, bid, aid, delta, mtime, filler,
        "_Slony-I_${CLUSTERNAME}_rowID"
        from history order by "_Slony-I_${CLUSTERNAME}_rowID";
_EOF_
psql -U $REPLICATIONUSER -h $SLAVEHOST $SLAVEDBNAME >dump.tmp.2.$$ <<_EOF_
    select 'accounts:'::text, aid, bid, abalance, filler
        from accounts order by aid;
    select 'branches:'::text, bid, bbalance, filler
        from branches order by bid;
    select 'tellers:'::text, tid, bid, tbalance, filler
        from tellers order by tid;
    select 'history:'::text, tid, bid, aid, delta, mtime, filler,
        "_Slony-I_${CLUSTERNAME}_rowID"
        from history order by "_Slony-I_${CLUSTERNAME}_rowID";
_EOF_

if diff dump.tmp.1.$$ dump.tmp.2.$$ >$CLUSTERNAME.diff ; then
    echo "success - databases are equal."
    rm dump.tmp.?.$$
    rm $CLUSTERNAME.diff
else
    echo "FAILED - see $CLUSTERNAME.diff for database differences"
fi

Für den Fall das dieses Skript "FAILED" zurückliefert, kontaktieren Sie
bitte die Entwickler unter http://slony.info/.
