Citus 支持
Patroni 使部署 多节点 Citus 集群变得极为简单。
TL;DR
只需遵循以下几条简单规则:
- 所有节点上必须安装 Citus 数据库扩展。最低支持的 Citus 版本为 10.0,但为了充分利用工作节点透明主从切换和重启的特性,建议使用至少 Citus 11.2。
- 集群名称(
scope)在所有 Citus 节点上必须相同! - 超级用户凭据在协调节点和所有工作节点上必须相同,且
pg_hba.conf应允许所有节点之间的超级用户访问。 - 工作节点到协调节点的 REST API 访问必须被允许。例如,凭据应相同,如果配置了客户端证书,工作节点的客户端证书必须被协调节点接受。
- 在
patroni.yaml中添加以下配置节:
citus:
group: X # 0 表示协调节点,工作节点使用 1、2、3 等
database: citus # 所有节点上必须相同
之后只需启动 Patroni,它会处理其余的一切:
- Patroni 会将
bootstrap.dcs.synchronous_mode设置为 法定人数模式,除非已显式设置为其他值。 citus扩展将自动添加到shared_preload_libraries。- 如果全局 动态配置 中未显式设置
max_prepared_transactions,Patroni 将自动将其设置为2*max_connections。 citus.local_hostnameGUC 的值将从localhost调整为 Patroni 用于连接本地 PostgreSQL 实例的值。有时该值与localhost不同,因为 PostgreSQL 可能没有在该地址上监听。citus.database指定的数据库将被自动创建,随后执行CREATE EXTENSION citus。- 当前超级用户 凭据 将被添加到
pg_dist_authinfo表中,以允许跨节点通信。如果之后决定修改超级用户的用户名/密码/sslcert/sslkey,别忘了同步更新此表! - 协调节点主库将自动发现工作节点主库,并使用
citus_add_node()函数将其添加到pg_dist_node表中。 - Patroni 还会在协调节点或工作节点集群发生故障转移/主从切换时,自动维护
pg_dist_node。
patronictl
协调节点和工作节点集群是物理上独立的 PostgreSQL/Patroni 集群,仅通过 Citus 数据库扩展在逻辑上组合在一起。因此,在大多数情况下无法将它们作为单一实体进行管理。
当 patroni.yaml 中包含 citus 配置节时,与通常情况相比,patronictl 的行为存在两个主要差异:
list和topology命令默认输出 Citus formation 的所有成员(协调节点和工作节点),新增的Group列表明它们所属的 Citus 组。- 所有
patronictl命令都引入了新选项--group。对于某些命令,组的默认值可以从patroni.yaml中获取。例如,patronictl pause默认会为citus配置节中设置的group启用维护模式,但对于patronictl switchover或patronictl remove等命令,则必须显式指定 group。
以下是 Citus 集群的 patronictl list 输出示例:
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 | | | | |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
添加 --group 选项后,输出将变为:
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 | | | | |
+---------+------------+----------------+---------+----+-------------+-----+------------+-----+
Citus 工作节点主从切换
当对 Citus 工作节点编排主从切换时,Citus 提供了使切换对应用程序接近透明的能力。由于应用程序连接到协调节点,再由协调节点连接工作节点,因此 Citus 能够在协调节点上 暂停 对某个工作节点组所承载分片的 SQL 流量。主从切换在流量保持在协调节点的同时进行,并在新的工作节点主库准备好接受读写查询后立即恢复。
以下是在工作节点集群上执行 patronictl switchover 的示例:
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 |
+-------+---------+-------------+----------------+---------+----+-------------+-----+------------+-----+
以下是协调节点侧的日志:
# 工作节点主库通知协调节点,它即将执行 "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))
# 从此刻起,协调节点上发往工作节点组 2 的所有应用流量被暂停。
# 旧的工作节点主库被指定为从库。
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))
# 未来的工作节点主库通知协调节点,它已在 DCS 中获取了 leader 锁,即将运行 "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))
# 新的工作节点主库刚完成提升,通知协调节点它已准备好接受读写流量。
2024-08-26 07:02:41,485 DEBUG: query(COMMIT, ())
# 从此刻起,协调节点上发往工作节点组 2 的应用流量被解除阻塞。
从节点(Secondary nodes)
从 Patroni v4.0.0 起,没有 noloadbalance 标签 的 Citus 从节点也会被注册到 pg_dist_node 中。但若要将从节点用于只读查询,应用程序需要修改 citus.use_secondary_nodes GUC。
查看 DCS 结构
Citus 集群(协调节点和工作节点)在 DCS 中以逻辑分组的 Patroni 集群群组方式存储:
/service/batman/ # scope=batman
/service/batman/0/ # citus.group=0,协调节点
/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,工作节点
/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 协调节点需要读取整棵树,因为它们需要发现工作节点。工作节点只读取自身组的子树,某些情况下也可能读取协调节点组的子树。
Kubernetes 上的 Citus
由于 Kubernetes 不支持层级结构,我们必须在 Patroni 创建的所有 K8s 对象中包含 citus group:
batman-0-leader # 协调节点的 leader ConfigMap
batman-0-config # 存储 initialize、config 和 history "键"的 ConfigMap
...
batman-1-leader # 工作节点组 1 的 leader ConfigMap
batman-1-config
...
命名规则为:${scope}-${citus.group}-${type}。
Patroni 使用 标签选择器 来发现所有 Kubernetes 对象,因此带有 Patroni&Citus 的所有 Pod 以及 Endpoints/ConfigMaps 必须具有相同的标签,且必须通过 Kubernetes Kubernetes 配置 或环境变量来配置 Patroni 使用这些标签。
以下是使用 Pod 环境变量配置 Patroni 的两个示例:
- 协调节点集群
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 组工作节点集群
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 或 Endpoints)时,会自动为其添加 citus-group: ${env.PATRONI_CITUS_GROUP} 标签:
apiVersion: v1
kind: ConfigMap
metadata:
name: citusdemo-0-leader # 由 ${env.PATRONI_SCOPE}-${env.PATRONI_CITUS_GROUP}-leader 生成
labels:
application: patroni # 从 ${env.PATRONI_KUBERNETES_LABELS} 中设置
cluster-name: citusdemo # 从 ${env.PATRONI_SCOPE} 自动设置
citus-group: '0' # 从 ${env.PATRONI_CITUS_GROUP} 自动设置
你可以在 Patroni 仓库的 kubernetes 目录中找到支持 Citus 的 Patroni Kubernetes 部署完整示例。
其中两个重要文件:
- Dockerfile.citus
- citus_k8s.yaml
Citus 升级与 PostgreSQL 大版本升级
首先,请阅读 Citus 文档 中关于升级 Citus 版本的内容。流程中有一处小变化:在执行升级时,必须使用 patronictl restart 代替 systemctl restart 来重启 PostgreSQL。
包含 Citus 的 PostgreSQL 大版本升级相对复杂。你需要结合 Citus 文档中关于大版本升级的技术,以及 Patroni 文档中关于 PostgreSQL major upgrade<major_upgrade> 的内容。请记住,Citus 集群由多个 Patroni 集群(协调节点和工作节点)组成,它们都需要独立升级。