Johannes Truschnigg - SRE & DevOps


Zur Suche ▼Zum Archiv ▼

Eintrag von 2016-11-11

pglogical: Starthilfe für Logical Replication mit PostgreSQL


Auf der pgconf Europe 2016 habe ich dieses Jahr einen sehr interessanten Vortrag über pglogical besucht - eine Extension für PostgreSQL ab Version 9.4, die "Logical Replication" erlaubt. Logical Replication ist ein naher Verwandter des seit Version 9.0 in Postgres vorhandenen, physischen Replikationsverfahrens: dem Log-Shipping. Während dabei das "Write-Ahead Log" in einer Art endlosem Strom blockweiser Binär-Diffs vom Quell- auf den Zieldatenbankcluster appliziert wird, verwendet pglogical ein Feature namens "Logical Decoding", das aus diesem Datenstrom wieder SQL-Statements errechnen kann.

Dadurch ergibt sich gegenüber dem physischen WAL-Replay eine erhöhte Flexibilität bzw. Granularität für die zu replizierenden Daten: Mit Logical Replication kann man einzelne Relationen bzw. auch nur gewisse Statements replizieren, und ist nicht auf ganze PostgreSQL-Cluster als kleinste Einheit der Replikation festgenagelt. Preis dafür ist eine gesteigerte Komplexität bei der Einrichtung - sowie einige Limitationen bei der Replikation von DDL-Statements, die aber voraussichtlich bis PostgreSQL 10.0 aus der Welt geschafft sein werden.

Logical Replication eignet sich aufgrund dieser Eigenschaften als Ersatzlösung für Trigger-basierte Ansätze zur selektiven Replikation wie etwa Londiste/pgq aus den SkyTools. Vorteil diesen gegenüber ist, dass keine zusätzliche Write-Load am Master anfällt, egal ob eine Mutation zu einer Replikation führt oder nicht - es muss nämlich nicht in einer oder mehreren Extra-Tabellen Buch darüber geführt werden, was an Daten genau wohin zu replizieren ist. Falls die Replikation einmal zwischenzeitlich ausfallen sollte, füllen sich auch nicht am Master durch Trigger befüllte Event-Queue-Tables innerhalb des PostgreSQL-Clusters, sondern es akkumulieren sich WAL-Segmente im Dateisystem des Master-Cluster-Hosts. Den Füllstand dieses Dateisystems sollte man aber ohnehin immer genau im Auge behalten, um Notfalls teildestruktiv (und wahrscheinlich die Verfügbarkeit des Masters sicherstellend) eingreifen zu können.

Beim praktischen Ausprobieren von pglogical bin ich auf einige kleine Stolpersteine gestoßen, die für Kopfkratzen und initiale Ratlosigkeit gesorgt haben. Deswegen will ich hier eine kurze, kommentierte Einführung in Form einer Anleitung bereitstellen, wie man zwei PostgreSQL 9.6-Cluster auf Debian Jessie per Hand passend für pglogical einrichtet, und eine Relation zwischen diesen beiden synchron hält. (Achtung: Sinnvollerweise folgt man dieser Anleitung in genau dieser Form nur auf einem Host, den man nach dem Experiment entsorgen kann! Unter dieser Annahme kümmere ich mich in Folge auch nicht um bspw. das Sicherstellen der Authentizität der GPG-Keys für die Paketmetadaten-Signatur, etc. Weiters sind diese Kommandos - sofern nicht anders angegeben - in einer root-Shell auszuführen.)

Als ersten Schritt müssen wir dazu zwei Repositories als Paketquellen für apt hinzufügen, und dem Schlüsselmaterial für deren Signaturen das Vertrauen des Paketmanagers schenken:

cat <<EOF > /etc/apt/sources.list.d/pgdg.list
deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main
EOF
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc  | apt-key add -

cat <<EOF > /etc/apt/sources.list.d/2ndquadrant.list
deb [arch=amd64] http://packages.2ndquadrant.com/pglogical/apt/ jessie-2ndquadrant main
EOF
wget --quiet -O - http://packages.2ndquadrant.com/pglogical/apt/AA7A6805.asc | apt-key add -

Nachdem dieser Schritt erfolgreich erledigt wurde, installieren wir die damit verfügbar gewordenen Pakete für PostgreSQL 9.6, und die zu dieser Version passende pglogical-Erweiterung von 2ndquadrant. Dann weisen wir das Debian-Tooling für PostgreSQL dazu an, zwei neue Cluster für unser Demo-Setup zu erstellen, die auf für postgres unüblichen Ports Listener errichten sollen:

apt-get update
apt-get install -y postgresql-9.6 postgresql-9.6-pglogical

pg_createcluster 9.6 demoMASTER -p 15432
pg_createcluster 9.6 demoRX0 -p 15433

Für eine grundsätzliche Funktionsdemonstration werden wir die durch pg_createcluster erstellten Cluster-Konfigurationen nicht weiter optimieren - was aber notwendig ist, ist die Aktivierung der pglogical-Extension, die für PostgreSQL 9.6 als Shared Object implementiert ist. Die folgenden Kommandos kümmern sich auch darum, dass beide neu erstellten Cluster diese Konfigurationsänderung erfahren:

cat <<EOF >/etc/postgresql/9.6/demoMASTER/postgresql_pglogical.conf
wal_level = 'logical'
max_worker_processes = 10
max_replication_slots = 10
max_wal_senders = 10
shared_preload_libraries = 'pglogical'
track_commit_timestamp = on
EOF

cp -a /etc/postgresql/9.6/demoMASTER/postgresql_pglogical.conf /etc/postgresql/9.6/demoRX0/postgresql_pglogical.conf

echo "include = 'postgresql_pglogical.conf'" >> /etc/postgresql/9.6/demoMASTER/postgresql.conf
echo "include = 'postgresql_pglogical.conf'" >> /etc/postgresql/9.6/demoRX0/postgresql.conf

Was nun im designierten Master-Cluster noch fehlt, sind die Replikationsprivilegien für eine Verbindung des Replica-Clusters. Wir gehen davon aus, dass beide postgres-Server über das UNIX Domain Socket des Masters miteinander kommunizieren werden, was auf einem gemeinsamen Host sinnvoll ist. Dieses Szenario benötigt einen tauglichen Eintrag in der pg_hba.conf am Master:

echo 'local replication postgres peer' >> /etc/postgresql/9.6/demoMASTER/pg_hba.conf

Nun starten wir die postgresql-Service-Unit, was dazu führt, dass unsere zwei neu erstellten Cluster hochfahren und (zusätzlich zu etwaigen schon laufenden, anderen PostgreSQL-Clustern) benutzbar werden:

systemctl start postgresql

Alle übrigen Arbeitsschritte benötigen nicht mehr root-Privilegien, sondern stattdessen Superuser-Rechte in PostgreSQL. Um diese zu erlangen, wechseln wir unseren Nutzerkontext zu dem des Systembenutzers postgres:

su - postgres

Bevor wir mittels pglogical Relationen replizieren können, müssen gleichnamige Datenbanken in den beiden Clustern existieren. Dies erledigen wir mit einem Aufruf von createdb gegen beide Cluster:

createdb -p 15432 demodb
createdb -p 15433 demodb

Nun gilt es, die pglogical-Extension in diesen frisch erstellten Datenbanken zu installieren, sodass die pglogical-Funktionen verfügbar werden:

psql -p 15432 demodb -c "CREATE EXTENSION pglogical"
psql -p 15433 demodb -c "CREATE EXTENSION pglogical"

Ab nun unterscheiden sich die Aktionen, die wir gegen den Master bzw. den Slave ausführen, maßgeblich. Der Master-Node will für seine Verwendung mit pglogical folgendermaßen initialisiert werden:

psql -p 15432 demodb -c "SELECT pglogical.create_node(node_name := 'demoMASTER', dsn := 'port=15432 dbname=demodb')"

Der Slave braucht im Kontrast dazu über pglogical (und nicht etwa über die recovery.conf, wie bei Log-Shipping/Streaming Replication) den Auftrag, sich auf den WAL-Stream des eben erstellten Master-Nodes zu subscriben:

psql -p 15433 demodb -c "SELECT pglogical.create_node(node_name := 'demoRX0', dsn := 'port=15433 dbname=demodb')"
psql -p 15433 demodb -c "SELECT pglogical.create_subscription(subscription_name := 'demo_rx0_from_master', provider_dsn := 'port=15432 dbname=demodb', synchronize_data := TRUE)"

Ab jetzt sollte der Master in seinen System-Views Informationen über seinen Replikations-Consumer bereitstellen können. Dies prüfen wir mithilfe der folgenden Abfrage:

psql -p 15432 -c "SELECT * FROM pg_stat_replication" | cat

Ist bis hierher nichts schiefgegangen, können wir die Datenbank am Master mit einer Relation füttern, die wir letztendlich replizieren wollen. Hierzu nutzen wir einen Wrapper aus dem Funktionsumfang von pglogical, um das DDL-Statement auch auf etwaige Subscriber-Nodes anzuwenden - so kommt die Tabelle auch auf unserem Slave-Cluster an:

psql -p 15432 demodb <<EOF
SELECT pglogical.replicate_ddl_command('CREATE TABLE public.films (
    code        char(5) CONSTRAINT firstkey PRIMARY KEY,
    title       varchar(40) NOT NULL,
    did         integer NOT NULL,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute
)');
EOF

Momentan haben wir auf Master und Slave also die gleiche, leere Tabelle, deren Schemata ident sind. Man könnte nun annehmen, dass ein INSERT am Master unmittelbar dazu führt, dass der so erzeugte Record gleichlautend am Slave aufscheint, sobald die Replikationslogik diese Transaktion erfolgreich zum Slave übertragen hat. Das ist allerdings etwas voreilig - pglogical kennt das Konzept von Replication Sets, die zum relationenspezifischen Opt-In für die Datensynchronisation dienen. Sozusagen "ab Werk" existiert auch schon ein Replication Set am Master; es heißt "default". Allerdings beinhaltet es noch keine Relationen - das können wir mit dem folgenden Statement nach unserem Wunsch ändern:

psql -p 15432 demodb -c "SELECT pglogical.replication_set_add_all_tables(set_name := 'default', schema_names := ARRAY['public'], synchronize_data := TRUE)"

Da Slaves dem "default"-Replication Set automatisch subscriben, führt dieses Statement dazu, dass ab der nächsten Transaktion Änderungen an der Tabelle am Master zu genau denselben Änderungen an der Tabelle am Slave führen. Über den optionalen Parameter synchronize_data entscheidet man, ob der momentane Ist-Zustand der Relationen im Replication Set auf den Slave übertragen werden soll, oder ob stattdessen nur zukünftige Modifikationen an den Daten den Weg von MASTER nach RX0 finden sollen. Dass das auch tatsächlich funktioniert zeigen wir, indem wir eine Hand voll Records in die Tabelle am Master eintragen, und sie dann am anderen Ende des Replikations-Setups wieder hervorzaubern:

psql -p 15432 demodb -c "INSERT INTO films VALUES ('LOTR1', 'The Fellowship of the Ring', 1, '2001-12-19'::date, 'Fantasy', '2h 58min'::interval)"
psql -p 15432 demodb -c "INSERT INTO films VALUES ('LOTR2', 'The Two Towers', 2, '2002-12-19'::date, 'Adventure', '2h 59min'::interval)"
psql -p 15432 demodb -c "INSERT INTO films VALUES ('LOTR3', 'The Return of the King', 3, '2003-12-17'::date, 'Drama', '3h 21min'::interval)"

psql -p 15433 demodb -c "SELECT * FROM films"

Fertig!

direkter Link ▲

© 2007-2020 Johannes Truschnigg | valid xhmtl & css