Перейти к основному содержимому

Поддержка Citus

примечание

Эта страница переведена при помощи нейросети GigaChat.

Patroni чрезвычайно упрощает развертывание многоузловых кластеров Citus.

TL;DR

Есть всего несколько простых правил, которым необходимо следовать:

  1. Расширение базы данных Citus для PostgreSQL должно быть доступно на всех узлах. Минимальная поддерживаемая версия Citus составляет 10.0, но чтобы воспользоваться всеми преимуществами прозрачных переключений и перезапусков рабочих процессов, рекомендуется использовать хотя бы Citus 11.2.

  2. Имя кластера (scope) должно быть одинаковым для всех узлов Citus!

  3. Учетные данные суперпользователя должны быть одинаковыми на координаторе и всех рабочих узлах, а pg_hba.conf должен разрешать доступ суперпользователю между всеми узлами.

  4. Доступ к REST API должен быть разрешен от рабочих узлов до координатора. Например, учетные данные должны быть одинаковыми и если настроены, клиентские сертификаты с рабочих узлов должны приниматься координатором.

  5. Добавьте следующий раздел в patroni.yaml:

    citus:
    group: X # 0 for coordinator and 1, 2, 3, etc for workers
    database: citus # must be the same on all nodes

После этого нужно запустить Patroni, и он будет обрабатывать остальное:

  1. Расширение citus будет автоматически добавлено в shared_preload_libraries.
  2. Если max_prepared_transactions явно не задано в глобальной динамической конфигурации, Patroni автоматически установит его значение равным 2*max_connections.
  3. Значение GUC citus.local_hostname будет скорректировано с localhost до значения, которое Patroni использует для подключения к локальному экземпляру PostgreSQL. Иногда это значение должно отличаться от localhost, потому что PostgreSQL может не прослушивать его.
  4. citus.database будет автоматически создан после CREATE EXTENSION citus.
  5. Текущие учетные данные суперпользователя будут добавлены в таблицу pg_dist_authinfo, чтобы разрешить межузловое общение. Не забудьте обновить их, если позже решите изменить имя пользователя/пароль/SSL-сертификат/SSL-ключ суперпользователя!
  6. Координатор первичного узла автоматически обнаружит рабочие первичные узлы и добавит их в таблицу с помощью функции pg_dist_node.
  7. Patroni также будет поддерживать pg_dist_node, если произойдет отказ или переключение на координационном узле или кластере рабочих узлов.

patronictl

Кластеры координаторов и рабочих узлов представляют собой физически разные кластеры PostgreSQL/Patroni, которые просто логически объединены с использованием расширения базы данных Citus для PostgreSQL. Поэтому в большинстве случаев невозможно управлять ими как единым целым.

Это приводит к двум основным различиям в поведении patronictl при наличии раздела patroni.yaml по сравнению с обычным:

  1. По умолчанию list и topology выводят все члены группы Citus (координаторы и рабочие). Новая колонка Group указывает, к какой группе Citus они принадлежат.
  2. Для всех команд patronictl вводится новый параметр, называемый --group. Для некоторых команд значение по умолчанию для группы может быть взято из patroni.yaml. Например, patronictl pause по умолчанию включит режим обслуживания для group, который установлен в разделе citus, но, например, для patronictl switchover или patronictl remove группа должна быть указана явно.

Пример вывода команды patronictl list для кластера Citus:

postgres@coord1:~$ patronictl list demo
+ Citus cluster: demo ----------+--------------+---------+----+-----------+
| Group | Member | Host | Role | State | TL | Lag in MB |
+-------+---------+-------------+--------------+---------+----+-----------+
| 0 | coord1 | {IP-Address} | Replica | running | 1 | 0 |
| 0 | coord2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 0 | coord3 | {IP-Address} | Leader | running | 1 | |
| 1 | work1-1 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 1 | work1-2 | {IP-Address} | Leader | running | 1 | |
| 2 | work2-1 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 2 | work2-2 | {IP-Address} | Leader | running | 1 | |
+-------+---------+-------------+--------------+---------+----+-----------+

Если добавить опцию --group, то вывод изменится на:

postgres@coord1:~$ patronictl list demo --group 0
+ Citus cluster: demo (group: 0, 7179854923829112860) -----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-------------+--------------+---------+----+-----------+
| coord1 | {IP-Address} | Replica | running | 1 | 0 |
| coord2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| coord3 | {IP-Address} | Leader | running | 1 | |
+--------+-------------+--------------+---------+----+-----------+

postgres@coord1:~$ patronictl list demo --group 1
+ Citus cluster: demo (group: 1, 7179854923881963547) -----------+
| Member | Host | Role | State | TL | Lag in MB |
+---------+------------+--------------+---------+----+-----------+
| work1-1 | {IP-Address} | Sync Standby | running | 1 | 0 |
| work1-2 | {IP-Address} | Leader | running | 1 | |
+---------+------------+--------------+---------+----+-----------+

Переключение рабочих процессов Citus

Когда для рабочего узла Citus организуется переключение, Citus предоставляет возможность сделать это переключение почти незаметным для приложения. Поскольку приложение подключается к координатору, который, в свою очередь, подключается к рабочим узлам, то с помощью Citus можно приостановить трафик SQL на координаторе для фрагментов, размещенных на рабочем узле. Затем происходит переключение при сохранении трафика на координаторе и возобновляется сразу же после того, как новый основной рабочий узел готов принимать запросы чтения-записи.

Пример patronictl switchover в кластере рабочих узлов:

postgres@coord1:~$ patronictl switchover demo
+ Citus cluster: demo ----------+--------------+---------+----+-----------+
| Group | Member | Host | Role | State | TL | Lag in MB |
+-------+---------+-------------+--------------+---------+----+-----------+
| 0 | coord1 | {IP-Address} | Replica | running | 1 | 0 |
| 0 | coord2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 0 | coord3 | {IP-Address} | Leader | running | 1 | |
| 1 | work1-1 | {IP-Address} | Leader | running | 1 | |
| 1 | work1-2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 2 | work2-1 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 2 | work2-2 | {IP-Address} | Leader | running | 1 | |
+-------+---------+-------------+--------------+---------+----+-----------+
Citus group: 2
Primary [work2-2]:
Candidate ['work2-1'] []:
When should the switchover take place (e.g. 2022-12-22T08:02 ) [now]:
Current cluster topology
+ Citus cluster: demo (group: 2, 7179854924063375386) -----------+
| Member | Host | Role | State | TL | Lag in MB |
+---------+------------+--------------+---------+----+-----------+
| work2-1 | {IP-Address} | Sync Standby | running | 1 | 0 |
| work2-2 | {IP-Address} | Leader | running | 1 | |
+---------+------------+--------------+---------+----+-----------+
Are you sure you want to switchover cluster demo, demoting current primary work2-2? [y/N]: y
2022-12-22 07:02:40.33003 Successfully switched over to "work2-1"
+ Citus cluster: demo (group: 2, 7179854924063375386) ------+
| Member | Host | Role | State | TL | Lag in MB |
+---------+------------+---------+---------+----+-----------+
| work2-1 | {IP-Address} | Leader | running | 1 | |
| work2-2 | {IP-Address} | Replica | stopped | | unknown |
+---------+------------+---------+---------+----+-----------+

postgres@coord1:~$ patronictl list demo
+ Citus cluster: demo ----------+--------------+---------+----+-----------+
| Group | Member | Host | Role | State | TL | Lag in MB |
+-------+---------+-------------+--------------+---------+----+-----------+
| 0 | coord1 | {IP-Address} | Replica | running | 1 | 0 |
| 0 | coord2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 0 | coord3 | {IP-Address} | Leader | running | 1 | |
| 1 | work1-1 | {IP-Address} | Leader | running | 1 | |
| 1 | work1-2 | {IP-Address} | Sync Standby | running | 1 | 0 |
| 2 | work2-1 | {IP-Address} | Leader | running | 2 | |
| 2 | work2-2 | {IP-Address} | Sync Standby | running | 2 | 0 |
+-------+---------+-------------+--------------+---------+----+-----------+

И вот как это выглядит со стороны координатора:

# The worker primary notifies the coordinator that it is going to execute "pg_ctl stop".
2022-12-22 07:02:38,636 DEBUG: query("BEGIN")
2022-12-22 07:02:38,636 DEBUG: query("SELECT pg_catalog.citus_update_node(3, '{IP-Address}-demoted', 5432, true, 10000)")
# From this moment all application traffic on the coordinator to the worker group 2 is paused.

# The future worker primary notifies the coordinator that it acquired the leader lock in DCS and about to run "pg_ctl promote".
2022-12-22 07:02:40,085 DEBUG: query("SELECT pg_catalog.citus_update_node(3, '{IP-Address}', 5432)")

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

Внутри DCS

Кластер Citus (координатор и рабочие узлы) хранится в DCS как парк кластеров Patroni, логически объединенных вместе:

/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
...

Такой подход был выбран потому, что для большинства DCS становится возможным получить весь кластер Citus с помощью одного рекурсивного запроса на чтение. Только узлы-координаторы Citus читают все дерево, поскольку они должны обнаруживать рабочие узлы. Рабочие узлы читают только поддерево для своей собственной группы, а в некоторых случаях они могут читать поддерево группы координаторов.

Citus на Kubernetes

Поскольку Kubernetes не поддерживает иерархические структуры, пришлось включить группу citus во все объекты K8s, которые создает Patroni:

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
...

То есть шаблон именования следующий: ${scope}-${citus.group}-${type}.

Все объекты Kubernetes обнаруживаются Patroni с помощью селектора меток, поэтому все Pod с Patroni/Citus и Endpoints/ConfigMaps должны иметь аналогичные метки, а Patroni должен быть настроен на их использование с помощью настроек Kubernetes или переменных окружения.

Несколько примеров настройки Patroni с использованием переменных среды Pod:

  1. Для кластера координатора:

    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"
  2. Для рабочего кластера из группы 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"

Как можно заметить, оба примера имеют установленную метку citus-group. Эта метка позволяет Patroni идентифицировать объект как принадлежащий к определенной группе Citus. В дополнение к этому, существует также переменная окружения PATRONI_CITUS_GROUP, которая имеет то же значение, что и метка citus-group. Когда Patroni создает новые объекты Kubernetes ConfigMaps или конечные точки, он автоматически помещает на них метку 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}

Полный пример развертывания Patroni на Kubernetes с поддержкой Citus находится в папке Kubernetes репозитория Patroni. Там есть два важных файла:

  1. Dockerfile.citus;
  2. citus_k8s.yaml.

Обновления Citus и мажорные обновления PostgreSQL

Сначала прочитайте об обновлении версии Citus в документации. Есть одно незначительное изменение в процессе. При выполнении обновления необходимо использовать patronictl restart вместо systemctl restart для перезагрузки PostgreSQL.

Основное обновление PostgreSQL с Citus немного сложнее. Придется комбинировать методы, используемые в документации Citus по основным обновлениям и документации Patroni по основному обновлению PostgreSQL. Имейте в виду, что кластер Citus состоит из множества кластеров Patroni (координатор и рабочие), и все они должны быть обновлены независимо.