教程
- 1: Pigsty CMDB
- 2: PG 标准操作流程
- 3: TPC-H 性能测试
- 4: CA自签名
- 5: PG 备份与恢复
- 6: PG 高可用演练
- 7: PG 慢查询优化
- 8: PG 数据库迁移
- 9: TimescaleDB 快速上手
1 - Pigsty CMDB
您可以使用 postgres
作为 Pigsty 的配置源,替代静态配置文件。
使用 CMDB 作为 Ansible 的动态 Inventory具有一些优点:元数据以高度结构化的方式以数据表的形式呈现,并通过数据库约束确保一致性。同时CMDB允许您使用第三方的工具来编辑管理Pigsty元数据,便于与外部系统相互集成。
目前 Pigsty 的CMDB仅支持 PostgreSQL 集群,如果您的 pigsty.yml 中包含 Redis与MatrixDB,则会报错,建议使用单独的 pigsty.yml 配置文件管理Redis与Greenplum集群。
加载配置
Pigsty CMDB的模式会在pg-meta
元数据库初始化时自动创建(files/cmdb.sql
),位于meta
数据库的pigsty
模式中。使用bin/inventory_load
可以将静态配置文件加载至CMDB中。
必须在元节点完整执行
infra.yml
,安装完毕后,方可使用CMDB
usage: inventory_load [-h] [-p PATH] [-d CMDB_URL]
load config arguments
optional arguments:
-h, --help show this help message and exit„
-p PATH, --path PATH config path, ${PIGSTY_HOME}/pigsty.yml by default
-d DATA, --data DATA postgres cmdb pgurl, ${METADB_URL} by default
默认情况下,不带参数执行该脚本将会把$PIGSTY_HOME/pigsty.yml
的名称载入默认CMDB中。
bin/inventory_load
bin/inventory_load -p files/conf/pigsty-demo.yml
bin/inventory_load -p files/conf/pigsty-dcs3.yml -d postgresql://dbuser_meta:DBUser.Meta@10.10.10.10:5432/meta
使用CMDB作为配置源
当原有配置文件加载至CMDB作为初始数据后,即可配置Ansible使用CMDB作为配置源:
bin/inventory_cmdb
您可以切换回静态配置文件:
bin/inventory_conf
修改配置源实质上是编辑Pigsty目录下的 ansible.cfg
实现的。
---
inventory = pigsty.yml
+++
inventory = inventory.sh
2 - PG 标准操作流程
大多数集群管理操作都需要使用到元节点上的管理用户,并在Pigsty根目录执行相应Ansible Playbook。以下示例如无特殊说明,均以沙箱环境,三节点集群 pg-test
作为演示对象。
操作命令速查表
集群实例管理
在元节点上使用管理用户执行以下命令管理PostgreSQL集群与实例:
bin/createpg pg-test # 初始化PGSQL集群 pg-test
bin/createpg 10.10.10.13 # 初始化PGSQL实例 10.10.10.13
bin/reloadha pg-test # 调整PGSQL集群 pg-test 的负载均衡配置
bin/reloadhba pg-test # 调整PGSQL集群 pg-test 的认证白名单配置
bin/createuser pg-test -e pg_user=test # 在PG集群pg-test创建用户test
bin/createdb pg-test -e pg_database=test # 在PG集群pg-test创建数据库test
底层的相应的Ansible剧本为:
# NODES集群创建/集群扩容
./nodes.yml -l pg-test # 集群初始化
./nodes.yml -l 10.10.10.13 # 实例初始化
# PGSQL集群创建/集群扩容
./pgsql.yml -l pg-test # 集群初始化
./pgsql.yml -l 10.10.10.13 # 实例初始化
# PGSQL集群销毁/实例销毁
./pgsql-remove.yml -l pg-test # 集群销毁
./pgsql-remove.yml -l 10.10.10.13 # 实例销毁
# NODES集群销毁/实例销毁
./nodes-remove.yml -l pg-test # 集群销毁
./nodes-remove.yml -l 10.10.10.13 # 实例销毁
# PGSQL业务数据库/用户创建
./pgsql-createuser.yml -l pg-test -e pg_user=test
./pgsql-createdb.yml -l pg-test -e pg_database=test
# 成员身份调整
./pgsql.yml -l pg-test -t pg_hba # 调整IP白名单
./pgsql.yml -l pg-test -t haproxy_config,haproxy_reload
# 服务注册信息调整
./pgsql.yml -l pg-test -t register_prometheus
./pgsql.yml -l pg-test -t register_grafana
Patroni数据库管理
Pigsty默认使用Patroni管理PostgreSQL实例数据库。这意味着您需要使用patronictl
命令来管理Postgres集群,包括:集群配置变更,重启,Failover,Switchover,重做特定实例,切换自动/手动高可用模式等。
用户可以使用patronictl
在元节点上的管理用户,或任意数据库节点的dbsu
执行。快捷命令pg
已经在所有托管的机器上创建,用户可以使用它对所有目标Postgres集群发起管理。
常用的管理命令如下所示,更多命令请参考pg --help
pg list [cluster] # 打印集群信息
pg edit-config [cluster] # 编辑某个集群的配置文件
pg reload [cluster] [instance] # 重载某个集群或实例的配置
pg restart [cluster] [instance] # 重启某个集群或实例
pg reinit [cluster] [instance] # 重置某个集群中的实例(重新制作从库)
pg pause [cluster] # 进入维护模式(不会触发自动故障切换)
pg resume [cluster] # 退出维护模式
pg failover [cluster] # 手工触发某集群的Failover
pg switchover [cluster] # 手工触发某集群的Switchover
服务组件管理
在Pigsty的部署中,所有组件均由systemd
管理;PostgreSQL除外,PostgreSQL由Patroni管理。
例外的例外:当
patroni_mode
为remove
时例外,Pigsty将直接使用systemd
管理Postgres
systemctl stop patroni # 关闭 Patroni & Postgres
systemctl stop pgbouncer # 关闭 Pgbouncer
systemctl stop pg_exporter # 关闭 PG Exporter
systemctl stop pgbouncer_exporter # 关闭 Pgbouncer Exporter
systemctl stop node_exporter # 关闭 Node Exporter
systemctl stop haproxy # 关闭 Haproxy
systemctl stop vip-manager # 关闭 Vip-Manager
systemctl stop consul # 关闭 Consul
systemctl stop postgres # 关闭 Postgres (patroni_mode = remove )
以下组件可以通过 systemctl reload
重新加载配置
systemctl reload patroni # 重载配置: Patroni
systemctl reload postgres # 重载配置: Postgres (patroni_mode = remove)
systemctl reload pgbouncer # 重载配置: Pgbouncer
systemctl reload pg_exporter # 重载配置: PG Exporter
systemctl reload pgbouncer_exporter # 重载配置: Pgbouncer Exporter
systemctl reload haproxy # 重载配置: Haproxy
systemctl reload vip-manager # 重载配置: vip-manager
systemctl reload consul # 重载配置: Consul
在元节点上,还可以通过 systemctl reload
重新加载基础设施组件的配置:
systemctl reload nginx # 重载配置: Nginx (更新Haproxy管理界面索引,以及外部访问域名)
systemctl reload prometheus # 重载配置: Prometheus (更新预计算指标计算逻辑与告警规则)
systemctl reload alertmanager # 重载配置: Alertmanager
systemctl reload grafana-server # 重载配置: Grafana
当Patroni管理Postgres时,请不要使用 pg_ctl
直接操作数据库集簇 (/pg/data
)。
您可以通过pg pause <cluster>
进入维护模式后再对数据库进行手工管理。
常用命令集锦
./infra.yml -t environ # 重新在元节点上配置环境变量与访问凭证
./infra.yml -t repo_upstream # 重新在元节点上添加上游repo
./infra.yml -t repo_download # 重新在元节点上下载软件包
./infra.yml -t nginx_home # 重新生成Nginx首页内容
./infra.yml -t nginx_config,nginx_restart # 重新生成Nginx配置文件并重启应用
./infra.yml -t prometheus_config # 重置Prometheus配置
./infra.yml -t grafana_provision # 重置Grafana监控面板
./pgsql.yml -l pg-test -t=pgsql # 完成数据库部署:数据库、监控、服务
./pgsql.yml -l pg-test -t=postgres # 完成数据库部署
./pgsql.yml -l pg-test -t=service # 完成负载均衡的部署,(Haproxy & VIP)
./pgsql.yml -l pg-test -t=pg-exporter # 完成监控部署
./pgsql.yml -l pg-test -t=pg-register # 将服务注册至基础设施
./pgsql.yml -l pg-test -t=register_prometheus # 将监控对象注册至Prometheus
./pgsql.yml -l pg-test -t=register_grafana # 将监控目标数据源注册至Grafana
Case 1:集群创建扩容
集群创建/扩容使用剧本 pgsql.yml
,创建集群使用集群名作为执行对象,创建新实例/集群扩容则以集群中的单个实例作为执行对象。在使用 pgsql.yml
部署PGSQL数据库前,目标节点应当已经被 nodes.yml
剧本初始化。您可以使用 bin/createpg
一次性完成两者。
集群初始化
./nodes.yml -l pg-test # 初始化 pg-test 包含的机器节点
./pgsql.yml -l pg-test # 初始化 pg-test 数据库集群
上述两剧本可简化为:
bin/createpg pg-test
集群扩容
假设现在有测试集群pg-test
,包含两实例10.10.10.11
与10.10.10.12
,现额外扩容一台10.10.10.13
。
修改配置
首先需要修改配置清单(pigsty.yml
或CMDB)中的相应配置。
请一定注意集群中各实例的
pg_seq
必须唯一,否则会出现身份重叠与重复。
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, pg_offline_query: true } # 新实例
vars: { pg_cluster: pg-test }
执行变更
然后,执行以下命令,完成集群成员的初始化
./nodes.yml -l 10.10.10.13 # 初始化 pg-test 机器节点 10.10.10.13
./pgsql.yml -l 10.10.10.13 # 初始化 pg-test 的实例 pg-test-3
# 上述两命令可简化为:
bin/createpg 10.10.10.13
调整角色
集群扩容会导致集群成员变化,请参考 Case 8:集群角色调整 将流量分发至新实例。
常见问题
常见问题1:PGSQL数据库已经存在,执行中止
Pigsty使用安全保险机制来避免误删运行中的PGSQL数据库,请使用 pgsql-remove
剧本先完成数据库实例下线,再复用该节点。如需进行紧急覆盖式安装,可使用以下参数在安装过程中强制抹除运行中实例(危险!!!)
pg_clean
= cleanpg_safeguard
= false
例如:./pgsql.yml -l pg-test -e pg_clean=clean
将强制对 pg-test
集群进行覆盖式安装。
常见问题2:Consul已经存在,执行中止
Pigsty使用安全保险机制来避免误删运行中的Consul实例,请使用 nodes-remove
剧本先完成节点下线,确保Consul已经移除,再复用该节点。如需进行紧急覆盖式安装,可使用以下参数在安装过程中强制抹除运行中实例(危险!!!)
dcs_clean
= cleandcs_safeguard
= false
常见问题3:数据库太大,扩容从库执行超时
当扩容操作卡在 Wait for postgres replica online
这一步并中止时,通常是因为已有数据库实例太大,超过了Ansible的超时等待时间。
如果报错中止,该实例仍然会继续在后台拉起从库实例,您可以使用 pg list pg-test
命令列出集群当前状态,当新从库的状态为running
时,可以使用以下命令,从中止的地方继续执行Ansible Playbook:
./pgsql.yml -l 10.10.10.13 --start-at-task 'Wait for postgres replica online'
如果拉起新从库因某些意外而中止,请参考常见问题2。
常见问题4:集群处于维护模式 ,从库没有自动拉起
解决方案1,使用pg resume pg-test
将集群配置为自动切换模式,再执行从库创建操作。
解决方案2,使用pg reinit pg-test pg-test-3
,手动完成实例初始化。该命令也可以用于重做集群中的现有实例。
常见问题5:集群从库带有`clonefrom`标签,但因数据损坏不宜使用或拉取失败
找到问题机器,切换至postgres
用户,修改 patroni 配置文件并重载生效
sudo su postgres
sed -ie 's/clonefrom: true/clonefrom: false/' /pg/bin/patroni.yml
sudo systemctl reload patroni
pg list -W # 查阅集群状态,确认故障实例没有clonefrom标签
常见问题6:如何使用现有用户创建固定的管理员用户
系统默认使用 dba
作为管理员用户,该用户应当可以从管理机通过ssh免密码登陆远程数据库节点,并免密码执行sudo命令。
如果分配的机器默认没有该用户,但您有其他的管理用户(例如vagrant
)可以ssh登陆远程节点并执行sudo,则可以执行以下命令,使用其他的用户登陆远程机器并自动创建标准的管理用户:
./nodes.yml -t node_admin -l pg-test -e ansible_user=vagrant -k -K
SSH password:
BECOME password[defaults to SSH password]:
如果指定-k|--ask-pass -K|--ask-become-pass
参数,则在执行前应当输入该管理用户的SSH登陆密码与sudo密码。
执行完毕后,即可从元节点上的管理用户(默认为dba
) 登陆目标数据库机器,并执行其他剧本。
偶见问题7:集群从库带有clonefrom标签,但因数据损坏不宜使用或拉取失败
找到问题机器,切换至postgres
用户,修改 patroni 配置文件并重载生效
sudo su postgres
sed -ie 's/clonefrom: true/clonefrom: false/' /pg/bin/patroni.yml
sudo systemctl reload patroni
pg list -W # 查阅集群状态,确认故障实例没有clonefrom标签
Case 2:集群下线缩容
集群销毁/缩容使用专用剧本pgsql-remove
,针对集群使用时,将下线移除整个集群。针对集群中的单个实例使用时,将从集群中移除该实例。
注意,直接移除集群主库将导致集群Failover,故同时移除包含主库在内的多个实例时,建议先移除所有从库,再移除主库。
注意,
pgsql-remove
剧本不受 安全保险 参数影响,会直接移除数据库实例,谨慎使用!
集群销毁
# 销毁 pg-test 集群:先销毁所有非主库实例,最后销毁主库实例
./pgsql-remove.yml -l pg-test
# 销毁集群时,一并移除数据目录与软件包
./pgsql-remove.yml -l pg-test -e rm_pgdata=true -e rm_pgpkgs=true
# 移除 pg-test 包含的节点,可选
./nodes-remove.yml -l pg-test
集群缩容
./pgsql-remove.yml -l 10.10.10.13 # 实例销毁(缩容):销毁 pg-test 集群中的 10.10.10.13 节点
./nodes-remove.yml -l 10.10.10.13 # 从Pigsty中移除 10.10.10.13 节点(可选)
调整角色
注意:集群缩容会导致集群成员变化,缩容时,该实例健康检查为假,原本由该实例承载的流量将立刻转由其他成员承载。但您仍需参考参考 Case 8:集群角色调整 中的说明,将该下线实例从集群配置中彻底移除。
下线Offline实例
请注意在默认配置中,如果下线了所有 pg_role
= offline
或 pg_offline_query
](/zh/docs/pgsql/config#pg_offline_query) = true
的实例,而集群中仅剩下 primary
实例。那么离线读取流量将没有实例可以承载。
Case 3:集群配置变更重启
集群配置修改
修改PostgreSQL集群配置需要通过 pg edit-config <cluster>
进行,此外,还有一些特殊的控制参数需要通过Patroni进行配置与修改,例如:同步复制选项synchronous_mode
,必须修改Patroni的配置项(.synchronous_mode
),而非(postgresql.parameters.synchronous_mode
等参数),类似的参数包括: 控制同步提交节点数量的synchronous_node_count
,以及 standby_cluster.recovery_min_apply_delay
。
配置保存后,无需重启的配置可以通过确认生效。
请注意,pg edit-config
修改的参数为集群参数,单个实例范畴的配置参数(例如Patroni的Clonefrom标签等配置)需要直接修改Patroni配置文件(/pg/bin/patroni.yml
)并systemctl reload patroni
生效。
请注意在Pigsty中,HBA规则由剧本自动创建并维护,请不要使用Patroni来管理HBA规则。
集群重启
需要重启的配置则需要安排数据库重启。重启集群可以使用以下命令进行:
pg restart [cluster] [instance] # 重启某个集群或实例
带有需重启生效的实例,在pg list <cluster>
中会显示 pending restart
记号。
Case 4:集群业务用户创建
可以通过 pgsql-createuser.yml
在已有的数据库中创建新的业务用户。
业务用户通常指生产环境中由软件程序所使用的用户,需要通过连接池访问数据库的用户必须通过这种方式管理。其它用户可以使用Pigsty创建与管理,亦可由用户自行维护管理。
# 在 pg-test 集群创建名为 test 的用户
./pgsql-createuser.yml -l pg-test -e pg_user=test
以上命令可以简写为:
bin/createuser pg-test test # 在 pg-test 集群创建名为 test 的用户
如果数据库配置有OWNER,请先创建对应OWNER用户后再创建相应数据库。因此,如果需要同时创建业务用户与业务数据库,通常应当先创建业务用户。
Case 5:集群业务数据库创建
可以通过 pgsql-createdb.yml
在已有的数据库集群中创建新的业务数据库。
业务数据库指代由用户创建并使用的数据库对象。如果您希望通过连接池访问该数据库,则必须使用Pigsty提供的剧本进行创建,以维持连接池中的配置与PostgreSQL保持一致。
# 在 pg-test 集群创建名为 test 的数据库
./pgsql-createdb.yml -l pg-test -e pg_database=test
以上命令可以简写为:
bin/createdb pg-test test # 在 pg-test 集群创建名为 test 的数据库
如果数据库配置有OWNER,请先创建对应OWNER用户后再创建相应数据库。
将新数据库注册为Grafana数据源
执行以下命令,会将pg-test
集群中所有实例上所有的业务数据库作为 PostgreSQL 数据源注册入Grafana,供PGCAT应用使用。
./pgsql.yml -t register_grafana -l pg-test
Case 6:集群HBA规则调整
用户可以通过 pgsql.yml
的 pg_hba
子任务,调整现有的数据库集群/实例的HBA配置。
当集群发生Failover,Switchover,以及HBA规则调整时,应当重新执行此任务,将集群的IP黑白名单规则调整至期待的行为。
HBA配置由 pg_hba_rules
与 pg_hba_rules_extra
合并生成,两者都是由规则配置对象组成的数组。样例如下:
- title: allow internal infra service direct access
role: common
rules:
- host putong-confluence dbuser_confluence 10.0.0.0/8 md5
- host putong-jira dbuser_jira 10.0.0.0/8 md5
- host putong-newjira dbuser_newjira 10.0.0.0/8 md5
- host putong-gitlab dbuser_gitlab 10.0.0.0/8 md5
执行以下命令,将重新生成HBA规则,并应用生效。
./pgsql.yml -t pg_hba -l pg-test
以上命令可以简写为:
bin/reloadhba pg-test
Pigsty强烈建议使用配置文件自动管理HBA规则,除非您清楚的知道自己在做什么。
Case 7:集群流量控制
Pigsty中PostgreSQL的集群流量默认由HAProxy控制,用户可以直接通过HAProxy提供的WebUI控制集群流量。
使用HAProxy Admin UI控制流量
Pigsty的HAProxy默认在9101端口(haproxy_exporter_port
)提供了管理UI,该管理UI默认可以通过Pigsty的默认域名,后缀以实例名(pg_cluster
-pg_seq
)访问。管理界面带有可选的认证选项,由参数(haproxy_auth_enabled
)启用。管理界面认证默认不启用,启用时则需要使用由 haproxy_admin_username
与 haproxy_admin_password
的用户名与密码登陆。
使用浏览器访问 http://pigsty/<ins>
(该域名因配置而变化,亦可从PGSQL Cluster Dashboard中点击前往),即可访问对应实例上的负载均衡器管理界面。样例界面
您可以在这里对每集群众一个服务,以及每一个后端服务器的流量进行控制。例如要将相应的Server排干,则可以选中该Server,设置 MAINT
状态并应用。如果您同时使用了多个HAProxy进行负载均衡,则需要依次在每一个负载均衡器上执行此动作。
修改集群配置
当集群发生成员变更时,您应当在合适的时候调整集群中所有成员的负载均衡配置,以如实反映集群架构变化,例如当发生主从切换后。
此外通过配置 pg_weight
参数,您可以显式地控制集群中各实例承担的负载比例,该变更需要重新生成集群中HAProxy的配置文件,并reload重载生效。例如,此配置将2号实例在所有服务中的相对权重从默认的100降为0
10.10.10.11: { pg_seq: 1, pg_role: primary}
10.10.10.12: { pg_seq: 2, pg_role: replica, pg_weight: 0 }
10.10.10.13: { pg_seq: 3, pg_role: replica, }
使用以下命令调整集群配置并生效。
# 重新生成 pg-test 的HAProxy配置(但没有应用)
./pgsql.yml -l pg-test -t haproxy_config
# 重新加载 pg-test 的HAProxy配置并启用生效
./pgsql.yml -l pg-test -t haproxy_config,haproxy_reload -e haproxy_reload=true
配置与生效命令可以合并简写为:
bin/reloadha pg-test # 调整pg-test集群所有HAPROXY并重载配置,通常不会影响现有流量。
Case 8:集群角色调整
这里介绍Pigsty默认使用的HAProxy接入方式,如果您使用L4 VIP或其它方式接入,则可能与此不同。
当集群发生任何形式的角色变更,即配置清单中集群与实例的 pg_role
参数无法真实反映服务器状态时,便需要进行此项调整。
例如,集群缩容后,集群负载均衡会根据健康检查立刻重新分配流量,但不会移除下线实例的配置项。
集群扩容后,已有实例的负载均衡器配置不会变化。即,您可以通过新实例上的HAProxy访问已有集群的所有成员,但旧实例上的HAProxy配置不变,因此不会将流量分发至新实例上。
1. 修改配置文件 pg_role
当集群发生了主从切换时,应当按照当前实际情况,调整集群成员的pg_role
。例如,当pg-test
发生了Failover或Switchover,导致pg-test-3
实例变为新的集群领导者,则应当修改 pg-test-3
的角色为 primary
,并将原主库 pg_role
配置为 replica
。
同时,您应当确保集群中至少存在一个实例能用于提供Offline服务,故为pg-test-1
配置实例参数:pg_offline_query: true
。
通常,非常不建议为集群配置一个以上的Offline实例,慢查询与长事务可能会导致在线只读流量受到影响。
10.10.10.11: { pg_seq: 1, pg_role: replica, pg_offline_query: true }
10.10.10.12: { pg_seq: 2, pg_role: replica }
10.10.10.13: { pg_seq: 3, pg_role: primary }
2. 调整集群实例HBA
当集群角色发生变化时,适用于不同角色的HBA规则也应当重新调整。
使用 Case 6:集群HBA规则调整 中介绍的方法,调整集群HBA规则
3. 调整集群负载均衡配置
HAProxy会根据集群中Patroni返回的健康检查结果来动态分发请求流量,因此节点故障并不会影响外部请求。但用户应当在合适的时间(比如早上睡醒后),调整集群负载均衡配置。例如:将故障彻底从集群配置中剔除,而不是以健康检查DOWN的状态继续僵死在集群中。
使用 Case 7:集群流量控制 中介绍的方法,调整集群负载均衡配置。
4.整合操作
您可以在修改配置后,使用以下命令,完成集群角色的调整。
./pgsql.yml -l pg-test -t pg_hba,haproxy_config,haproxy_reload
或使用等价的简写脚本
bin/reloadhba pg-test # 调整集群HBA配置
bin/reloadha pg-test # 调整集群HAProxy配置
Case 9:监控对象调整
Pigsty默认使用静态文件服务发现的方式管理 Prometheus 监控对象,默认位置:/etc/prometheus/targets
。
使用 Consul 服务发现是可选项,在此模式下,通常无需手工管理监控对象。使用静态文件服务发现时,在执行实例上线下线时,所有的监控对象都会一并自动处理:注册或注销。但仍然有一些特殊的场景无法覆盖周全(例如修改集群名称)。
手动添加Prometheus监控对象
# 将 pg-test 集群所有成员注册为 prometheus 监控对象
./pgsql.yml -t register_prometheus -l pg-test
PostgreSQL的服务发现对象定义默认存储于所有元节点的 /etc/prometheus/targets/pgsql
目录中:每一个实例对应一个yml文件,包含目标的标签,与Exporter暴露的端口。
# pg-meta-1 [primary] @ 172.21.0.11
- labels: { cls: pg-meta, ins: pg-meta-1 }
targets: [172.21.0.11:9630, 172.21.0.11:9100, 172.21.0.11:9631, 172.21.0.11:9101]
手工移除Prometheus监控对象
# 移除监控对象文件
rm -rf /etc/prometheus/targets/pgsql/pg-test-*.yml
手工添加Grafana数据源
# 将 pg-test 集群中的每一个数据库对象,注册为 grafana 的数据源
./pgsql.yml -t register_grafana -l pg-test
手工移除Grafana数据源
在Grafana中点击数据源管理,手工删除即可。
Case 10:集群主从切换
例如,想要在三节点演示集群 pg-test
上执行Failover,则可以执行以下命令:
pg failover <cluster>
然后按照向导提示,执行Failover即可,集群Failover后,应当参考 Case 8:集群角色调整 中的说明,修正集群角色。
执行Failover的操作记录
[08-05 17:00:30] postgres@pg-meta-1:~
$ pg list pg-test
+ Cluster: pg-test (6988888117682961035) -----+----+-----------+-----------------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Pending restart | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
| pg-test-1 | 172.21.0.3 | Leader | running | 1 | | | clonefrom: true |
| pg-test-2 | 172.21.0.4 | Replica | running | 1 | 0 | * | clonefrom: true |
| pg-test-3 | 172.21.0.16 | Replica | running | 1 | 0 | * | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
[08-05 17:00:34] postgres@pg-meta-1:~
$ pg failover pg-test
Candidate ['pg-test-2', 'pg-test-3'] []: pg-test-3
Current cluster topology
+ Cluster: pg-test (6988888117682961035) -----+----+-----------+-----------------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Pending restart | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
| pg-test-1 | 172.21.0.3 | Leader | running | 1 | | | clonefrom: true |
| pg-test-2 | 172.21.0.4 | Replica | running | 1 | 0 | * | clonefrom: true |
| pg-test-3 | 172.21.0.16 | Replica | running | 1 | 0 | * | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
Are you sure you want to failover cluster pg-test, demoting current master pg-test-1? [y/N]: y
2021-08-05 17:00:46.04144 Successfully failed over to "pg-test-3"
+ Cluster: pg-test (6988888117682961035) -----+----+-----------+-----------------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Pending restart | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
| pg-test-1 | 172.21.0.3 | Replica | stopped | | unknown | | clonefrom: true |
| pg-test-2 | 172.21.0.4 | Replica | running | 1 | 0 | * | clonefrom: true |
| pg-test-3 | 172.21.0.16 | Leader | running | 1 | | * | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
[08-05 17:00:46] postgres@pg-meta-1:~
$ pg list pg-test
+ Cluster: pg-test (6988888117682961035) -----+----+-----------+-----------------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Pending restart | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
| pg-test-1 | 172.21.0.3 | Replica | running | 2 | 0 | * | clonefrom: true |
| pg-test-2 | 172.21.0.4 | Replica | running | 2 | 0 | * | clonefrom: true |
| pg-test-3 | 172.21.0.16 | Leader | running | 2 | | * | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+-----------------+
Case 11:重置组件
俗话说,重启可以解决90%的问题,而重装可以解决剩下的10%。
面对疑难杂症,重置问题组件是一种简单有效的止损手段。使用Pigsty的初始化剧本 infra.yml
与 pgsql.yml
可以重置基础设施与数据库集群,但通常我们只需要使用特定的子任务来重置特定组件即可。
基础设施重置
常用的基础设施重新配置命令包括:
./infra.yml -t repo_upstream # 重新在元节点上添加上游repo
./infra.yml -t repo_download # 重新在元节点上下载软件包
./infra.yml -t nginx_home # 重新生成Nginx首页内容
./infra.yml -t prometheus_config # 重置Prometheus配置
./infra.yml -t grafana_provision # 重置Grafana监控面板
您也可以强行重新安装这些组件
./infra.yml -t nginx # 重新配置Nginx
./infra.yml -t prometheus # 重新配置Prometheus
./infra.yml -t grafana # 重新配置Grafana
./infra-jupyter.yml # 重置Jupyterlab
./infra-pgweb.yml # 重置PGWeb
此外,您可以使用以下命令重置数据库节点上的具体组件
# 较为常用,安全的重置命令,重装监控与重新注册不会影响服务
./pgsql.yml -l pg-test -t=monitor # 重新部署监控
./pgsql.yml -l pg-test -t=register # 重新将服务注册至基础设施(Nginx, Prometheus, Grafana, CMDB...)
./nodes.yml -l pg-test -t=consul -e dcs_clean=clean # 在维护模式下重置DCS Agent
# 略有风险的重置操作
./pgsql.yml -l pg-test -t=service # 重新部署负载均衡,可能导致服务闪断
./pgsql.yml -l pg-test -t=pgbouncer # 重新部署连接池,可能导致服务闪断
# 非常危险的重置任务
./pgsql.yml -l pg-test -t=postgres # 重置数据库(包括Patroni,Postgres,Pgbouncer)
./pgsql.yml -l pg-test -t=pgsql # 重新完成完整的数据库部署:数据库、监控、服务
./nodes.yml -l pg-test -t=consul # 当高可用自动切换模式启用时,直接重置DCS服务器
# 极度危险的重置任务
./nodes.yml -l pg-test -t=consul -e rm_dcs_servers=true # 强制抹除DCS服务器,可能导致所有DB集群不可写入
例如,如果集群的连接池出现问题,一种兜底的止损方式便是重启或重装Pgbouncer连接池。
./pgsql.yml -l pg-test -t=pgbouncer # 重装连接池(所有用户与DB会重新生成),手工修改的配置会丢失
Case 12:替换集群DCS服务器
DCS(Consul/Etcd)本身是非常可靠的服务,一旦出现问题,其影响也是非常显著的。
按照Patroni的工作逻辑,一旦集群主库发现DCS服务器不可达,会立即遵循Fencing逻辑,将自身降级为普通从库,无法写入。
维护模式
除非当前集群处于“维护模式”(使用pg pause <cluster>
进入,使用pg resume <cluster>
退出)
# 使目标集群进入维护模式
pg pause pg-test
# 将目标集群恢复为自动故障切换模式(可选)
pg resume pg-test
重置数据库节点的DCS服务
当DCS故障不可用,需要迁移至新的DCS(Consul)集群时,可以采用以下操作。
首先创建新DCS集群,然后编辑配置清单 dcs_servers
填入新DCS Servers的地址。
# 强制重置目标集群上的Consul Agent(因为HA处于维护模式,不会影响新数据库集群)
./nodes.yml -l pg-test -t consul -e dcs_clean=clean
当Patroni完成重启后(维护模式中,Patroni重启不会导致Postgres关停),会将集群元数据KV写入新的Consul集群中,所以必须确保原主库上的Patroni服务首先完成重启。
# 重要!首先重启目标集群主库的Patroni,然后重启其余从库的Patroni
ansible pg-test-1 -b -a 'sudo systemctl reload patroni'
ansible pg-test-2,pg-test-3 -b -a 'sudo systemctl restart patroni'
3 - TPC-H 性能测试
Build TPC-H Generator
git clone git@github.com:electrum/tpch-dbgen.git
cd tpch-dbgen/
make
Generate TPC-H Data
SCALE_FACTOR=1
export DSS_QUERY="${PWD}/queries"
export OUT_QUERY="/tmp/queries"
mkdir -p ${OUT_QUERY}
./dbgen -vf -s ${SCALE_FACTOR}
for i in {1..22}
do
diff $i.sql ../queries/$i.sql
done
for i in {1..22}
do
./qgen -s 100 -d ${i} > ${OUT_QUERY}/${i}.sql
done
Prepare Database
psql postgres -c 'DROP DATABASE tpch;';
psql postgres -c 'CREATE DATABASE tpch';
psql postgres -c 'TRUNCATE TABLE REGION,PART CASCADE;';
# psql tpc -f dss.ddl ; psql tpc -c '\d+' ; ls -alh *.tbl
CREATE TABLE REGION
(
R_REGIONKEY INTEGER NOT NULL PRIMARY KEY,
R_NAME CHAR(25) NOT NULL,
R_COMMENT VARCHAR(152)
);
CREATE TABLE NATION
(
N_NATIONKEY INTEGER NOT NULL PRIMARY KEY,
N_NAME CHAR(25) NOT NULL,
N_REGIONKEY INTEGER NOT NULL,
N_COMMENT VARCHAR(152),
FOREIGN KEY (N_REGIONKEY) REFERENCES REGION (R_REGIONKEY)
);
CREATE TABLE PART
(
P_PARTKEY INTEGER NOT NULL PRIMARY KEY,
P_NAME VARCHAR(55) NOT NULL,
P_MFGR CHAR(25) NOT NULL,
P_BRAND CHAR(10) NOT NULL,
P_TYPE VARCHAR(25) NOT NULL,
P_SIZE INTEGER NOT NULL,
P_CONTAINER CHAR(10) NOT NULL,
P_RETAILPRICE DECIMAL(15, 2) NOT NULL,
P_COMMENT VARCHAR(23) NOT NULL
);
CREATE TABLE SUPPLIER
(
S_SUPPKEY INTEGER NOT NULL PRIMARY KEY,
S_NAME CHAR(25) NOT NULL,
S_ADDRESS VARCHAR(40) NOT NULL,
S_NATIONKEY INTEGER NOT NULL,
S_PHONE CHAR(15) NOT NULL,
S_ACCTBAL DECIMAL(15, 2) NOT NULL,
S_COMMENT VARCHAR(101) NOT NULL,
FOREIGN KEY (S_NATIONKEY) REFERENCES NATION (N_NATIONKEY)
);
CREATE TABLE PARTSUPP
(
PS_PARTKEY INTEGER NOT NULL,
PS_SUPPKEY INTEGER NOT NULL,
PS_AVAILQTY INTEGER NOT NULL,
PS_SUPPLYCOST DECIMAL(15, 2) NOT NULL,
PS_COMMENT VARCHAR(199) NOT NULL,
PRIMARY KEY (PS_PARTKEY, PS_SUPPKEY),
FOREIGN KEY (PS_SUPPKEY) REFERENCES SUPPLIER (S_SUPPKEY),
FOREIGN KEY (PS_PARTKEY) REFERENCES PART (P_PARTKEY)
);
CREATE TABLE CUSTOMER
(
C_CUSTKEY INTEGER NOT NULL PRIMARY KEY,
C_NAME VARCHAR(25) NOT NULL,
C_ADDRESS VARCHAR(40) NOT NULL,
C_NATIONKEY INTEGER NOT NULL,
C_PHONE CHAR(15) NOT NULL,
C_ACCTBAL DECIMAL(15, 2) NOT NULL,
C_MKTSEGMENT CHAR(10) NOT NULL,
C_COMMENT VARCHAR(117) NOT NULL,
FOREIGN KEY (C_NATIONKEY) REFERENCES NATION (N_NATIONKEY)
);
CREATE TABLE ORDERS
(
O_ORDERKEY INTEGER NOT NULL PRIMARY KEY,
O_CUSTKEY INTEGER NOT NULL,
O_ORDERSTATUS CHAR(1) NOT NULL,
O_TOTALPRICE DECIMAL(15, 2) NOT NULL,
O_ORDERDATE DATE NOT NULL,
O_ORDERPRIORITY CHAR(15) NOT NULL,
O_CLERK CHAR(15) NOT NULL,
O_SHIPPRIORITY INTEGER NOT NULL,
O_COMMENT VARCHAR(79) NOT NULL,
FOREIGN KEY (O_CUSTKEY) REFERENCES CUSTOMER (C_CUSTKEY)
);
CREATE TABLE LINEITEM
(
L_ORDERKEY INTEGER NOT NULL,
L_PARTKEY INTEGER NOT NULL,
L_SUPPKEY INTEGER NOT NULL,
L_LINENUMBER INTEGER NOT NULL,
L_QUANTITY DECIMAL(15, 2) NOT NULL,
L_EXTENDEDPRICE DECIMAL(15, 2) NOT NULL,
L_DISCOUNT DECIMAL(15, 2) NOT NULL,
L_TAX DECIMAL(15, 2) NOT NULL,
L_RETURNFLAG CHAR(1) NOT NULL,
L_LINESTATUS CHAR(1) NOT NULL,
L_SHIPDATE DATE NOT NULL,
L_COMMITDATE DATE NOT NULL,
L_RECEIPTDATE DATE NOT NULL,
L_SHIPINSTRUCT CHAR(25) NOT NULL,
L_SHIPMODE CHAR(10) NOT NULL,
L_COMMENT VARCHAR(44) NOT NULL,
PRIMARY KEY (L_ORDERKEY, L_LINENUMBER),
FOREIGN KEY (L_ORDERKEY) REFERENCES ORDERS (O_ORDERKEY),
FOREIGN KEY (L_PARTKEY, L_SUPPKEY) REFERENCES PARTSUPP (PS_PARTKEY, PS_SUPPKEY)
);
CREATE INDEX ON ORDERS (O_ORDERDATE);
CREATE INDEX ON LINEITEM (L_SHIPDATE);
CREATE INDEX ON LINEITEM (L_PARTKEY, L_SUPPKEY);
-- ALTER TABLE lineitem ADD FOREIGN KEY(l_partkey, l_suppkey) REFERENCES partsupp(ps_partkey, ps_suppkey);
sed 's/|$//' region.tbl | psql tpch -c "COPY region FROM STDIN DELIMITER '|';"
sed 's/|$//' nation.tbl | psql tpch -c "COPY nation FROM STDIN DELIMITER '|';"
sed 's/|$//' supplier.tbl | psql tpch -c "COPY supplier FROM STDIN DELIMITER '|';"
sed 's/|$//' part.tbl | psql tpch -c "COPY part FROM STDIN DELIMITER '|';"
sed 's/|$//' customer.tbl | psql tpch -c "COPY customer FROM STDIN DELIMITER '|';"
sed 's/|$//' partsupp.tbl | psql tpch -c "COPY partsupp FROM STDIN DELIMITER '|';"
sed 's/|$//' orders.tbl | psql tpch -c "COPY orders FROM STDIN DELIMITER '|';"
sed 's/|$//' lineitem.tbl | psql tpch -c "COPY lineitem FROM STDIN DELIMITER '|';"
Prepare Queries
git clone https://github.com/2ndQuadrant/pg-tpch.git
cd pg-tpch/queries
psql tpc
\i q01.analyze.sql
\i q02.analyze.sql
\i q03.analyze.sql
\i q04.analyze.sql
\i q05.analyze.sql
\i q06.analyze.sql
\i q07.analyze.sql
\i q08.analyze.sql
\i q09.analyze.sql
\i q10.analyze.sql
\i q11.analyze.sql
\i q12.analyze.sql
\i q13.analyze.sql
\i q14.analyze.sql
\i q15.analyze.sql
\i q16.analyze.sql
\i q17.analyze.sql
\i q18.analyze.sql
\i q19.analyze.sql
\i q20.analyze.sql
\i q21.analyze.sql
\i q22.analyze.sql
\i q01.explain.sql
\i q02.explain.sql
\i q03.explain.sql
\i q04.explain.sql
\i q05.explain.sql
\i q06.explain.sql
\i q07.explain.sql
\i q08.explain.sql
\i q09.explain.sql
\i q10.explain.sql
\i q11.explain.sql
\i q12.explain.sql
\i q13.explain.sql
\i q14.explain.sql
\i q15.explain.sql
\i q16.explain.sql
\i q17.explain.sql
\i q18.explain.sql
\i q19.explain.sql
\i q20.explain.sql
\i q21.explain.sql
\i q22.explain.sql
-- enhance with index
ALTER TABLE region ADD PRIMARY KEY (r_regionkey);
ALTER TABLE nation ADD PRIMARY KEY (n_nationkey);
ALTER TABLE customer ADD PRIMARY KEY (c_custkey);
ALTER TABLE supplier ADD PRIMARY KEY (s_suppkey);
ALTER TABLE part ADD PRIMARY KEY (p_partkey);
ALTER TABLE orders ADD PRIMARY KEY (o_orderkey);
ALTER TABLE partsupp ADD PRIMARY KEY (ps_partkey, ps_suppkey);
ALTER TABLE lineitem ADD PRIMARY KEY (l_orderkey, l_linenumber);
ALTER TABLE nation ADD FOREIGN KEY(n_regionkey) REFERENCES region(r_regionkey);
ALTER TABLE customer ADD FOREIGN KEY(c_nationkey) REFERENCES nation(n_nationkey);
ALTER TABLE supplier ADD FOREIGN KEY(s_nationkey) REFERENCES nation(n_nationkey);
ALTER TABLE orders ADD FOREIGN KEY(o_custkey) REFERENCES customer(c_custkey);
ALTER TABLE customer ADD FOREIGN KEY(c_suppkey) REFERENCES supplier(s_suppkey);
ALTER TABLE partsupp ADD FOREIGN KEY(ps_partkey) REFERENCES part(p_partkey);
ALTER TABLE lineitem ADD FOREIGN KEY(l_orderkey) REFERENCES orders(o_orderkey);
ALTER TABLE lineitem ADD FOREIGN KEY(l_partkey, l_suppkey) REFERENCES partsupp(ps_partkey, ps_suppkey);
TPC-H
Apple M1 Max (10C 64G 8TB)
s = 10 , s = 100
查询 | 10仓耗时 | 100仓耗时 |
---|---|---|
1 | 3.9 | 94 |
2 | 0.9 | 21 |
3 | 1.4 | 94 |
4 | 5.0 | 247 |
5 | 1.3 | 123 |
6 | 1.8 | 79 |
7 | 1.2 | 109 |
8 | 0.9 | 288 |
9 | 4.6 | 1346 |
10 | 1.7 | 209 |
11 | 0.7 | 32 |
12 | 1.1 | 115 |
13 | 11.8 | 214 |
14 | 0.7 | 111 |
15 | 3.7 | 189 |
16 | 1.5 | 140 |
17 | 7.5 | 605 |
18 | 37.4 | 844 |
19 | 0.3 | 36 |
20 | 1.7 | 653 |
21 | 2.2 | 215 |
22 | 1.0 | 68 |
4 - CA自签名
Pigsty默认会在初始化时,在管理节点生成一个自签名的CA。
CA密钥放置于元节点的 /etc/pigsty/ca/
目录中。
当您需要使用 SSL,数字签名,高级安全特性时,可以使用此 CA。
如果您已经有了自己的 CA 公私钥,将其放置于 files/
目录,并将 ca_method
修改为 copy
,则Pigsty会使用用户提供的CA证书与私钥。
FHS规约
/etc/pki # CA与证书目录
/etc/pki/ca # CA证书目录
/etc/pki/ca/cert # CA证书目录
常见操作
查看证书的内容:
openssl x509 -text -in /etc/pigsty/ca/ca.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
0b:e1:0b:39:c9:67:56:e5:b2:3d:6d:db:6a:b4:74:c5:50:0e:00:4e
Signature Algorithm: sha256WithRSAEncryption
Issuer: O=Pigsty CA, CN=pigsty-ca
Validity
Not Before: Jul 13 07:52:21 2022 GMT
Not After : Jun 19 07:52:21 2122 GMT
Subject: O=Pigsty CA, CN=pigsty-ca
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
........................................
73:09:b5
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:pigsty-ca
X509v3 Key Usage:
Digital Signature, Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:1
X509v3 Subject Key Identifier:
........................................
Signature Algorithm: sha256WithRSAEncryption
........................................
-----BEGIN CERTIFICATE-----
........................................
-----END CERTIFICATE-----
```
5 - PG 备份与恢复
故障大体可以分为两类:硬件故障/资源不足(坏盘/宕机),软件缺陷/人为错误(删库/删表)。基于主从复制的物理复制用于应对前者,延迟从库与冷备份通常用于应对后者。
Pigsty提供了完善的备份支持,无需配置即可使用开箱即用的主从物理复制,绝大多数物理故障均可自愈。同时,还提供了延迟备库与冷备份支持,用于应对软件故障与人为误操作。
物理复制
在Pigsty中,可以通过为集群中的数据库实例指定角色( pg_role
),即可以创建物理复制备份,用于从机器与硬件故障中恢复。例如以下配置声明了一个一主两从的高可用数据库集群。
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: offline } # 温备(不承载在线流量)
vars:
pg_cluster: pg-test
热备
replica
= Hot Standby,承载只读流量,与主库保持实时同步,但可能存在微量复制延迟。
与主库保持一致,当主库出现故障时会接管主库的工作,同时也会用于承接线上只读流量。其中,采用同步复制与主库保持实时一致的热备又可以称为同步备份。正常情况下,物理复制的复制延迟视网络条件与负载水平,可能在1ms-100ms/几十KB ~ 几MB的范围。请参考主从集群。
温备
offline
= Warm Standby,温备,不承担在线流量。备用,或仅用于离线/分析查询。
温备(Warm Standby):与热备类似,但不承载线上流量。请参考离线从库部署。
同步备库
standby
= Sync Standby,与主库保持严格实时同步。
使用同步提交的从库,又称做同步备库,详情请参考同步从库部署
延迟从库
延迟从库相比是一种快速应对软件故障/人为错误的措施。延迟从库采用标准的主从流复制机制从主库实时接收变更,但会延迟一段特定时间(例如1小时,一天)后再执行应用。因此在状态上,是原始主库的历史状态副本。当出现诸如误删数据这类问题时,实时主从同步会立即将此类变更同步至所有物理副本,但延迟从库则提供了一个抢救时间窗口:您可以立即从延迟从库中查询出数据并回补原主库。
高可用与主从复制可以解决机器硬件故障带来的问题,但无法解决软件Bug与人为操作导致的故障,例如:误删库删表。误删数据通常需要用到冷备份,但另一种更优雅高效快速的方式是事先准备一个延迟从库。
您可以使用 备份集群 的功能创建延时从库,例如,现在您希望为pg-test
集群指定一个延时从库:pg-testdelay
,该集群是pg-test
1小时前的状态。因此如果出现了误删数据,您可以立即从延时从库中获取并回灌入原始集群中。
# pg-test是原始数据库
pg-test:
hosts:
10.10.10.11: { pg_seq: 1, pg_role: primary }
vars:
pg_cluster: pg-test
pg_version: 14
# pg-testdelay 将作为 pg-test 库的延时从库
pg-testdelay:
hosts:
10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11 } # 实际角色为 Standby Leader
vars:
pg_cluster: pg-testdelay
pg_version: 14
创建完毕后,在元节点使用 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
冷备份
冷备份是最后的兜底机制,您可能几年都用不上一次,但真用上的时候,可以救命。
冷备(Code Backup):冷备数据库以数据目录静态文件的形式存在,是数据库目录的二进制备份。便于制作,管理简单,便于放到其他AZ实现容灾。误删库误删表,或整集群/整机房出现灾难性故障时,数据备份(冷备)是最后的兜底。
Pigsty提供了一个制作冷备份的脚本 pg-backup
,在数据库节点上以dbsu
身份执行,即可创建当前实例的全量物理备份,并放置于 /pg/backup
目录中(默认位于 {{ pg_fs_bkup }}
/backup
)。
用户可以通过参数来指定备份的数据库URL,备份目录,文件名,加密方式,已有备份的保留策略等。
$ pg-backup # 不带任何参数执行备份脚本
[2021-08-05 17:41:35][INFO] ================================================================
[2021-08-05 17:41:35][INFO] [INIT] pg-backup begin, checking parameters
[2021-08-05 17:41:35][DEBUG] [INIT] #====== BINARY
[2021-08-05 17:41:35][DEBUG] [INIT] pg_basebackup : /usr/pgsql/bin/pg_basebackup
[2021-08-05 17:41:35][DEBUG] [INIT] openssl : /bin/openssl
[2021-08-05 17:41:35][DEBUG] [INIT] #====== PARAMETER
[2021-08-05 17:41:35][DEBUG] [INIT] filename (-f) : backup_pg-meta_20210805.tar.lz4
[2021-08-05 17:41:35][DEBUG] [INIT] src (-s) : postgres:///
[2021-08-05 17:41:35][DEBUG] [INIT] dst (-d) : /pg/backup
[2021-08-05 17:41:35][DEBUG] [INIT] tag (-t) : pg-meta
[2021-08-05 17:41:35][DEBUG] [INIT] key (-k) : pg-meta
[2021-08-05 17:41:35][DEBUG] [INIT] encrypt (-e) : false
[2021-08-05 17:41:35][DEBUG] [INIT] upload (-u) : false
[2021-08-05 17:41:35][DEBUG] [INIT] remove (-r) : -mmin +1200
[2021-08-05 17:41:35][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[2021-08-05 17:41:35][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=25438
[2021-08-05 17:41:35][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20210805.tar.lz4
[2021-08-05 17:41:35][INFO] [BKUP] backup in normal mode
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/6B000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/6B000138
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2021-08-05 17:41:45][INFO] [BKUP] backup complete!
[2021-08-05 17:41:45][INFO] [RMBK] remove local obsolete backup: 1200
[2021-08-05 17:41:45][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2021-08-05 17:41:45][WARN] [BKUP] remove obsolete backups:
[2021-08-05 17:41:45][INFO] [RMBK] remove old backup complete
[2021-08-05 17:41:45][INFO] [LOCK] release lock @ /tmp/backup.lock
[2021-08-05 17:41:45][INFO] [DONE] backup procdure complete!
[2021-08-05 17:41:45][INFO] ================================================================
该脚本将使用 pg_basebackup
从指定的PGURL(默认为本地数据库实例)发起备份,使用tar
归档与lz4
压缩,并加以可选的openssl
RC4流加密。
备份文件默认放置于/pg/backup/
目录下,默认文件名由前缀,集群名,日期组成,形如:backup_pg-meta_20210805.tar.lz4
。
默认的备份清理策略是当最新备份完成时,会清理掉1200分钟(20小时前)的旧备份文件。
您需要根据自己的业务情况,使用该脚本制作备份并放置在合适的地方,例如专用的对象存储集群/NFS/或者本地备份盘。如果您希望将数据库回溯至任意时刻,而非仅仅回滚至数据库备份时刻,则还需要对集群WAL日志进行归档。因为此功能需要具体功能具体分析,因此Pigsty只提供工具与机制,不提供具体策略与实现。您可以使用配置于节点上的Crontab与本地目录作为最基本的冷备份实现。
node_crontab: # 每日凌晨1点执行一次全量备份
- '00 01 * * * postgres /pg/bin/pg-backup 2>>/pg/log/backup.log'
从冷备份中恢复
需要使用该备份时,您需要将PG集群设置为维护模式(pg pause <cluster>
),停止数据集群主库并清空数据集簇目录,然后冷备份文件解压至/pg/data
中,相关命令如下所示。
# 找到最新的备份文件并打印信息
backup_dir="/pg/backup"
data_dir=/pg/data
backup_latest=$(ls -t ${backup_dir} | head -n1)
echo "backup ${backup_latest} will be used"
# 暂停Patroni,关停数据库,移除数据目录(危险)
pg pause pg-meta
pg_ctl -D /pg/data stop
rm -rf /pg/data/* # 清空数据目录(危险)
# 解压备份至数据库目录
echo "unlz4 -d -c ${backup_dir}/${backup_latest} | tar -xC ${data_dir}"
unlz4 -d -c ${backup_dir}/${backup_latest} | tar -xC ${data_dir} # 解压至数据库目录
# 可选:如果加密时设置了密码,则需要先解密再解压
openssl enc -rc4 -d -k ${PASSWORD} -in ${backup_latest} | unlz4 -d -c | tar -xC ${data_dir}
# 重新拉起数据库
systemctl restart patroni
# 重做集群的其他从库
pg reinit <cluster> # 依次重置集群的其他实例成员
您也可以使用冷备份创建新的集群进行PITR,或使用 pg_probackup 与 pg_backrest 等工具进行外部备份管理。
6 - PG 高可用演练
您可以通过高可用场景演练,来加强对集群高可用能力的信心。
以下列出了24种典型的高可用故障场景,分为主库故障,从库故障,DCS故障三类,每类8个具体场景。
所有演练均假设高可用自动切换模式已启用,其中,Patroni应当正确处理主库故障与从库故障。
编号 | 案例名称 | 自动模式 | 手动切换 |
---|---|---|---|
A | 主库故障 | ||
1A | 主库节点宕机 | 自动Failover | 人工切换 |
2A | 主库Postgres进程关停(pg_ctl or kill -9 ) |
自动Failover | 人工重启 |
3A | 主库Patroni进程正常关停(systemctl stop patroni ) |
自动Failover | 人工重启 |
4A | 主库Patroni进程异常关停(kill -9 ) |
需要确认 | 无影响 |
5A | 主库负载打满,假死(watchdog) | 需要确认 | 无影响 |
6A | 主库DCS Agent不可用(systemctl stop consul ) |
集群主库降级 | 无影响 |
7A | 主库网络抖动 | 超时自动Failover | 需观察 |
8A | 误删主库数据目录 | 自动Failover | 手工切换 |
B | 从库故障(1/n , n>1) | ||
1B | 从库节点宕机 | 无影响 | 无影响 |
2B | 从库Postgres进程关停(pg_ctl or kill -9 ) |
无影响 | 无影响 |
3B | 从库Postgres进程手工关停 (pg_ctl ) |
无影响 | 无影响 |
4B | 从库Patroni进程异常Kill(kill -9 ) |
无影响 | 无影响 |
5B | 从库DCS Agent不可用(systemctl stop consul ) |
无影响 | 无影响 |
6B | 从库负载打满,假死 | Depends | Depends |
7B | 从库网络抖动 | 无影响 | 无影响 |
8B | 误提升一个从库(pg_ctl promte ) |
自动恢复 | 脑裂 |
C | DCS故障 | ||
1C | DCS Server完全不可用(多数节点不可用) | 所有集群主库降级 | 无影响 |
2C | DCS通主库,不通从库(1主1从) | 无影响 | 无影响 |
3C | DCS通主库,不通从库(1主n从,n>1) | 无影响 | 无影响 |
4C | DCS通从库,不通主库(1主1从) | 无影响 | 无影响 |
5C | DCS通从库,不通主库(1主n从,n>1) | 自动Failover | 无影响 |
6C | DCS网络抖动:同时中断, 主库从库同时恢复,或主库先恢复 |
无影响 | 无影响 |
7C | DCS网络抖动:同时中断, 从库先恢复,主库后恢复(1主1从) |
无影响* | 无影响 |
8C | DCS网络抖动:同时中断, 从库先恢复,主库后恢复(1主n从,n>1) |
超过TTL自动Failover | 无影响 |
演练环境说明
以下以本地Pigsty 四节点沙箱作为演练对象。
准备负载
在演练中,您可以使用pgbench
生成虚拟负载,观察负载流量在各种故障下的状态。
make test-ri # 在 pg-test集群初始化 pgbench 表
make test-rw # 生成 pgbench 写入流量
make test-ro # 生成 pgbench 只读流量
如果您希望仿真其他样式的流量,可以直接调整负载生成的命令并执行。
# 4条连接,总计64读写TPS
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://test:test@pg-test:5433/test; done
# 8条连接,总计512只读TPS
while true; do pgbench -nv -P1 -c8 --select-only --rate=512 -T10 postgres://test:test@pg-test:5434/test; done
观察状态
PGSQL Cluster 面板提供了关于pg-test
集群的重要监控信息,您可以查阅最近5-15分钟的指标,并设置为每5秒自动刷新。
Pigsty的监控指标采集周期默认为10秒,而Patroni主从切换的典型耗时通常在几秒到十几秒之间。您可以使用patronictl
来获取亚秒级别的观测精度:
pg list pg-test # 查看 pg-test 集群状态(在单独的窗口中)
pg list pg-test -w 0.1 # 查看 pg-test 集群状态,每0.1s刷新一次
您可以开启四个Terminal窗口,分别用于:
- 在元节点上执行管理命令(用来触发模拟故障的命令)
- 发起并观察读写请求负载(
pgbench
) - 发起并观察只读请求负载(
pgbench --select-only
) - 实时查阅集群主从状态(
pg list
)
主库故障演练
1A-主库节点宕机
操作说明
ssh 10.10.10.3 sudo reboot # 直接将 pg-test-1 主节点重启(VIP指向实际主节点)
操作结果
Patroni可以正常处理主库宕机,执行自动Failover。
当集群处于维护模式时,则需要人工介入处理(人工执行pg failover <cluster>
)
patronictl list 结果
# 正常情况:pg-test-3 是当前集群主库,时间线为3(此集群已经经历过两次Failover)
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Leader | running | 3 | | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# ssh 10.10.10.13 sudo reboot 将 pg-test-3 主实例所在节点重启,pg-test-3 实例的Patroni从集群中消失
# 下线超过TTL后,pg-test-1实例抢到Leader Key,成为新的集群领导者。
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 3 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-1实例完成Promote,成为集群的新领导者,时间线变为4
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-2 实例将自己的上游修改为新领导者 pg-test-1 ,时间线由3变为4,进入新时代,看齐新核心。
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 4 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-3 完成重启,Postgres处于停止状态,Patroni重新加入集群中
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 4 | 1 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | stopped | | unknown | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-3 上的Postgres以从库身份被拉起,从新主库 pg-test-1 同步数据。
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 4 | 1 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 3 | 10 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-3 追赶上新领导者,时间线进入4,与新领导保持同步。
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 4 | 1 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 4 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
2A-主库Postgres进程关停
操作说明
采用两种不同的方式关停主库 Postgres 实例:常规的 pg_ctl
与暴力的 kill -9
# 关停主库上的Postgres主进程
ssh 10.10.10.3 'sudo -iu postgres /usr/pgsql/bin/pg_ctl -D /pg/data stop'
# 查询主库PID并强行Kill
ssh 10.10.10.3 'sudo kill -9 $(sudo cat /pg/data/postmaster.pid | head -n1)'
操作结果
关停 Postgres 后,Patroni 会尝试重新拉起 Postgres 进程。如果成功,则集群恢复正常。
如果无法正常拉起 PostgreSQL 进程,则集群会自动进行Failover。
patronictl list 结果
# 主库实例被强制Kill后,状态显示为crashed,而后立刻被重新拉起,恢复为Running
+ Cluster: pg-test (7037005266924312648) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | crashed | | | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 7 | 0 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 7 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 如果持续Kill主库,导致主库拉起失败(状态变为start failed),那么就会触发Failover
+ Cluster: pg-test (7037005266924312648) ----------+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+--------------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 11 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Leader | running | 12 | | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | start failed | | unknown | clonefrom: true |
+-----------+-------------+---------+--------------+----+-----------+-----------------+
3A-主库Patroni进程正常关停
操作说明
# 关停主库上的Postgres主进程
ssh 10.10.10.3 'sudo systemctl stop patroni'
操作结果
通过常规方式关停主库Patroni,会导致Patroni所管理PostgreSQL实例一并关闭,并立即触发集群Failover。
在维护模式下通过正常方式关停Patroni,关闭Patroni不会影响所托管的PostgreSQL实例,这可以用于重启Patroni以重载配置(例如更换使用的DCS)。
patronictl list 结果
# 主库Patroni (pg-test-3) 关停后
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 2 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 2 | 0 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Leader | running | 2 | | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# pg-test-3 进入 stopped 状态
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 2 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Replica | running | 2 | 0 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | stopped | | unknown | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 新主库 pg-test-2 当选,时间线从2进入3
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 2 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Leader | running | 3 | | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | stopped | | unknown | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 另一个健康从库 pg-test-1 重新追随新主库 pg-test-2 进入时间线3,老主库 pg-test-3 在一段时间后,从集群中消失
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Leader | running | 3 | | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 使用 systemctl start patroni 重新拉起老主库 pg-test-3,该实例自动进入复制模式,追随新领导者。
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Leader | running | 3 | | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
4A-主库Patroni进程异常关停
这种情况需要特别关注!
如果使用Kill -9 强行杀死主库Patroni,则主库Patroni有大概率无法关停所管理的PostgreSQL主库实例。这会导致原主库PostgreSQL 实例在Patroni死亡后继续存活,而剩余的集群从库则会进行领导选举选出新的主库来,从而导致脑裂。
操作说明
# 关停主库上的Patroni主进程
ssh 10.10.10.3 "ps aux | grep /usr/bin/patroni | grep -v grep | awk '{print $2}'"
ssh 10.10.10.3 'sudo kill -9 723'
操作结果
该操作可能导致集群脑裂:因为Patroni暴死,无暇杀死自己管理的PostgreSQL进程。而其他集群成员则会在TTL超时后进行新一轮选举,选出新的主库。
如果您采用标准的基于负载均衡健康检查的服务接入机制,不会有问题,因为原主库 Patroni已死,健康检查为假。即使该主库存活,负载均衡器也不会将流量分发至此实例。但如果您通过其他方式继续写入该主库,则可能会出现脑裂!
Patroni使用Watchdog机制对这种情况进行兜底,您需要视情况使用(参数 patroni_watchdog_mode
)。启用watchdog时,如果原主库因为各种原因(Patroni暴死,机器负载假死,虚拟机调度,PG关机太慢)等原因,无法在Failover中及时关停PG主库以避免脑裂,则会使用Linux内核模块softdog
强制关机以免脑裂。
patronictl list 结果
# 使用Kill -9 强杀 主库Patroni (pg-test-2)
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-2 | 10.10.10.12 | Leader | running | 3 | | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 因为Patroni暴死,PostgreSQL进程通常仍然会存活并且为主库状态
# 因为Patroni暴死,原主库的健康检查会立刻失败,导致主库流量没有实例承载,集群不可写入。
# 因为Patroni暴死,无暇释放 DCS中的Leader Key,因此上面的状态会保持TTL的时间。
# 直到 DCS 中的 Leader Lease 因为超时被释放(约15s),集群才意识到主库已死,发起Failover
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | running | 3 | 0 | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 集群触发Failover,pg-test-1 成为新的集群领导者,开始承载只读流量,集群写入服务恢复
+ Cluster: pg-test (7037370797549923387) -----+----+-----------+-----------------+
| Member | Host | Role | State | TL | Lag in MB | Tags |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader | running | 4 | | clonefrom: true |
| pg-test-3 | 10.10.10.13 | Replica | running | 3 | 0 | clonefrom: true |
+-----------+-------------+---------+---------+----+-----------+-----------------+
# 此时必须注意!原集群主库仍然存活并允许写入!!!
# 如果您采用标准的基于负载均衡健康检查的流量分发机制则不会有问题,因为Patroni已死,健康检查为假。
# 该主库存活,但负载均衡器不会将流量分发至此实例。但如果您通过其他方式直接写入该主库,则会出现脑裂!
$ psql -AXtwh 10.10.10.12 -d postgres -c 'select pg_is_in_recovery();'
t
从这种情况中恢复
当Patroni暴死时,您应当首先手工关闭由其管理的,仍在运行的原PostgreSQL主库实例。然后再重新启动Patroni,并由Patroni拉起PostgreSQL实例,如下所示:
/usr/pgsql/bin/pg_ctl -D /pg/data stop
systemctl restart patroni
如若不然,则可能出现Patroni无法正常启动的错误:
2021-12-03 14:16:18 +0800 INFO: stderr=2021-12-03 14:16:18.752 HKT [7852] FATAL: lock file "postmaster.pid" already exists
2021-12-03 14:16:18.752 HKT [7852] HINT: Is another postmaster (PID 887) running in data directory "/pg/data"?
参数
patroni_watchdog_mode
的说明:
- 如果模式为
required
,但/dev/watchdog
不可用,不会影响Patroni启动,只会影响当前实例的领导候选人资格。- 如果模式为
required
,但/dev/watchdog
不可用,那么该实例无法作为合格的主库候选人,即无法参与Failover,即使手工强制指定也不行:会出现Switchover failed, details: 412, switchover is not possible: no good candidates have been found
的错误。若想解决此问题,修改/pg/bin/patroni.yml
文件的patroni_watchdog
选项为automatic|off
即可。- 如果模式为
automatic
,则没有限制,无论/dev/watchdog
可不可用,该实例都可以正常参选主库选举。/dev/watchdog
可用需要两个条件,加载softdog
内核模块,/dev/watchdog
的属主为postgres
(dbsu)
5A-主库DCS Agent不可用
在这种情况下,主库上的Patroni会因为无法连接至DCS服务,将自身降级为普通从库,但如果从库Patroni仍然意识到主库存活(例如,流复制仍然正常进行),并不会触发Failover!
在这种情况下,Pigsty的接入机制会因为原主库健康检查为假,而导致整个集群进入无主状态,无法写入,需要特别关注!
在维护模式下,不会有变化发生。
6A-主库负载打满,假死
TBD
7A-主库网络抖动
8A-误删主库数据目录
从库故障演练
1B-从库节点宕机
操作说明
ssh 10.10.10.3 sudo reboot # 直接将 pg-test-1 主节点重启(VIP指向实际主节点)
操作结果
从库宕机会导致该节点上的 HAPorxy
Patroni
,Postgres
等服务不可用。通常业务侧会察觉到极少量的瞬时报错(与故障实例的连接会中断),而后集群中的其他负载均衡器会将此故障节点从后端列表中摘除。
请注意,如果集群为一主一从结构,且唯一一台从库宕机,那么离线查询服务可能会受到影响(没有可用承载实例)。
节点重启完成后,Patroni服务会自动拉起,实例会自动重新加入集群中。
2B-从库Postgres进程关停
操作说明
采用两种不同的方式关停从库 Postgres 实例:常规的 pg_ctl
与暴力的 kill -9
# 关停从库上的Postgres主进程
ssh 10.10.10.3 'sudo -iu postgres /usr/pgsql/bin/pg_ctl -D /pg/data stop'
# 查询从库PID并强行Kill
ssh 10.10.10.3 'sudo kill -9 $(sudo cat /pg/data/postmaster.pid | head -n1)'
操作结果
关停 Postgres 后,Patroni 会尝试重新拉起 Postgres 进程。如果成功,则集群恢复正常。如果
从库 宕机会导致该实例健康检查为Down,集群的负载均衡器不会将流量再分发至该实例,应用只读请求会有少量瞬时报错。
3B-从库Postgres进程手工关停
4B-从库Patroni进程异常Kill
5B-从库DCS Agent不可用
6B-从库负载打满,假死
7B-从库网络抖动
8B-误提升一个从库
DCS故障演练
1C-DCS Server完全不可用
DCS完全不可用是一个极其严重的故障,默认情况下将导致所有数据库集群不可写入。 如果您使用L2 VIP接入,则默认绑定于主库节点的L2 VIP亦不可用,这意味着整集群可能都无法读写!您应当尽全力避免此种故障!
好在DCS本身便是为了解决此问题而生:本身采用分布式架构,并有可靠的容灾机制,能容忍各种常见的硬件故障。例如,3节点的DCS集群允许一台服务器出现故障,而5节点的DCS集群则最多允许两个服务器节点同时出现故障。
有一些方式可以缓解此问题。
关停 Consul 后,所有 启用高可用自动切换模式的数据库集群主库会触发降级逻辑(因为主库的Patroni意识不到其他集群成员的存在,须假定其他从库已经构成一个法定多数的分区并进行选举,因而要将自身降级为从库避免脑裂)
操作说明
关停元节点上的DCS Server,如果有3台,至少应当关停2台,如果有5台,至少应当关停3台。
systemctl stop consul
解决方案
- 在维护模式下,用户失去了自动Failover的能力,但DCS故障不会导致主库不可写入。(仍可以手工快速切换)
- 使用更多的DCS实例确保DCS的可用性(DCS本身便是为了解决此问题而生)
- 为Patroni配置足够长的超时重试时间,并为DCS故障设置最高的响应优先级
2C-DCS通主库,不通从库(1主1从)
3C-DCS通主库,不通从库(1主n从,n>1)
4C-DCS通从库,不通主库(1主1从)
5C-DCS通从库,不通主库(1主n从,n>1)
6C-DCS网络抖动:同时中断,主库从库同时恢复,或主库先恢复
7C-DCS网络抖动:同时中断,从库先恢复,主库后恢复(1主1从)
8C-DCS网络抖动:同时中断,从库先恢复,主库后恢复(1主n从,n>1)
7 - PG 慢查询优化
下面以Pigsty自带的沙箱环境为例,介绍一个使用Pigsty监控系统处理慢查询的过程。
慢查询:模拟
因为没有实际的业务系统,这里我们以一种简单快捷的方式模拟系统中的慢查询。即pgbench自带的tpc-c
。
在主库上执行以下命令
ALTER TABLE pgbench_accounts DROP CONSTRAINT pgbench_accounts_pkey ;
该命令会移除 pgbench_accounts 表上的主键,导致相关查询变慢,系统瞬间雪崩过载。
图1:单个从库实例的QPS从500下降至7,Query RT下降至300ms
图2:系统负载达到200%,触发机器负载过大,与查询响应时间过长的报警规则。
慢查询:定位
首先,使用PG Cluster面板定位慢查询所在的具体实例,这里以 pg-test-2为例
然后,使用PG Query面板定位具体的慢查询:编号为 -6041100154778468427
图3:从查询总览中发现异常慢查询
该查询表现出:
- 响应时间显著上升: 17us 升至 280ms
- QPS 显著下降: 从500下降到 7
- 花费在该查询上的时间占比显著增加
可以确定,就是这个查询变慢了!
接下来,利用PG Stat Statements面板或PG Query Detail,根据查询ID定位慢查询的具体语句。
图4:定位的查询是
SELECT abalance FROM pgbench_accounts WHERE aid = $1
慢查询:猜想
接下来,我们需要推断慢查询产生的原因。
SELECT abalance FROM pgbench_accounts WHERE aid = $1
该查询以 aid
作为过滤条件查询 pgbench_accounts
表,如此简单的查询变慢,大概率是这张表上的索引出了问题。
用屁股想都知道是索引少了,因为就是我们自己删掉的嘛!
分析查询后提出猜想: 该查询变慢是pgbench_accounts
表上aid
列缺少索引
下一步,查阅 PG Table Detail 面板,检查 pgbench_accounts
表上的访问,来验证我们的猜想
图5:
pgbench_accounts
表上的访问情况
通过观察,我们发现表上的索引扫描归零,与此同时顺序扫描却有相应增长。这印证了我们的猜想!
慢查询:解决
确定了问题根源后,我们将着手解决。
尝试在 pgbench_accounts
表上为 aid
列添加索引,看看能否解决这个问题。
加上索引后,神奇的事情发生了。
图6:可以看到,查询的响应时间与QPS已经恢复正常。
图7:系统的负载也恢复正常
慢查询:样例
通过这篇教程,您已经掌握了慢查询优化的一般方法论。
图8:一个慢查询优化的实际例子,将系统的饱和度从40%降到了4%
8 - PG 数据库迁移
本文基于Pigsty沙箱中的实例,介绍基于逻辑复制进行主从切换与数据库迁移的原理,细节与注意事项。
逻辑复制相关基础知识可参考 Postgres逻辑复制详解 一文。
0 逻辑复制迁移
逻辑复制通常可用于跨大版本跨操作系统在线升级PostgreSQL,例如从PG 10到PG 13,从Windows到Linux。
0.1 逻辑迁移的优点
相比原地pg_upgrade
升级与pg_dump
升级,逻辑复制的好处有:
- 在线:迁移可以在线进行,不需要或者只需要极小的停机窗口。
- 灵活:目标库的结构可以与源库不同,例如普通表改为分区表,加列等。可以跨越大版本使用。
- 安全:相比物理复制,目标库是可写的,因此在最终切换前,可以随意进行测试并重建。
- 快速:停机窗口很短,可以控制在秒级到分钟级。
0.2 逻辑迁移的局限性
逻辑复制的局限性主要在于设置相对繁琐,初始时刻拷贝数据较物理复制更慢,对于单实例多DB的情况需要迁移多次。大对象与序列号需要在迁移时手动同步。
-
不能复制DDL变更
-
不能复制序列号(Sequence)
-
如果逻辑从库上某张被外键引用的表被Truncate,但因为引用该表的表不在订阅集中(所以无法在不truncate该表的情况下继续,但在订阅集之外的表上执行truncate违反语义),那么就会出现冲突。
-
大对象无法复制。
-
只支持普通表的复制,包括分区表。不支持视图,物化视图,外部表。
总体来说都,属于可以解决或可以容忍的问题。
0.3 逻辑迁移的基本流程
整体上讲,基于逻辑复制的迁移遵循以下步骤:
其中准备工作与存量迁移部分耗时较长,但不需要停机,不会对生产业务产生影响。
切换时刻需要短暂的停机窗口,采用自动化的脚本可以将停机时间控制在秒级到分钟级。
下面将基于Pigsty沙箱介绍这些步骤涉及到的具体细节。
1 准备工作
1.1 准备源宿集群
在进行迁移之前,首先要确定迁移的源端集群与目标集群配置正确。
Pigsty标准沙箱由四个节点与两套数据库集群构成。
两套数据库集群pg-meta
与pg-test
将分别作为逻辑复制的源端(SRC)与宿端(DST)。
本例将pg-meta-1
作为发布者,pg-test-1
作为订阅者,将pgbench
相关表从pg-meta
迁移至pg-test
。
1.1.1 用户
迁移通常需要在原宿两端拥有两个用户,分别用于管理与复制。
CREATE USER dbuser_admin SUPERUSER; -- 超级用户用于创建发布与订阅
CREATE USER replicator REPLICATION BYPASSRLS; -- 复制用户用于订阅变更
1.1.2 HBA规则
同时,还需要配置相应的HBA规则,允许复制用户在原宿集群间相互访问。
此外,迁移通常会从中控机发起,应当允许管理用户从中控机访问原/宿集群。
因为创建订阅需要超级用户权限,建议为管理用户(永久或临时)配置SUPERUSER
权限。
1.1.3 配置项
必选的配置项是wal_level
,您必须在源端将wal_level
配置为logical
,方能启用逻辑复制。
其他一些关于复制的相关参数也需要合理配置,但除了wal_level
外的参数默认值都不会影响逻辑复制正常工作,均为可选。
推荐在源端与宿端使用相同的配置项,下面是在64核机器上,一些相关配置的参考值:
wal_level: logical # MANDATORY!
max_worker_processes: 64 # default 8 -> 64, set to CPU CORE 64
max_parallel_workers: 32 # default 8 -> 32, limit by max_worker_processes
max_parallel_maintenance_workers: 16 # default 2 -> 16, limit by parallel worker
max_parallel_workers_per_gather: 0 # default 2 -> 0, disable parallel query on OLTP instance
# max_parallel_workers_per_gather: 16 # default 2 -> 16, enable parallel query on OLAP instance
max_wal_senders: 24 # 10 -> 24
max_replication_slots: 16 # 10 -> 16
max_logical_replication_workers: 8 # 4 -> 8, 6 sync worker + 1~2 apply worker
max_sync_workers_per_subscription: 6 # 2 -> 6, 6 sync worker
对于数据库来说,通常还需要关注数据库的 编码(encoding)与 本地化 (locale)配置项是否正确,通常建议统一使用C.UTF8
。
1.1.4 连接信息
为了执行管理命令,您需要通过连接串访问原/宿集群的主库。
建议不要在连接串中使用明文密码,密码可以通过~/.pgpass
,~/.pg_service
,环境变量等方式管理,下面使用时将不会列出密码。
PGSRC='postgres://dbuser_admin@10.10.10.10/meta' # 源端发布者 (SU)
PGDST='postgres://dbuser_admin@10.10.10.11/test' # 宿端订阅者 (SU)
建议在中控机/元节点上执行迁移命令,并在操作过程中保持上面两个变量生效。
1.2 确定迁移对象
相比于物理复制,逻辑复制允许用户对复制的内容与过程施加更为精细的控制。您可以选择数据库内容的一个子集进行复制。不过在这个例子中,我们将进行整库复制。
在本例中,我们采用pgbench
提供的用例作为迁移标的。因此可以在源端集群使用pgbench
初始化相关表项。
pgbench -is64 ${PGSRC}
此外,考虑到测试的覆盖范围,我们还将创建一张额外的测试数据表(用于测试Sequence的迁移)
psql ${PGSRC} -qAXtw <<-EOF
DROP TABLE IF EXISTS pgbench_extras;
CREATE TABLE IF NOT EXISTS pgbench_extras
(id BIGSERIAL PRIMARY KEY,v TIMESTAMP NOT NULL UNIQUE);
EOF
要注意,只有 基本表 (包括分区表)可以参与逻辑复制,其他类型的对象,包括 视图,物化视图,外部表,索引,序列号都无法加入到逻辑复制中。使用以下查询,可以列出当前数据库中可以加入逻辑复制的表的完全限定名。
SELECT quote_ident(nspname) || '.' || quote_ident(relname) AS name
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema', 'monitor', 'repack', 'pg_toast')
在准备阶段,您需要筛选出希望进行复制的表。在存量迁移中将这些表的结构定义同步至宿集群中,并建立在这些表上的逻辑复制。
1.3 修复复制标识
并不是所有的表都可以直接纳入逻辑复制中并正常工作。在进行迁移前,您需要对所有待迁移的表进行检查,确认它们都已经正确配置了复制标识。
复制身份模式\表上的约束 | 主键(p) | 非空唯一索引(u) | 两者皆无(n) |
---|---|---|---|
default | 有效 | x | x |
index | x | 有效 | x |
full | 低效 | 低效 | 低效 |
nothing | x | x | x |
-
如果表上有主键,则会默认使用
REPLICA IDENTITY default
,这是最好的,不用进行任何修改。 -
如果表上没有主键,有条件的话请创建一个,没有条件的话,一个建立在非空列集上的唯一索引也可以起到同样的作用。在这种情况下需要显式的为表配置
REPLICA IDENTITY USING <tbl_unique_key_idx_name>
。 -
如果表上既没有主键,也没有唯一索引,那么您可以为表配置
REPLICA IDENTITY FULL
,将完整的一行作为复制标识。使用
FULL
身份标识的性能非常差,发布侧和订阅侧的删改操作都会导致顺序扫表,建议只将其作为保底手段使用。另一种选择是为表配置
REPLICA IDENTITY NOTHING
,这样任何在发布端对此表进行UPDATE|DELETE
操作都会直接报错中止。
使用以下查询,可以列出所有表的完全限定名,复制标识配置,以及表上是否有主键或唯一索引,
SELECT quote_ident(nspname) || '.' || quote_ident(relname) AS name, con.ri AS keys,
CASE relreplident WHEN 'd' THEN 'default' WHEN 'n' THEN 'nothing' WHEN 'f' THEN 'full' WHEN 'i' THEN 'index' END AS identity
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid, LATERAL (SELECT array_agg(contype) AS ri FROM pg_constraint WHERE conrelid = c.oid) con
WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema', 'monitor', 'repack', 'pg_toast')
ORDER BY 2,3;
以1.2的测试场景为例:
name | keys | identity
-------------------------+-------+----------
public.spatial_ref_sys | {c,p} | default
public.pgbench_accounts | {p} | default
public.pgbench_branches | {p} | default
public.pgbench_tellers | {p} | default
public.pgbench_extras | {p,u} | default
public.pgbench_history | NULL | default
如果表上只有唯一索引,例如您需要检查该唯一索引是否满足要求:所有列都为非空,not deferrable
,not partial
,如果满足,则可以使用以下命令将表的复制身份修改为index
模式。
-- 一个例子:即使pgbench_extras上有主键,但也可以使用唯一索引作为身份标识
ALTER TABLE pgbench_extras REPLICA IDENTITY USING INDEX pgbench_extras_v_key;
如果表上没有主键,也没有唯一约束。如上面的pgbench_history
表,那就需要通过以下命令将其复制身份设置为FULL|NOTHING
。
ALTER TABLE pgbench_history REPLICA IDENTITY FULL;
完成修复后,所有表都应当具有合适的复制身份:
name | keys | identity
-------------------------+-------+----------
public.spatial_ref_sys | {c,p} | default
public.pgbench_accounts | {p} | default
public.pgbench_branches | {p} | default
public.pgbench_tellers | {p} | default
public.pgbench_extras | {p,u} | index
public.pgbench_history | NULL | full
2 存量迁移
2.1 同步数据库模式
2.1.1 转储
使用以下命令转储所有对象定义,并复制到宿端应用。
pg_dump ${PGSRC} --schema-only -n public | psql ${PGDST}
可以通过pg_dump
的-n
,-t
参数进行灵活控制,只转储所需的对象。例如,如果只需要public
模式下pgbench
的相关表,则可以通过以下命令转储:
pg_dump ${PGSRC} --schema-only -n public -t 'pgbench_*' | psql ${PGDST}
2.1.2 校验
同步完成后,通常需要进行模式校验。
- 所有目标表及其索引、序列号是否已经建立
- 函数、类型、模式、用户、权限是否均符合预期?
数据库模式需要根据用户自己的需求进行同步与校验,没有什么通用的方式。
2.2 在源端创建发布
源端集群主库作为发布者,需要创建发布,将所需的表加入到发布集中。
2.2.1 创建发布的方式
创建发布的语法如下所示:
CREATE PUBLICATION name
[ FOR TABLE [ ONLY ] table_name [ * ] [, ...]
| FOR ALL TABLES ]
[ WITH ( publication_parameter [= value] [, ... ] ) ]
针对所有表创建发布(需要超级用户权限):
CREATE PUBLICATION "pg_meta_pub" FOR ALL TABLES;
注意无论是发布还是订阅,名称都建议遵循PostgreSQL对象标识符命名规则([a-z][0-9a-z_]+
),特别是不要在名称中使用-
。以免不必要的麻烦,例如创建订阅同名的复制槽因命名不规范而失败。
如果需要控制订阅的事件类型(不常见),可以通过参数publish
指定,默认为insert, update, delete, truncate
。
如果源端上有分区表,有一个参数可以用于控制其复制行为。把分区表当成一张表(使用分区根表的复制标识),还是当成多张子表(使用子表上的复制标识)来处理。启用这个选项可以把分区表在逻辑上看成一张表(分区根表),而不是一系列的分区子表,所以订阅端只需要存在一张分区根表的同名表即可正常复制,这是13版本引入的新选项。该选项默认为false
,也就是说逻辑复制分区表时,源端的每一个分区都必须在订阅端存在。
额外的参数可以通过以下的形式传入:
CREATE PUBLICATION "pg_meta_pub" FOR ALL TABLES
WITH(publish = 'insert', publish_via_partition_root = true);
2.2.2 发布的内容
如果不希望发布所有的表,则可以在发布中具体指定所需的表名称。
例如在这个例子中spatial_ref_sys
是一张postgis
扩展使用的常量表,并不需要迁移,我们可以将其排除。利用以下SQL,可以直接在数据库中拼接出创建发布的SQL命令:
SELECT E'CREATE PUBLICATION pg_meta_pub FOR TABLE\n' ||
string_agg(quote_ident(nspname) || '.' || quote_ident(relname), E',\n') || ';' AS sql
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema', 'monitor', 'repack', 'pg_toast')
AND relname ~ 'pgbench'; -- 只复制表名形如 pgbench* 的表
\gexec -- 在psql中执行上面命令生成的SQL语句
在这个例子中,实际生成并执行的命令如下:
psql ${PGSRC} -Xtw <<-EOF
CREATE PUBLICATION pg_meta_pub FOR TABLE
public.pgbench_accounts,
public.pgbench_branches,
public.pgbench_tellers,
public.pgbench_history,
public.pgbench_extras;
EOF
2.2.3 确认发布状态
建立完发布后,可以从 pg_publication
视图看到所创建的发布。
$ psql ${PGSRC} -Xxwc 'table pg_publication;'
-[ RECORD 1 ]+------------
oid | 24679
pubname | pg_meta_pub
pubowner | 10
puballtables | f
pubinsert | t
pubupdate | t
pubdelete | t
pubtruncate | t
pubviaroot | f
可以从pg_publication_tables
确认纳入到发布中的表有哪些。
$ psql ${PGSRC} -Xwc 'table pg_publication_tables;'
pubname | schemaname | tablename
-------------+------------+------------------
pg_meta_pub | public | pgbench_history
pg_meta_pub | public | pgbench_tellers
pg_meta_pub | public | pgbench_accounts
pg_meta_pub | public | pgbench_branches
pg_meta_pub | public | pgbench_extras
确认无误后,发布端的工作完成。接下来要在宿端集群主库上创建订阅,订阅源端集群主库上的这个发布。
2.3 在宿端创建订阅
宿端集群主库作为订阅者,需要创建订阅,从发布者上订阅所需的变更。
2.3.1 创建订阅
创建订阅需要SUPERUSER
权限,创建订阅的语法如下所示:
CREATE SUBSCRIPTION subscription_name
CONNECTION 'conninfo'
PUBLICATION publication_name [, ...]
[ WITH ( subscription_parameter [= value] [, ... ] ) ]
创建订阅必须使用CONNECTION
子句指定发布者的连接信息,通过PUBLICATION
子句指定发布名称。这里使用replicator
用户连接发布者,该用户的密码已经写入宿端实例下~/.pgpass
,因此这里可以在连接串中省去。
创建订阅还有一些其他的参数,通常只有手动管理复制槽时才需要修改这些参数:
copy_data
,默认为true
,当复制开始时,是否要复制全量数据。create_slot
,默认为true
,该订阅是否会在发布实例上创建复制槽。enabled
,默认为true
,是否立即开始订阅。connect
,默认为true
,是否连接至订阅实例,如果不连接,上面几个选项都会被重置为false
。
这里,创建订阅的实际命令为:
psql ${PGDST} -Xtw <<-EOF
CREATE SUBSCRIPTION "pg_test_sub"
CONNECTION 'host=10.10.10.10 user=replicator dbname=meta'
PUBLICATION "pg_meta_pub";
EOF
2.3.2 订阅状态确认
成功创建订阅后,可以从 pg_subscription
视图看到所创建的发布。
$ psql ${PGDST} -Xxwc 'TABLE pg_subscription;'
-[ RECORD 1 ]---+---------------------------------------------
oid | 20759
subdbid | 19351
subname | pg_test_sub
subowner | 16390
subenabled | t
subconninfo | host=10.10.10.10 user=replicator dbname=meta
subslotname | pg_test_sub
subsynccommit | off
subpublications | {pg_meta_pub}
可以从pg_subscription_rel
中确认哪些表被纳入到订阅的范围,及其复制状态。
$ psql ${PGDST} -Xwc 'table pg_subscription_rel;'
srsubid | srrelid | srsubstate | srsublsn
---------+---------+------------+------------
20759 | 20742 | r | 0/B0BC1FB8
20759 | 20734 | r | 0/B0BC20B0
20759 | 20737 | r | 0/B0BC20B0
20759 | 20745 | r | 0/B0BC20B0
20759 | 20731 | r | 0/B0BC20B0
2.4 等待逻辑复制同步
创建订阅后,首先必须监控 发布端与订阅端两侧的数据库日志,确保没有错误产生。
2.4.1 逻辑复制状态机
如果一切正常,逻辑复制会自动开始,针对每张订阅中的表执行复制状态机逻辑,如下图所示。
当所有的表都完成复制,进入r
(ready)状态时,逻辑复制的存量同步阶段便完成了,发布端与订阅端整体进入同步状态。
当创建或刷新订阅时,表会被加入到 订阅集 中,每一张订阅集中的表都会在pg_subscription_rel
视图中有一条对应纪录,展示这张表当前的复制状态。刚加入订阅集的表初始状态为i
,即initialize
,初始状态。
如果订阅的copy_data
选项为真(默认情况),且工作进程池中有空闲的Worker,PostgreSQL会为这张表分配一个同步工作进程,同步这张表上的存量数据,此时表的状态进入d
,即拷贝数据中。对表做数据同步类似于对数据库集群进行basebackup
,Sync Worker会在发布端创建临时的复制槽,获取表上的快照并通过COPY完成基础数据同步。
当表上的基础数据拷贝完成后,表会进入sync
模式,即数据同步,同步进程会追赶同步过程中发生的增量变更。当追赶完成时,同步进程会将这张表标记为r
(ready)状态,转交逻辑复制主Apply进程管理变更,表示这张表已经处于正常复制中。
2.4.2 同步进度跟踪
数据同步(d
)阶段可能需要花费一些时间,取决于网卡,网络,磁盘,表的大小与分布,逻辑复制的同步worker数量等因素。
作为参考,1TB的数据库,20张表,包含有250GB的大表,双万兆网卡,在6个数据同步worker的负责下大约需要6~8小时完成复制。
在数据同步过程中,每个表同步任务都会源端库上创建临时的复制槽。请确保逻辑复制初始同步期间不要给源端主库施加过大的不必要写入压力,以免WAL撑爆磁盘。
发布侧的 pg_stat_replication
,pg_replication_slots
,订阅端的pg_stat_subscription
,pg_subscription_rel
提供了逻辑复制状态的相关信息,需要关注。
psql ${PGDST} -Xxw <<-'EOF'
SELECT subname, json_object_agg(srsubstate, cnt) FROM
pg_subscription s JOIN
(SELECT srsubid, srsubstate, count(*) AS cnt FROM pg_subscription_rel
GROUP BY srsubid, srsubstate) sr
ON s.oid = sr.srsubid GROUP BY subname;
EOF
可以使用以下SQL确认订阅中表的状态,如果所有表的状态都显示为r
,则表示逻辑复制已经成功建立,订阅端可以用于切换。
subname | json_object_agg
-------------+-----------------
pg_test_sub | { "r" : 5 }
当然,最好的方式始终是通过监控系统来跟踪复制状态。
3 切换时刻
3.1 准备工作
一个良好的工程实践是,在搞事情之前,在源端宿端都执行几次存盘操作,避免后续操作因被内存刷盘拖慢。
也可以执行分析命令更新统计信息,便于后续快速对比校验数据完整性。
psql ${PGSRC} -Xxwc 'CHECKPOINT;ANALYZE;CHECKPOINT;'
psql ${PGSRC} -Xxwc 'CHECKPOINT;ANALYZE;CHECKPOINT;'
在此之后的操作,都处于服务不可用状态,因此尽可能快地进行。通常情况下在分钟级内完成较为合适。
3.2 停止源端写入流量
3.2.1 选择合适的停止方式
暂停源端写入有多种方式,请根据实际业务场景选择与组合:
- 告知业务方停止流量
- 停止解析源端主库域名
- 停止或暂停负载均衡器(Haproxy | VIP)的流量转发
- 停止或暂停连接池Pgbouncer
- 停止或暂停Postgres实例
- 修改数据库主库的参数,设置默认事务模式为只读。
- 修改数据库主库的HBA规则,拒绝业务访问。
通常建议使用修改HBA,修改连接池,修改负载均衡器的方式停止主库的写入流量。
请注意,无论使用何种方式,建议保持PostgreSQL存活,并且管理用户和复制用户仍然可以连接到源端主库。
3.2.2 确认源端写入流量停止
当源端主库停止接受写入后,首先执行确认逻辑,通过观察pg_stat_replication
,确认逻辑订阅者已经与发布者保持同步。
psql ${PGSRC} -Xxw <<-EOF
SELECT application_name AS name,
pg_current_wal_lsn() AS lsn,
pg_current_wal_lsn() - replay_lsn AS lag
FROM pg_stat_replication;
EOF
-[ RECORD 1 ]-----
name | pg_test_sub
lsn | 0/B0C24918
lag | 0
重复执行上述命令,如果lsn
字段保持不变,lag
始终为0
,就说明主库的写入流量已经正确停止,且逻辑从库上已经没有复制延迟,可以用于切换。
3.2.3 建立反向逻辑复制(可选)
如果要求迁移失败后业务可以随时回滚,可以在停止源端写入流量后,设置反向的逻辑复制,将后续订阅端(新主库)的变更反向同步至原来的发布端(旧主库)。不过此过程需要重新同步数据,耗时太久。通常情况下,只有在数据非常重要,且数据量不大或停机窗口足够长的情况下才适用于此方法。
首先停止宿端现有的逻辑订阅。必须停止现有逻辑复制才能继续后面的步骤,否则会形成循环复制。
停止源端写入流量后,继续维持逻辑复制没有意义,因此可以停止宿端的订阅。但建议保留该订阅,只是禁用它,以备迁移失败回滚。
psql ${PGDST} -qAXtwc 'ALTER SUBSCRIPTION pg_test_sub DISABLE;'
然后依照上述流程重新建立 反向的逻辑复制,这里只给出命令:
# 在宿端创建发布:pg_test_pub
psql ${PGDST} -Xtw <<-EOF
CREATE PUBLICATION pg_test_pub FOR TABLE
public.pgbench_accounts,
public.pgbench_branches,
public.pgbench_tellers,
public.pgbench_history,
public.pgbench_extras;
TABLE pg_publication;
EOF
# 在源端创建订阅
psql ${PGSRC} -Xtw <<-EOF
CREATE SUBSCRIPTION "pg_meta_sub"
CONNECTION 'host=10.10.10.11 user=replicator dbname=test'
PUBLICATION "pg_test_pub";
TABLE pg_subscription;
EOF
# 清空源端所有相关表(危险),等待/或者不等待同步完成
psql ${PGSRC} -Xtw <<-EOF
TRUNCATE TABLE
public.pgbench_accounts,
public.pgbench_branches,
public.pgbench_tellers,
public.pgbench_history,
public.pgbench_extras;
TABLE pg_publication;
EOF
3.3 同步序列号与其他对象
逻辑复制不复制序列号(Sequence),因此基于逻辑复制做Failover时,必须在切换前手工同步序列号的值。
3.3.1 从源端同步序列号值
如果您的序列号都是从表上的SERIAL
列定义自动创建的,而且宿端库也单纯只从源端订阅,那么同步序列号比较简单。从订阅端找出所有需要同步的序列号:
PGSRC='postgres://dbuser_admin@10.10.10.10/meta' # 源端发布者 (SU)
PGDST='postgres://dbuser_admin@10.10.10.11/test' # 宿端订阅者 (SU)
-- 查询订阅端,生成的用于同步SEQUENCE的shell命令
psql ${PGDST} -qAXtw <<-'EOF'
SELECT 'pg_dump ${PGSRC} -a ' ||
string_agg('-t ' || quote_ident(schemaname) || '.' || quote_ident(sequencename), ' ') ||
' | grep setval | psql -qAXtw ${PGDST}'
FROM pg_sequences;
EOF
在本例中,只有pgbench_extras.id
上有一个对应的SEQUENCE pgbench_extras_id_seq
。这里生成的同步语句为
pg_dump ${PGSRC} -a -t public.pgbench_extras_id_seq | grep setval | psql -qAXtw ${PGDST}
比较复杂的情况,需要您手工生成这条命令,通过-t
依次指定需要转储的序列号。
3.3.2 基于业务数据设置序列号值
另一种管理序列号的方式是直接根据表中的数据设置序列号的值,而无需从源端同步。
例如,表pgbench_extras.id
的最大值为100
,那么将订阅端端pgbench_extras_id_seq
直接设置为一个足够大的值,例如100+10000 = 10100
,就可以保证迁移后使用该序列号分配的新id
不会与已有数据冲突。
采用这种方式,可以直接在故障切换前进行序列号的设置,减少迁移切换所需的停机时间。但这样可能会导致业务数据序列号分配出现空洞,对于一些边界条件与特殊的序列号使用场景需要特别小心。例如:序列号从未被使用过,序列号的增长步长为负数,采用函数发号器调用Sequence等。
直接设置序列号的命令如下所示:
psql ${PGDST} -qAXtw <<-'EOF'
SELECT pg_catalog.setval('public.pgbench_extras_id_seq', (SELECT max(id) + 1000 FROM pgbench_extras));
EOF
3.3.3 其他对象的同步
某些无法被逻辑复制处理的对象,也需要在这里一并进行同步。
例如:刷新物化视图,手工迁移大对象等。但这些功能很少有人会用到,所以在此不详细展开。
3.4 校验数据一致性
如果逻辑复制工作正常,通常不用校验数据,您可以在第二步中间执行多次对比校验以增强对逻辑复制的信心。
在停机窗口期间,建议只进行简单基本的数据校验,例如,比较表中的行数,主键的最大最小值是否一致。
以下函数用于执行这一校验
function compare_relation(){
local relname=$1
local identity=${2-'id'}
psql ${3-${PGSRC}} -AXtwc "SELECT count(*) AS cnt, max($identity) AS max, min($identity) AS min FROM ${relname};"
psql ${4-${PGDST}} -AXtwc "SELECT count(*) AS cnt, max($identity) AS max, min($identity) AS min FROM ${relname};"
}
compare_relation pgbench_accounts aid
compare_relation pgbench_branches bid
compare_relation pgbench_history tid
compare_relation pgbench_tellers tid
function compare_relation() {
local src_url=${1}
local dst_url=${2}
local relname=${3}
res1=$(psql "${src_url}" -AXtwc "SELECT count(*) AS cnt FROM ${relname};")
res2=$(psql "${dst_url}" -AXtwc "SELECT count(*) AS cnt FROM ${relname};")
if [[ "${res1}" == "${res2}" ]]; then
echo -e "[ok] ${relname}\t\t\t${res1}\t${res2}"
else
echo -e "[xx] ${relname}\t\t\t${res1}\t${res2}"
fi
}
function compare_all() {
local src_url=${1}
local dst_url=${2}
tables=$(psql ${src_url} -AXtwc "SELECT quote_ident(nspname) || '.' || quote_ident(relname) AS name FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema', 'monitor', 'repack', 'pg_toast')")
for tbl in $tables; do
result=$(compare_relation "${src_url}" "${dst_url}" ${tbl})
echo ${result}
done
}
compare_all ${PGSRC} ${PGDST}
同时,也可以过一遍3.3中同步的序列号,确认其配置是否相同。
psql ${PGSRC} -qwXtc "SELECT schemaname || '.' || sequencename AS name, last_value AS v FROM pg_sequences;"
psql ${PGDST} -qwXtc "SELECT schemaname || '.' || sequencename AS name, last_value AS v FROM pg_sequences;"
其他在3.3.3中手工同步的对象请按需自行校验。如果需要进行其他业务侧的校验,也在这里进行。但停机窗口时间宝贵,花费在这里的时间越长,服务不可用时间也越久。
校验完成后,就可以进行最终的流量切换了。
3.5 流量切换与善后
完成数据校验后就可以进行流量切换。
流量切换的方式取决于您所使用的访问方式,通常与3.2中停流量的方式对偶。例如:
- 修改应用端连接串,并应用生效
- 将源端主库域名解析至新主库
- 将负载均衡器(Haproxy | VIP)的流量转发至新主库
- 将原主库上Pgbouncer连接池的流量转发至新主库
通过监控系统或其他方式,确认写入流量已经正确应用订阅端的新主库后,基于逻辑复制的迁移就完成了。
不要忘记一些善后清理工作,停用并删除订阅端的订阅,删除发布端的发布。
同时,应当继续确保原主库拒绝新的写入,以免有未清理干净的流量因为配置失误错漏仍然向旧主库访问。
# 删除订阅侧的 订阅
psql ${PGDST} -qAXtw <<-'EOF'
ALTER SUBSCRIPTION pg_test_sub DISABLE;
DROP SUBSCRIPTION pg_test_sub;
EOF
# 删除发布侧的 发布
psql ${PGSRC} -qAXtw <<-'EOF'
DROP PUBLICATION pg_meta_sub;
EOF
至此,基于逻辑复制的完整迁移结束。
内置剧本
Pigsty内置了一个 数据库在线迁移的辅助脚本:pgsql-migration.yml
,提供了一个开箱即用的基于逻辑复制的不停机数据库迁移方案。
填入源集群与宿集群相关信息,该剧本即会自动创建出迁移中所需的脚本,在数据库迁移时只需要依次执行即可,包括:
activate # 激活迁移上下文,注册环境变量
check-replica-identity # 准备阶段:检查源集群所有表是否都具有复制身份(主键,或非空唯一候选键)
check-replica-identity-solution # 准备阶段:针对没有合理复制身份表,生成修复SQL语句
check-special-object # 准备阶段:检查物化视图,复合类型等特殊对象
compare # 比较:对源宿集群中的表进行快速比较(行数计算)
copy-schema # 存量迁移:将源集群中的模式复制到宿集群中(可以幂等执行)
create-pub # 存量迁移:在源集群中创建发布
create-sub # 存量迁移:在宿集群中创建订阅,建立源宿集群之间的逻辑复制
progress # 存量迁移:打印逻辑复制的进度
copy-seq # 存量/增量迁移:将源集群中的序列号复制到宿集群中(可以幂等执行,在切换时需要再次执行)
next-seq # 切换时刻:将宿集群的所有序列号紧急步进1000,以避免主键冲突。
remove-sub # 移除宿集群中的逻辑订阅
准备工作
准备源宿集群
现在假设我们希望迁移沙箱中的pg-meta
集群(包含Pigsty元数据库与pgbench测试表)至pg-test
集群。
pg-meta-1 10.10.10.10 --> pg-test-1 10.10.10.11 (10.10.10.12,10.10.10.13)
首先,新创建好空的目标集群pg-test
,然后编辑pgsql-migration.yml
中的变量清单部分,填入相关信息(原宿集群主库的连接信息)
#--------------------------------------------------------------#
# MIGRATION CONTEXT #
#--------------------------------------------------------------#
# src cluster (the old cluster)
src_cls: pg-meta # src cluster name
src_db: meta # src database name
src_ip: 10.10.10.10 # ip address of src cluster primary
src_list: [ ] # ip address list of src cluster members (non-primary)
#--------------------------------------------------------------#
# dst cluster (the new cluster)
dst_cls: pg-test # dst cluster name
dst_db: test # dst database name
dst_ip: 10.10.10.11 # dst cluster leader ip addressh
dst_list: [ 10.10.10.12, 10.10.10.13 ] # dst cluster members (non-primary)
# dst cluster access information
dst_dns: pg-test # dst cluster dns records
dst_vip: 10.10.10.3 # dst cluster vip records
#--------------------------------------------------------------#
# credential (assume .pgpass viable)
pg_admin_username: dbuser_dba # superuser @ both side
pg_replicatoin_username: replicator # repl user @ src to be used
migration_context_dir: ~/migration # this dir will be created
#--------------------------------------------------------------#
执行pgsql-migration.yml
,该脚本默认会在元节点上创建 ~/migration/pg-meta.meta
目录,包含有迁移使用的资源与脚本。
迁移模板
9 - TimescaleDB 快速上手
https://docs.timescale.com/getting-started/latest/create-hypertable/#hypertables-and-chunks
CREATE TABLE stocks_real_time
(
time TIMESTAMPTZ NOT NULL,
symbol TEXT NOT NULL,
price DOUBLE PRECISION NULL,
day_volume INT NULL
);
SELECT create_hypertable('stocks_real_time', 'time');
CREATE INDEX ix_symbol_time ON stocks_real_time (symbol, time DESC);
TABLE timescaledb_information.hypertables;
TABLE timescaledb_information.chunks;
SELECT add_retention_policy('conditions', INTERVAL '24 hours');
SELECT remove_retention_policy('conditions');
SELECT j.hypertable_name,
j.job_id,
config,
schedule_interval,
job_status,
last_run_status,
last_run_started_at,
js.next_start,
total_runs,
total_successes,
total_failures
FROM timescaledb_information.jobs j
JOIN timescaledb_information.job_stats js
ON j.job_id = js.job_id
WHERE j.proc_name = 'policy_retention';