Zum Inhalt springen

Citus-Unterstützung

Einzelheiten zur Patroni-Integration für Citus-Koordinator- und Worker-Gruppen.

Patroni vereinfacht die Bereitstellung von Citus-Clustern mit mehreren Knoten erheblich.


Kurzüberblick (TL;DR)

Es müssen nur wenige einfache Regeln beachtet werden:

  1. Die PostgreSQL-Datenbankerweiterung Citus muss auf allen Knoten verfügbar sein. Die älteste unterstützte Citus-Version ist 10.0. Um alle Vorteile transparenter Switchover und Worker-Neustarts zu nutzen, wird jedoch mindestens Citus 11.2 empfohlen.
  2. Der Cluster-Name (scope) muss auf allen Citus-Knoten identisch sein!
  3. Die Superuser-Zugangsdaten müssen auf dem Koordinator und allen Worker-Knoten identisch sein, und pg_hba.conf sollte den Superuser-Zugriff zwischen allen Knoten erlauben.
  4. Der Zugriff auf die REST API des Koordinators sollte von den Worker-Knoten aus erlaubt sein. Beispielsweise sollten die Zugangsdaten identisch sein, und sofern Client-Zertifikate konfiguriert sind, muss der Koordinator die Zertifikate der Worker-Knoten akzeptieren.
  5. Den folgenden Abschnitt zu patroni.yaml hinzufügen:
citus:
  group: X  # 0 for coordinator and 1, 2, 3, etc for workers
  database: citus  # must be the same on all nodes

Anschließend muss nur noch Patroni gestartet werden; es übernimmt den Rest:

  1. Patroni setzt bootstrap.dcs.synchronous_mode auf quorum , sofern nicht ausdrücklich ein anderer Wert festgelegt ist.
  2. Die Erweiterung citus wird automatisch zu shared_preload_libraries hinzugefügt.
  3. Wenn max_prepared_transactions in der globalen dynamischen Konfiguration nicht ausdrücklich gesetzt ist, setzt Patroni den Wert automatisch auf 2*max_connections.
  4. Der GUC-Wert citus.local_hostname wird von localhost auf den Wert geändert, den Patroni für die Verbindung zur lokalen PostgreSQL-Instanz verwendet. Dieser Wert sollte in manchen Fällen von localhost abweichen, da PostgreSQL dort möglicherweise nicht lauscht.
  5. Die mit citus.database angegebene Datenbank wird automatisch erstellt; anschließend wird CREATE EXTENSION citus ausgeführt.
  6. Die aktuellen Superuser-Zugangsdaten werden zur Tabelle pg_dist_authinfo hinzugefügt, um die Kommunikation zwischen Knoten zu ermöglichen. Diese Daten müssen ebenfalls aktualisiert werden, wenn später username/password/sslcert/sslkey des Superusers geändert werden!
  7. Der Primärserver des Koordinators erkennt die Primärserver der Worker automatisch und fügt sie mit der Funktion citus_add_node() zur Tabelle pg_dist_node hinzu.
  8. Patroni pflegt pg_dist_node auch dann, wenn auf dem Koordinator- oder einem Worker-Cluster ein Failover oder Switchover erfolgt.

patronictl

Koordinator- und Worker-Cluster sind physisch getrennte PostgreSQL-/Patroni-Cluster, die lediglich mithilfe der PostgreSQL-Datenbankerweiterung Citus logisch zusammengefasst werden. Daher lassen sie sich in den meisten Fällen nicht als eine einzige Einheit verwalten.

Im Verhalten von patronictl ergeben sich zwei wesentliche Unterschiede zum üblichen Verhalten, wenn patroni.yaml den Abschnitt citus enthält:

  1. list und topology geben standardmäßig alle Mitglieder des Citus-Verbunds aus (Koordinatoren und Worker). Die neue Spalte Group zeigt an, zu welcher Citus-Gruppe sie gehören.
  2. Für alle Befehle von patronictl gibt es die neue Option --group. Bei einigen Befehlen kann der Standardwert für die Gruppe aus patroni.yaml übernommen werden. Beispielsweise aktiviert patronictl pause den Wartungsmodus standardmäßig für die group, die im Abschnitt citus festgelegt ist. Bei patronictl switchover oder patronictl remove muss die Gruppe hingegen ausdrücklich angegeben werden.

Beispiel einer Ausgabe von patronictl list für einen Citus-Cluster:

postgres@coord1:~$ patronictl list demo
+ Citus cluster: demo ----------+----------------+---------+----+-------------+-----+------------+-----+
| Group | Member  | Host        | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
|     0 | coord1  | 172.27.0.10 | Replica        | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord2  | 172.27.0.6  | Quorum Standby | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord3  | 172.27.0.4  | Leader         | running |  1 |             |     |            |     |
|     1 | work1-1 | 172.27.0.8  | Quorum Standby | running |  1 |   0/31D3198 |   0 |  0/31D3198 |   0 |
|     1 | work1-2 | 172.27.0.2  | Leader         | running |  1 |             |     |            |     |
|     2 | work2-1 | 172.27.0.5  | Quorum Standby | running |  1 |   0/31CDFC0 |   0 |  0/31CDFC0 |   0 |
|     2 | work2-2 | 172.27.0.7  | Leader         | running |  1 |             |     |            |     |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+

Mit der zusätzlichen Option --group ändert sich die Ausgabe wie folgt:

postgres@coord1:~$ patronictl list demo --group 0
+ Citus cluster: demo (group: 0, 7179854923829112860) -+-------------+-----+------------+-----+
| Member | Host        | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+--------+-------------+----------------+---------+----+-------------+-----+------------+-----+
| coord1 | 172.27.0.10 | Replica        | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
| coord2 | 172.27.0.6  | Quorum Standby | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
| coord3 | 172.27.0.4  | Leader         | running |  1 |             |     |            |     |
+--------+-------------+----------------+---------+----+-------------+-----+------------+-----+

postgres@coord1:~$ patronictl list demo --group 1
+ Citus cluster: demo (group: 1, 7179854923881963547) -+-------------+-----+------------+-----+
| Member  | Host       | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------+------------+----------------+---------+----+-------------+-----+------------+-----+
| work1-1 | 172.27.0.8 | Quorum Standby | running |  1 |   0/31D3198 |   0 |  0/31D3198 |   0 |
| work1-2 | 172.27.0.2 | Leader         | running |  1 |             |     |            |     |
+---------+------------+----------------+---------+----+-------------+-----+------------+-----+

Switchover eines Citus-Workers

Bei einem Switchover eines Citus-Worker-Knotens bietet Citus die Möglichkeit, diesen für die Anwendung nahezu transparent durchzuführen. Da sich die Anwendung mit dem Koordinator verbindet, der wiederum Verbindungen zu den Worker-Knoten herstellt, kann Citus den SQL-Datenverkehr auf dem Koordinator für die Shards eines Worker-Knotens pausieren. Der Switchover erfolgt dann, während der Datenverkehr auf dem Koordinator zurückgehalten wird. Der Datenverkehr wird fortgesetzt, sobald ein neuer Primärserver des Workers bereit ist, lesende und schreibende Abfragen anzunehmen.

Beispiel für patronictl switchover auf einem Worker-Cluster:

postgres@coord1:~$ patronictl switchover demo
+ Citus cluster: demo ----------+----------------+---------+----+-------------+-----+------------+-----+
| Group | Member  | Host        | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
|     0 | coord1  | 172.27.0.10 | Replica        | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord2  | 172.27.0.6  | Quorum Standby | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord3  | 172.27.0.4  | Leader         | running |  1 |             |     |            |     |
|     1 | work1-1 | 172.27.0.8  | Leader         | running |  1 |             |     |            |     |
|     1 | work1-2 | 172.27.0.2  | Quorum Standby | running |  1 |   0/31D3198 |   0 |  0/31D3198 |   0 |
|     2 | work2-1 | 172.27.0.5  | Quorum Standby | running |  1 |   0/31CDFC0 |   0 |  0/31CDFC0 |   0 |
|     2 | work2-2 | 172.27.0.7  | Leader         | running |  1 |             |     |            |     |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
Citus group: 2
Primary [work2-2]:
Candidate ['work2-1'] []:
When should the switchover take place (e.g. 2024-08-26T08:02 )  [now]:
Current cluster topology
+ Citus cluster: demo (group: 2, 7179854924063375386) -+-------------+-----+------------+-----+
| Member  | Host       | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------+------------+----------------+---------+----+-------------+-----+------------+-----+
| work2-1 | 172.27.0.5 | Quorum Standby | running |  1 |   0/31CDFC0 |   0 |  0/31CDFC0 |   0 |
| work2-2 | 172.27.0.7 | Leader         | running |  1 |             |     |            |     |
+---------+------------+----------------+---------+----+-------------+-----+------------+-----+
Are you sure you want to switchover cluster demo, demoting current primary work2-2? [y/N]: y
2024-08-26 07:02:40.33003 Successfully switched over to "work2-1"
+ Citus cluster: demo (group: 2, 7179854924063375386) --------+---------+------------+---------+
| Member  | Host       | Role    | State   | TL | Receive LSN |     Lag | Replay LSN |     Lag |
+---------+------------+---------+---------+----+-------------+---------+------------+---------+
| work2-1 | 172.27.0.5 | Leader  | running |  1 |             |         |            |         |
| work2-2 | 172.27.0.7 | Replica | stopped |    |     unknown | unknown |    unknown | unknown |
+---------+------------+---------+---------+----+-------------+---------+------------+---------+

postgres@coord1:~$ patronictl list demo
+ Citus cluster: demo ----------+----------------+---------+----+-------------+-----+------------+-----+
| Group | Member  | Host        | Role           | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
|     0 | coord1  | 172.27.0.10 | Replica        | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord2  | 172.27.0.6  | Quorum Standby | running |  1 |   0/41C0368 |   0 |  0/41C0368 |   0 |
|     0 | coord3  | 172.27.0.4  | Leader         | running |  1 |             |     |            |     |
|     1 | work1-1 | 172.27.0.8  | Leader         | running |  1 |             |     |            |     |
|     1 | work1-2 | 172.27.0.2  | Quorum Standby | running |  1 |   0/31D3198 |   0 |  0/31D3198 |   0 |
|     2 | work2-1 | 172.27.0.5  | Leader         | running |  2 |             |     |            |     |
|     2 | work2-2 | 172.27.0.7  | Quorum Standby | running |  2 |   0/31CDFC0 |   0 |  0/31CDFC0 |   0 |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+

Auf der Koordinatorseite sieht das wie folgt aus:

# The worker primary notifies the coordinator that it is going to execute "pg_ctl stop".
2024-08-26 07:02:38,636 DEBUG: query(BEGIN, ())
2024-08-26 07:02:38,636 DEBUG: query(SELECT pg_catalog.citus_update_node(%s, %s, %s, true, %s), (3, '172.19.0.7-demoted', 5432, 10000))
# From this moment all application traffic on the coordinator to the worker group 2 is paused.

# The old worker primary is assigned as a secondary.
2024-08-26 07:02:40,084 DEBUG: query(SELECT pg_catalog.citus_update_node(%s, %s, %s, true, %s), (7, '172.19.0.7', 5432, 10000))

# The future worker primary notifies the coordinator that it acquired the leader lock in DCS and about to run "pg_ctl promote".
2024-08-26 07:02:40,085 DEBUG: query(SELECT pg_catalog.citus_update_node(%s, %s, %s, true, %s), (3, '172.19.0.5', 5432, 10000))

# The new worker primary just finished promote and notifies coordinator that it is ready to accept read-write traffic.
2024-08-26 07:02:41,485 DEBUG: query(COMMIT, ())
# From this moment the application traffic on the coordinator to the worker group 2 is unblocked.

Sekundärknoten

Ab Patroni v4.0.0 werden auch Citus-Sekundärknoten ohne das Tag noloadbalance in pg_dist_node registriert. Um Sekundärknoten für schreibgeschützte Abfragen zu verwenden, müssen Anwendungen jedoch den GUC citus.use_secondary_nodes ändern.


Ein Blick in den DCS

Der Citus-Cluster (Koordinator und Worker) wird im DCS als Verbund logisch zusammengehörender Patroni-Cluster gespeichert:

/service/batman/              # scope=batman
/service/batman/0/            # citus.group=0, coordinator
/service/batman/0/initialize
/service/batman/0/leader
/service/batman/0/members/
/service/batman/0/members/m1
/service/batman/0/members/m2
/service/batman/1/            # citus.group=1, worker
/service/batman/1/initialize
/service/batman/1/leader
/service/batman/1/members/
/service/batman/1/members/m3
/service/batman/1/members/m4
...

Dieser Ansatz wurde gewählt, weil sich bei den meisten DCS damit der gesamte Citus-Cluster mit einer einzigen rekursiven Leseanfrage abrufen lässt. Nur die Citus-Koordinatorknoten lesen den gesamten Baum, da sie die Worker-Knoten erkennen müssen. Worker-Knoten lesen lediglich den Teilbaum ihrer eigenen Gruppe und können in manchen Fällen auch den Teilbaum der Koordinatorgruppe lesen.


Citus auf Kubernetes

Da Kubernetes keine hierarchischen Strukturen unterstützt, muss die Citus-Gruppe in alle K8s-Objekte aufgenommen werden, die Patroni erstellt:

batman-0-leader  # the leader config map for the coordinator
batman-0-config  # the config map holding initialize, config, and history "keys"
...
batman-1-leader  # the leader config map for worker group 1
batman-1-config
...

Das Namensschema lautet also: ${scope}-${citus.group}-${type}.

Patroni erkennt alle Kubernetes-Objekte über den Label-Selektor . Daher müssen alle Pods mit Patroni&Citus sowie Endpoints/ConfigMaps Labels, die dem gemeinsamen Label-Selektor entsprechen haben, und Patroni muss über die Kubernetes-Einstellungen oder Umgebungsvariablen für ihre Verwendung konfiguriert werden.

Einige Beispiele für die Konfiguration von Patroni über Umgebungsvariablen der Pods:

  1. Für den Koordinator-Cluster
apiVersion: v1
kind: Pod
metadata:
  labels:
    application: patroni
    citus-group: "0"
    citus-type: coordinator
    cluster-name: citusdemo
  name: citusdemo-0-0
  namespace: default
spec:
  containers:
  - env:
    - name: PATRONI_SCOPE
      value: citusdemo
    - name: PATRONI_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    - name: PATRONI_KUBERNETES_POD_IP
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: status.podIP
    - name: PATRONI_KUBERNETES_NAMESPACE
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.namespace
    - name: PATRONI_KUBERNETES_LABELS
      value: '{application: patroni}'
    - name: PATRONI_CITUS_DATABASE
      value: citus
    - name: PATRONI_CITUS_GROUP
      value: "0"
  1. Für den Worker-Cluster der Gruppe 2
apiVersion: v1
kind: Pod
metadata:
  labels:
    application: patroni
    citus-group: "2"
    citus-type: worker
    cluster-name: citusdemo
  name: citusdemo-2-0
  namespace: default
spec:
  containers:
  - env:
    - name: PATRONI_SCOPE
      value: citusdemo
    - name: PATRONI_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    - name: PATRONI_KUBERNETES_POD_IP
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: status.podIP
    - name: PATRONI_KUBERNETES_NAMESPACE
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.namespace
    - name: PATRONI_KUBERNETES_LABELS
      value: '{application: patroni}'
    - name: PATRONI_CITUS_DATABASE
      value: citus
    - name: PATRONI_CITUS_GROUP
      value: "2"

In beiden Beispielen ist das Label citus-group gesetzt. Anhand dieses Labels erkennt Patroni, zu welcher Citus-Gruppe ein Objekt gehört. Zusätzlich gibt es die Umgebungsvariable PATRONI_CITUS_GROUP mit demselben Wert wie das Label citus-group. Wenn Patroni neue Kubernetes-Objekte (ConfigMaps oder Endpoints) erstellt, versieht es diese automatisch mit dem Label citus-group: ${env.PATRONI_CITUS_GROUP}:

apiVersion: v1
kind: ConfigMap
metadata:
  name: citusdemo-0-leader  # Is generated as ${env.PATRONI_SCOPE}-${env.PATRONI_CITUS_GROUP}-leader
  labels:
    application: patroni    # Is set from the ${env.PATRONI_KUBERNETES_LABELS}
    cluster-name: citusdemo # Is automatically set from the ${env.PATRONI_SCOPE}
    citus-group: '0'        # Is automatically set from the ${env.PATRONI_CITUS_GROUP}

Ein vollständiges Beispiel für die Bereitstellung von Patroni auf Kubernetes mit Citus-Unterstützung findet sich im Ordner kubernetes des Patroni-Repositories.

Dabei sind zwei Dateien relevant:

  1. Dockerfile.citus
  2. citus_k8s.yaml

Citus-Upgrades und PostgreSQL-Hauptversions-Upgrades

Zunächst die Dokumentation zum Aktualisieren der Citus-Version lesen. Im Ablauf gibt es eine kleine Änderung: Beim Upgrade muss PostgreSQL mit patronictl restart anstelle von systemctl restart neu gestartet werden.

Ein PostgreSQL-Hauptversions-Upgrade mit Citus ist etwas komplexer. Dafür müssen die Verfahren aus der Citus-Dokumentation zu Hauptversions-Upgrades mit denen aus der Patroni-Dokumentation zu PostgreSQL-Hauptversionsupgrade kombiniert werden. Dabei ist zu beachten, dass ein Citus-Cluster aus mehreren Patroni-Clustern (Koordinator und Worker) besteht, die alle unabhängig voneinander aktualisiert werden müssen.