跳转到主要内容

PostgreSQL集群部署

从 Pigsty v1.5.1 标签恢复的历史文档。
从 Pigsty v1.5.1 标签恢复的历史文档。

本文介绍使用Pigsty部署PostgreSQL集群的几种不同方式:PGSQL相关剧本配置请参考相关文档。

  • 身份参数:介绍定义标准PostgreSQL高可用集群所需的身份参数。
  • 单机部署:定义一个单实例的PostgreSQL集群
  • 主从集群:定义一个一主一从的标准可用性集群。
  • 同步从库:定义一个同步复制,RPO = 0 的高一致性集群。
  • 法定人数同步提交:定义数据一致性更高的集群:多数从库成功方返回提交。
  • 离线从库:用于单独承载OLAP分析,ETL,交互式个人查询的专用实例
  • 备份集群:制作现有集群的实时在线克隆,用于异地灾备或延迟从库。
  • 延迟从库:用于应对误删表删库等软件/人为故障,比PITR更快。
  • 级联复制:用于搭建一个集群内的级联复制,针对大量从库场景(20+),降低主库复制压力。
  • Citus集群部署:部署Citus分布式数据库集群
  • MatrixDB集群部署:部署Greenplum7/PostgreSQL12兼容的时序数据仓库。

身份参数

核心身份参数是定义 PostgreSQL 数据库集群时必须提供的信息,包括:

名称 属性 说明 例子
pg_cluster 必选,集群级别 集群名 pg-test
pg_role 必选,实例级别 实例角色 primary, replica
pg_seq 必选,实例级别 实例序号 1, 2, 3,...

身份参数的内容遵循 实体命名规则 。其中 pg_clusterpg_rolepg_seq 属于核心身份参数,是定义数据库集群所需的最小必须参数集,核心身份参数必须显式指定,不可忽略。

  • pg_cluster 标识了集群的名称,在集群层面进行配置,作为集群资源的顶层命名空间。

  • pg_role标识了实例在集群中扮演的角色,在实例层面进行配置,可选值包括:

    • primary:集群中的唯一主库,集群领导者,提供写入服务。
    • replica:集群中的普通从库,承接常规生产只读流量。
    • offline:集群中的离线从库,承接ETL/SAGA/个人用户/交互式/分析型查询。
    • standby:集群中的同步从库,采用同步复制,没有复制延迟(保留)。
    • delayed:集群中的延迟从库,显式指定复制延迟,用于执行回溯查询与数据抢救(保留)。
  • pg_seq 用于在集群内标识实例,通常采用从0或1开始递增的整数,一旦分配不再更改。

其他身份参数

  • pg_shard 用于标识集群所属的上层 分片集簇,只有当集群是水平分片集簇的一员时需要设置。

  • pg_sindex 用于标识集群的分片集簇编号,只有当集群是水平分片集簇的一员时需要设置。

  • pg_instance衍生身份参数,用于唯一标识一个数据库实例,其构成规则为

    {{ pg_cluster }}-{{ pg_seq }}。 因为pg_seq是集群内唯一的,因此该标识符全局唯一。

水平分片集簇

pg_shardpg_sindex 用于定义特殊的分片数据库集簇,是可选的身份参数,目前为Citus与Greenplum保留。

假设用户有一个水平分片的 分片数据库集簇(Shard) ,名称为test。这个集簇由四个独立的集群组成:pg-test1, pg-test2pg-test3pg-test-4。则用户可以将 pg_shard: test 的身份绑定至每一个数据库集群,将pg_sindex: 1|2|3|4 分别绑定至每一个数据库集群上。如下所示:

pg-test1:
  vars: {pg_cluster: pg-test1, pg_shard: test, pg_sindex: 1}
  hosts: {10.10.10.10: {pg_seq: 1, pg_role: primary}}
pg-test2:
  vars: {pg_cluster: pg-test1, pg_shard: test, pg_sindex: 2}
  hosts: {10.10.10.11: {pg_seq: 1, pg_role: primary}}
pg-test3:
  vars: {pg_cluster: pg-test1, pg_shard: test, pg_sindex: 3}
  hosts: {10.10.10.12: {pg_seq: 1, pg_role: primary}}
pg-test4:
  vars: {pg_cluster: pg-test1, pg_shard: test, pg_sindex: 4}
  hosts: {10.10.10.13: {pg_seq: 1, pg_role: primary}}

通过这样的定义,您可以方便地从 PGSQL Shard 监控面板中,观察到这四个水平分片集群的横向指标对比。同样的功能对于 Citus 与 MatrixDB集群同样有效。

单机部署

让我们从最简单的案例开始,在单个节点上部署单实例PostgreSQL。

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-test

使用以下命令,在 10.10.10.11 节点上创建一个单主的数据库实例。

bin/createpg pg-test

单实例数据库无法应对硬件故障,建议在生产使用时,最少使用一主一从的配置。

主从集群

复制可以极大高数据库系统可靠性,是应对硬件故障的最佳手段,在生产环境中强烈建议至少使用一主一从的配置。

Pigsty原生支持设置主从复制,例如,声明一个典型的一主一从高可用数据库集群,可以使用:

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
  vars:
    pg_cluster: pg-test

使用 bin/createpg pg-test,即可创建出该集群来。如果您已经在第一步 单机部署中完成了10.10.10.11的部署,那么也可以使用 bin/createpg 10.10.10.12,进行集群扩容,为集群添加一台从库。

如果主库出现故障,或者我们希望将从库提升为新主库,可以使用 pg 命令:

pg switchover pg-test     # 手工执行Switchover(原主库可用)
pg switchover pg-test     # 手工执行Failover (原主库不可用)

请注意,自动故障切换需要第三方进行仲裁,在Pigsty中,部署于管理节点上的DCS提供了此仲裁服务。

三节点标准HA

如果您的整套环境只有两个节点,且没有使用外部DCS进行仲裁,则无法进行安全可靠的自动故障切换,当故障发生时,您需要手工介入,人工仲裁。任何真正有意义的高可用方案,在没有特殊硬件(如心跳线等Fencing硬件)支持下,至少需要整个环境中有三个节点。因为高可用所依赖的仲裁者(DCS)本身的高可用至少需要三个节点。

如果您的整套环境中有三个节点,则可以使用 pigsty-dcs3.yml 中的样例,构建一个3元节点 x 3实例PG集群的基础高可用单元。在此部署下, 三个管理节点上部署有Consul Server,任意一个节点故障,整个集群都可以继续正常工作。

在生产环境中,您可以使用此三节点集群作为整个集群的管控核心,管理更多的数据库集群。在已有3节点DCS的仲裁者的情况下,您可以部署大量1主1从的基本高可用PGSQL集群,这些集群可以自动进行故障切换。

children:
  meta:   # meta nodes are defined in this special group "meta"
    vars:
      pg_cluster: pg-meta        # define a cluster pg-meta on 3 meta nodes
      meta_node: true            # mark this group as meta nodes
      ansible_group_priority: 99 # overwrite with the highest priority
    hosts:
      10.10.10.10: { pg_seq: 1, pg_role: primary }
      10.10.10.11: { pg_seq: 2, pg_role: replica , nginx_enabled: false }
      10.10.10.12: { pg_seq: 3, pg_role: replica , nginx_enabled: false, pg_offline_query: true }
vars:
  dcs_servers:            # dcs server dict in name:ip format
    meta-1: 10.10.10.10   # you could use existing dcs cluster
    meta-2: 10.10.10.11   # host which have their IP listed here will be init as server
    meta-3: 10.10.10.12   # 3 or 5 dcs nodes are recommended for production environment

同步从库

CAP定理指出:可用性与一致性两者相互抵触,用户必须根据自己的需求进行权衡。 高可用是一方面,而另一面则是高一致,Pigsty允许您创建高一致性的集群,确保出现故障切换时数据不丢,乃至于整个集群保持实时同步一致。

正常情况下,PostgreSQL的复制延迟在几十KB/10ms的量级,对于常规业务而言可以近似忽略不计。重要的是,当主库出现故障时,尚未完成复制的数据会丢失!当您在处理非常关键与精密的业务查询时(例如和钱打交道),复制延迟可能会成为一个问题。此外,或者在主库写入后,立刻向从库查询刚才的写入(read-your-write),也会对复制延迟非常敏感。

为了解决此类问题,需要用到同步从库。 一种简单的配置同步从库的方式是使用 pg_conf = crit 模板,该模板会自动启用同步复制与校验和,适用于和钱有关的,追求一致性的场景。

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: replica }
  vars:
    pg_cluster: pg-test
    pg_conf: crit.yml

或者,您可以在集群创建完毕后,通过在元节点上执行 pg edit-config <cluster.name> ,编辑集群配置文件,修改参数synchronous_mode的值为true并应用即可。

$ pg edit-config pg-test
---
+++
-synchronous_mode: false
+synchronous_mode: true
 synchronous_mode_strict: false

Apply these changes? [y/N]: y

对于启用同步提交的集群,您可以在参考配置文件,在集群中额外配置 standby 服务,提供与主库完全一致的无延迟读取服务。

- name: standby
  src_ip: "*"
  src_port: 5435
  dst_port: pgbouncer
  check_method: http
  check_port: patroni
  check_url: /sync
  check_code: 200
  selector: "[]"
  selector_backup: "[? pg_role == `primary`]"
警告

使用同步提交时,强烈建议集群至少有3个实例,否则唯一的从库故障将立即导致主库不可用。

在PG中启用同步提交,默认会有一个从库实例被选为同步从库,而其他的实例会仍然会使用异步提交模式,以降低事务延迟,提高性能。如果您需要在整个集群范围内获得更强的一致性,可以使用法定人数同步提交

法定人数同步提交

在默认情况下,同步复制会从所有候选从库 挑选一个实例,作为同步从库,任何主库事务只有当复制到从库并Flush至磁盘上时,方视作成功提交并返回。 如果我们期望更高的数据持久化保证,例如,在一个一主三从的四实例集群中,至少有两个从库成功刷盘后才确认提交,则可以使用法定人数提交。

使用法定人数提交时,需要修改 PostgreSQL 中 synchronous_standby_names 参数的值,并配套修改Patroni中 synchronous_node_count 的值。假设三个从库分别为 pg-test-2, pg-test-3, pg-test-4 ,那么应当配置:

  • synchronous_standby_names = ANY 2 (pg-test-2, pg-test-3, pg-test-4)
  • synchronous_node_count : 2
pg-test:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary } # pg-test-1
    10.10.10.11: { pg_seq: 2, pg_role: replica } # pg-test-2
    10.10.10.12: { pg_seq: 3, pg_role: replica } # pg-test-3
    10.10.10.13: { pg_seq: 4, pg_role: replica } # pg-test-4
  vars:
    pg_cluster: pg-test

执行pg edit-config pg-test,并修改配置如下:

$ pg edit-config pg-test
---
+++
@@ -82,10 +82,12 @@
     work_mem: 4MB
+    synchronous_standby_names: 'ANY 2 (pg-test-2, pg-test-3, pg-test-4)'

-synchronous_mode: false
+synchronous_mode: true
+synchronous_node_count: 2
 synchronous_mode_strict: false

Apply these changes? [y/N]: y

应用后,即可看到配置生效,出现两个Sync Standby,当集群出现Failover或扩缩容时,请相应调整这些参数以免服务不可用。

+ Cluster: pg-test (7080814403632534854) +---------+----+-----------+-----------------+
| Member    | Host        | Role         | State   | TL | Lag in MB | Tags            |
+-----------+-------------+--------------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.10 | Leader       | running |  1 |           | clonefrom: true |
| pg-test-2 | 10.10.10.11 | Sync Standby | running |  1 |         0 | clonefrom: true |
| pg-test-3 | 10.10.10.12 | Sync Standby | running |  1 |         0 | clonefrom: true |
| pg-test-4 | 10.10.10.13 | Replica      | running |  1 |         0 | clonefrom: true |
+-----------+-------------+--------------+---------+----+-----------+-----------------+

离线从库

当您的在线业务请求负载水位很大时,将数据分析/ETL/个人交互式查询放置在专用的离线只读从库上是一个更为合适的选择。

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 2, pg_role: offline } # 定义一个新的Offline实例
  vars:
    pg_cluster: pg-test

使用 bin/createpg pg-test,即可创建出该集群来。如果您已经完成了第一步 单机部署与第二步 主从集群,那么可以使用 bin/createpg 10.10.10.13,进行集群扩容,向集群中添加一台离线从库实例。

离线从库默认不承载 replica 服务,只有当所有 replica 服务中的实例均不可用时,离线实例才会用于紧急承载只读流量。如果您只有一主一从,或者干脆只有一个主库,没有专用的离线实例,可以通过为该实例设置 pg_offline_query 标记,该实例仍然扮演原来的角色,但同时也承载 offline 服务,用作 准离线实例

备份集群

您可以使用 Standby Cluster 的方式,制作现有集群的克隆,使用这种方式,您可以从现有数据库平滑迁移至Pigsty集群中。

创建 Standby Cluster 的方式无比简单,您只需要确保备份集群的主库上配置有合适的 pg_upstream 参数,即可自动从原始上游拉取备份。

# pg-test是原始数据库
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-test
    pg_version: 14


# pg-test2将作为pg-test1的Standby Cluster
pg-test2:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11 } # 实际角色为 Standby Leader
    10.10.10.13: { pg_seq: 2, pg_role: replica }
  vars:
    pg_cluster: pg-test2
    pg_version: 14          # 制作Standby Cluster时,数据库大版本必须保持一致!
bin/createpg pg-test     # 创建原始集群
bin/createpg pg-test2    # 创建备份集群

提升备份集群

当您想要将整个备份集群提升为一个独立运作的集群时,编辑新集群的Patroni配置文件,移除所有standby_cluster配置,备份集群中的Standby Leader会被提升为独立的主库。

pg edit-config pg-test2  # 移除 standby_cluster 配置定义并应用

移除下列配置:整个standby_cluster定义部分。

-standby_cluster:
-  create_replica_methods:
-  - basebackup
-  host: 10.10.10.11
-  port: 5432

修改备份集群上游复制源

当源集群发生Failover主库发生变化时,您需要调整备份集群的复制源。执行pg edit-config <cluster>,并修改standby_cluster中的源地址为新主库,应用即可生效。这里需要注意,从源集群的从库进行复制是可行的,源集群发生Failover并不会影响备份集群的复制。但新集群在只读从库上无法创建复制槽,可能出现相关报错,并存在潜在的复制中断风险,建议及时调整备份集群的上游复制源。

 standby_cluster:
   create_replica_methods:
   - basebackup
-  host: 10.10.10.13
+  host: 10.10.10.12
   port: 5432

修改 standby_cluster.host 中复制上游的IP地址,应用即可生效(无需重启,Reload即可)。

延迟从库

高可用与主从复制可以解决机器硬件故障带来的问题,但无法解决软件Bug与人为操作导致的故障,例如:误删库删表。误删数据通常需要用到冷备份,但另一种更优雅高效快速的方式是事先准备一个延迟从库。

您可以使用 备份集群 的功能创建延时从库,例如,现在您希望为pg-test 集群指定一个延时从库:pg-testdelay,该集群是pg-test1小时前的状态。因此如果出现了误删数据,您可以立即从延时从库中获取并回灌入原始集群中。

# pg-test是原始数据库
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: primary }
  vars:
    pg_cluster: pg-test
    pg_users: [ { name: test , password: test , pgbouncer: true , roles: [ dbrole_admin ] , comment: test user } ]
    pg_databases: [ { name: test , extensions: [ { name: postgis, schema: public } ] } ]

# pg-testdelay 将作为 pg-test 库的延时从库
pg-testdelay:
  hosts:
    10.10.10.13: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11 } # 实际角色为 Standby Leader
  vars:
    pg_cluster: pg-testdelay

创建完毕后,在元节点使用 pg edit-config pg-testdelay编辑延时集群的Patroni配置文件,修改 standby_cluster.recovery_min_apply_delay 为你期待的值,例如1h,应用即可。

 standby_cluster:
   create_replica_methods:
   - basebackup
   host: 10.10.10.11
   port: 5432
+  recovery_min_apply_delay: 1h

级连复制

在创建集群时,如果为集群中的某个从库指定 pg_upstream 参数(指定为集群中另一个从库),那么该实例将尝试从该指定从库构建逻辑复制。

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica } # 尝试从2号从库而非主库复制
    10.10.10.13: { pg_seq: 2, pg_role: replica, pg_upstream: 10.10.10.12 }
  vars:
    pg_cluster: pg-test

Citus集群部署

Citus是一个PostgreSQL生态的分布式扩展插件,默认情况下Pigsty安装Citus,但不启用。 pigsty-citus.yml 提供了一个部署Citus集群的配置文件案例。为了启用Citus,您需要修改以下参数:

  • max_prepared_transaction: 修改为一个大于max_connections的值,例如800。
  • pg_libs:必须包含citus,并放置在最前的位置。
  • 您需要在业务数据库中包含 citus 扩展插件(但您也可以事后手工通过CREATE EXTENSION自行安装)
Citus集群样例配置
#----------------------------------#
# cluster: citus coordinator
#----------------------------------#
pg-meta:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary , pg_offline_query: true }
  vars:
    pg_cluster: pg-meta
    vip_address: 10.10.10.2
    pg_users: [ { name: citus , password: citus , pgbouncer: true , roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta , owner: citus , extensions: [ { name: citus } ] } ]

#----------------------------------#
# cluster: citus data nodes
#----------------------------------#
pg-node1:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-node1
    vip_address: 10.10.10.3
    pg_users: [ { name: citus , password: citus , pgbouncer: true , roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta , owner: citus , extensions: [ { name: citus } ] } ]

pg-node2:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary  , pg_offline_query: true }
  vars:
    pg_cluster: pg-node2
    vip_address: 10.10.10.4
    pg_users: [ { name: citus , password: citus , pgbouncer: true , roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta , owner: citus , extensions: [ { name: citus } ] } ]

pg-node3:
  hosts:
    10.10.10.13: { pg_seq: 1, pg_role: primary  , pg_offline_query: true }
  vars:
    pg_cluster: pg-node3
    vip_address: 10.10.10.5
    pg_users: [ { name: citus , password: citus , pgbouncer: true , roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta , owner: citus , extensions: [ { name: citus } ] } ]

接下来,您需要参照Citus多节点部署指南,在 Coordinator 节点上,执行以下命令以添加数据节点:

sudo su - postgres; psql meta
SELECT * from citus_add_node('10.10.10.11', 5432);
SELECT * from citus_add_node('10.10.10.12', 5432);
SELECT * from citus_add_node('10.10.10.13', 5432);
SELECT * FROM citus_get_active_worker_nodes();
  node_name  | node_port
-------------+-----------
 10.10.10.11 |      5432
 10.10.10.13 |      5432
 10.10.10.12 |      5432
(3 rows)

成功添加数据节点后,您可以使用以下命令,在协调者上创建样例数据表,并将其分布到每个数据节点上。

-- 声明一个分布式表
CREATE TABLE github_events
(
    event_id     bigint,
    event_type   text,
    event_public boolean,
    repo_id      bigint,
    payload      jsonb,
    repo         jsonb,
    actor        jsonb,
    org          jsonb,
    created_at   timestamp
) PARTITION BY RANGE (created_at);
-- 创建分布式表
SELECT create_distributed_table('github_events', 'repo_id');

更多Citus相关功能介绍,请参考Citus官方文档

MatrixDB集群部署

Greenplum是基于PostgreSQL生态构建的分布式数据仓库,广受广大用户喜爱。MatrixDB是Greenplum的一个分支,基于Greenplum 7 ,使用PostgreSQL 12内核。因为Greenplum 7尚未正式发布,因此Pigsty目前使用MatrixDB作为Greenplum的替代实现。

因为MatrixDB基于PostgreSQL生态,因此大多数PostgreSQL剧本与任务可以复用在 MatrixDB 上。MatrixDB 专用的额外参数只有两个:

  • gp_role:定义Greenplum集群的身份,mastersegment
  • pg_instances:定义Segment实例,用于部署Segment实例监控。

详情请参考 MatrixDB部署

MatrixDB集群样例配置 4节点
#----------------------------------#
# cluster: mx-mdw (gp master)
#----------------------------------#
mx-mdw:
  hosts:
    10.10.10.10: { pg_seq: 1, pg_role: primary , nodename: mx-mdw-1 }
  vars:
    gp_role: master          # this cluster is used as greenplum master
    pg_shard: mx             # pgsql sharding name & gpsql deployment name
    pg_cluster: mx-mdw       # this master cluster name is mx-mdw
    pg_databases:
      - { name: matrixmgr , extensions: [ { name: matrixdbts } ] }
      - { name: meta }
    pg_users:
      - { name: meta , password: DBUser.Meta , pgbouncer: true }
      - { name: dbuser_monitor , password: DBUser.Monitor , roles: [ dbrole_readonly ], superuser: true }

    pgbouncer_enabled: true                # enable pgbouncer for greenplum master
    pgbouncer_exporter_enabled: false      # enable pgbouncer_exporter for greenplum master
    pg_exporter_params: 'host=127.0.0.1&sslmode=disable'  # use 127.0.0.1 as local monitor host

#----------------------------------#
# cluster: mx-sdw (gp master)
#----------------------------------#
mx-sdw:
  hosts:
    10.10.10.11:
      nodename: mx-sdw-1        # greenplum segment node
      pg_instances:             # greenplum segment instances
        6000: { pg_cluster: mx-seg1, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633 }
        6001: { pg_cluster: mx-seg2, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634 }
    10.10.10.12:
      nodename: mx-sdw-2
      pg_instances:
        6000: { pg_cluster: mx-seg2, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633  }
        6001: { pg_cluster: mx-seg3, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634  }
    10.10.10.13:
      nodename: mx-sdw-3
      pg_instances:
        6000: { pg_cluster: mx-seg3, pg_seq: 1, pg_role: primary , pg_exporter_port: 9633 }
        6001: { pg_cluster: mx-seg1, pg_seq: 2, pg_role: replica , pg_exporter_port: 9634 }
  vars:
    gp_role: segment               # these are nodes for gp segments
    pg_shard: mx                   # pgsql sharding name & gpsql deployment name
    pg_cluster: mx-sdw             # these segment clusters name is mx-sdw
    pg_preflight_skip: true        # skip preflight check (since pg_seq & pg_role & pg_cluster not exists)
    pg_exporter_config: pg_exporter_basic.yml   # use basic config to avoid segment server crash
    pg_exporter_params: 'options=-c%20gp_role%3Dutility&sslmode=disable'  # use gp_role = utility to connect to segments