Vor einigen Tagen wurde ich bei einem Gespräch mit Kunden gefragt, welche Möglichkeiten es in PostgreSQL für Change Data Capture (CDC) gibt. Die offensichtliche Antwort war natürlich erstmal: Trigger! – Und so ist es auch bei der zu migrierenden Datenbank aktuell implementiert. Das Nutzen von Triggern kann jedoch einen nicht zu vernachlässigenden Einfluss auf die Performance haben. Trigger sind Datenbank Operationen und werden entweder vor oder nach einem DML (Data Manipulation Language) Statement wie z.B. Insert, Update oder Delete ausgeführt.
Gibt es also eine Alternative für PostgreSQL, um CDC zu ermöglichen?
Ja, und diese lässt sich sogar sehr einfach implementieren. Dafür nutze ich den Transaktionslog (Write Ahead Log / WAL) von PostgreSQL, der jedes ausgeführte DML enthält. Das Verfahren nennt sich Log Based CDC. Als Demo Setup nutze ich einen PostgreSQL v12.3 Docker Container auf meinem Windows 10 Notebook mit WSL2.
Der erste Schritt ist nun den PostgreSQL Container zu definieren. Aus Bequemlichkeit nutze ich docker-compose mit einer minimalen Konfiguration. Es ist dabei wichtig, dass der postgresql.conf Konfigurationsparameter ‘wal_level’ auf den Wert ‘logical’ gesetzt wird. Dadurch enthält der WAL die notwendigen Informationen für logische Replikation.
Meine docker-compose.yml sieht also so aus:
Nun starte ich den Container und lasse mir seine Status anzeigen.
Klasse! Der Container läuft und ich kann mich als nächstes direkt in psql einloggen. Zuerst prüfe ich, ob auch das WAL-Level auf logisch konfiguriert ist.
Hinweis: Der postgresql.conf Parameter ‘max_replication_slots’ muss mindestens den Wert 1 (Standard: 10) haben.
Als Nächstes erzeuge ich eine Tabelle, auf die später das DML abzielt.
Jetzt lege ich einen Replicaton Slot mit dem Namen ‘cdc_slot’ an. Dieser verwendet das ‘test_decoding’ Plugin, welches als Modul mit PostgreSQL verfügbar ist. Der Replication Slot ist dann auch in der Katalogview pg_replication_slots ersichtlich.
Als nächstes führe ich einen Insert auf der ice_cream Tabelle aus und schaue den Output des Replication Slots mit der Funktion pg_logical_slot_peek_changes an.
Der Replication Slot zeigt die Transaktion unseres Inserts.
Was passiert aber nun, wenn ich den Replication Slot erneut abfrage?
Die Insert Transaktion ist immer noch zu sehen. Sie wird also vom Replication Slot bei Nutzung der Funktion pg_logical_slot_peek_changes weiter vorgehalten. Ich “spähen” [engl. peek] also nur in den Replication Slot, ohne den Eintrag dabei zu entfernen.
Um den Transaktionslog aus dem Slot zu konsumieren (nach dem Lesen entfernen), wende ich die Funktion pg_logical_slot_get_changes an. Wiederholt man die Query, erhält man den Eintrag nicht mehr.
Der Replication Slot der PostgreSQL Instanz (= Publisher) hält nun alle DML Statments solange vor, bis diese durch eine andere Anwendung (= Consumer) abgeholt werden. Damit haben wir die Basis für PostgreSQL mit CDC geschaffen. Mit einem entsprechenden Interface, z.B. auf Basis von Rest, könnte nun Kafka als Consumer des Replication Slots genutzt werden und der DML Transaktionslog andersweitig verwendet werden.