这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

管理预案

数据库管理任务标准操作指南(SOP)

如何使用 Pigsty 维护现有的 PostgreSQL 集群?

本章节提供 PostgreSQL 常见管理任务的标准操作流程(SOP):

  • 常用预案:创建/移除集群与实例,备份恢复,滚动升级等标准操作流程
  • 故障排查:常见故障排查思路与处理方法,如磁盘写满、连接耗尽、XID回卷等
  • 误删处理:处理误删数据、误删表、误删数据库的应急处理流程
  • 维护保养:定期巡检、故障切换善后、表膨胀治理、VACUUM FREEZE 等维护任务
  • 参数优化:内存、CPU、存储等参数的自动优化策略与调整方法

1 - 数据库管理

数据库管理:创建、修改、删除、重建数据库,使用模板克隆数据库

在 Pigsty 中,数据库管理采用 IaC 的风格,先在配置清单中定义,然后执行剧本执行。

在没有定义 baseline SQL 的情况下,执行 pgsql-db.yml 剧本是幂等的。 它会将指定集群中的指定数据库调整至配置清单中的目标状态。

请注意,部分参数仅能在创建时指定。修改这些参数需要先删除再创建数据库(使用 state: recreate 重建数据库)。


定义数据库

业务数据库定义在数据库集群参数 pg_databases 中,这是一个数据库定义构成的对象数组。 数组内的数据库按照定义顺序依次创建,因此后面定义的数据库可以使用先前定义的数据库作为模板

下面是 Pigsty 演示环境中默认集群 pg-meta 中的数据库定义:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_databases:
      - { name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [{name: postgis, schema: public}, {name: timescaledb}]}
      - { name: grafana  ,owner: dbuser_grafana  ,revokeconn: true ,comment: grafana primary database }
      - { name: bytebase ,owner: dbuser_bytebase ,revokeconn: true ,comment: bytebase primary database }
      - { name: kong     ,owner: dbuser_kong     ,revokeconn: true ,comment: kong the api gateway database }
      - { name: gitea    ,owner: dbuser_gitea    ,revokeconn: true ,comment: gitea meta database }
      - { name: wiki     ,owner: dbuser_wiki     ,revokeconn: true ,comment: wiki meta database }
      - { name: noco     ,owner: dbuser_noco     ,revokeconn: true ,comment: nocodb database }

唯一必选的字段是 name,它应该是当前 PostgreSQL 集群中有效且唯一的数据库名称,其他参数都有合理的默认值。 关于数据库定义参数的完整参考,请查阅 数据库配置参考


创建数据库

要在现有的 PostgreSQL 集群上创建新的业务数据库,请将数据库定义添加到 all.children.<cls>.pg_databases,然后执行:

bin/pgsql-db <cls> <dbname>    # 等效于:pgsql-db.yml -l <cls> -e dbname=<dbname>

示例:创建名为 myapp 的业务数据库

  1. 在配置文件中添加数据库定义:
pg-meta:
  vars:
    pg_databases:
      - name: myapp
        owner: dbuser_myapp
        schemas: [app]
        extensions:
          - { name: pg_trgm }
          - { name: btree_gin }
        comment: my application database
  1. 执行创建命令:
bin/pgsql-db pg-meta myapp

执行效果:

  • 在主库上创建数据库 myapp
  • 设置数据库所有者为 dbuser_myapp
  • 创建 schema app
  • 安装扩展 pg_trgmbtree_gin
  • 配置默认权限(dbrole_readonly/readwrite/admin)
  • 将数据库添加到 Pgbouncer 连接池
  • 将数据库注册到 Grafana 数据源

修改数据库

修改数据库属性可以通过更新配置并重新执行剧本来完成:

bin/pgsql-db <cls> <dbname>    # 幂等操作,可重复执行

可修改属性

属性说明示例
owner数据库属主owner: dbuser_new
tablespace默认表空间(会触发数据迁移)tablespace: fast_ssd
is_template是否标记为模板数据库is_template: true
allowconn是否允许连接allowconn: false
connlimit连接数限制connlimit: 100
revokeconn是否回收 PUBLIC 的 CONNECT 权限revokeconn: true
comment备注信息comment: 新的备注
parameters数据库级参数见下方示例
schemas添加/删除模式(增量操作)管理模式
extensions添加/删除扩展(增量操作)管理扩展
pgbouncer是否加入连接池pgbouncer: false
pool_*连接池参数连接池配置

不可修改属性

以下属性在数据库创建后无法修改,需要使用 state: recreate 重建数据库:

  • template - 模板数据库
  • encoding - 字符编码
  • locale / lc_collate / lc_ctype - 本地化设置
  • locale_provider / icu_locale / icu_rules / builtin_locale - 本地化提供者设置
  • strategy - 克隆策略

修改属主

- name: myapp
  owner: dbuser_new_owner     # 修改为新属主
bin/pgsql-db pg-meta myapp

执行的 SQL:

ALTER DATABASE "myapp" OWNER TO "dbuser_new_owner";
GRANT ALL PRIVILEGES ON DATABASE "myapp" TO "dbuser_new_owner";

修改连接限制

- name: myapp
  connlimit: 100              # 限制最大 100 个连接

执行的 SQL:

ALTER DATABASE "myapp" CONNECTION LIMIT 100;

回收公共连接权限

- name: myapp
  owner: dbuser_myapp
  revokeconn: true            # 回收 PUBLIC 的 CONNECT 权限

执行的 SQL:

REVOKE CONNECT ON DATABASE "myapp" FROM PUBLIC;
GRANT CONNECT ON DATABASE "myapp" TO "replicator";
GRANT CONNECT ON DATABASE "myapp" TO "dbuser_monitor";
GRANT CONNECT ON DATABASE "myapp" TO "dbuser_dba" WITH GRANT OPTION;
GRANT CONNECT ON DATABASE "myapp" TO "dbuser_myapp" WITH GRANT OPTION;

要恢复公共连接权限,设置 revokeconn: false

- name: myapp
  revokeconn: false           # 恢复 PUBLIC 的 CONNECT 权限

执行的 SQL:

GRANT CONNECT ON DATABASE "myapp" TO PUBLIC;

标记为模板数据库

- name: app_template
  is_template: true           # 允许任何有 CREATEDB 权限的用户克隆

执行的 SQL:

ALTER DATABASE "app_template" IS_TEMPLATE true;

管理参数

数据库级参数通过 parameters 字典配置,会生成 ALTER DATABASE ... SET 语句。

设置参数

- name: myapp
  parameters:
    work_mem: '256MB'
    maintenance_work_mem: '512MB'
    statement_timeout: '30s'
    search_path: 'app,public'

执行的 SQL:

ALTER DATABASE "myapp" SET "work_mem" = '256MB';
ALTER DATABASE "myapp" SET "maintenance_work_mem" = '512MB';
ALTER DATABASE "myapp" SET "statement_timeout" = '30s';
ALTER DATABASE "myapp" SET "search_path" = 'app,public';

重置参数为默认值

使用特殊值 DEFAULT(大小写不敏感)可以将参数重置为 PostgreSQL 默认值:

- name: myapp
  parameters:
    work_mem: DEFAULT         # 重置为 PostgreSQL 默认值
    statement_timeout: DEFAULT

执行的 SQL:

ALTER DATABASE "myapp" SET "work_mem" = DEFAULT;
ALTER DATABASE "myapp" SET "statement_timeout" = DEFAULT;

常用数据库级参数

参数说明示例值
work_mem查询工作内存'64MB'
maintenance_work_mem维护操作内存'256MB'
statement_timeout语句超时时间'30s'
lock_timeout锁等待超时'10s'
idle_in_transaction_session_timeout空闲事务超时'10min'
search_pathSchema 搜索路径'app,public'
default_tablespace默认表空间'fast_ssd'
temp_tablespaces临时表空间'temp_ssd'
log_statement日志记录级别'ddl'

管理模式

模式(Schema)通过 schemas 数组配置,支持创建、指定属主和删除操作。

创建模式

- name: myapp
  schemas:
    # 简单形式:只指定模式名
    - app
    - api

    # 完整形式:指定属主
    - { name: core, owner: dbuser_myapp }

执行的 SQL:

CREATE SCHEMA IF NOT EXISTS "app";
CREATE SCHEMA IF NOT EXISTS "api";
CREATE SCHEMA IF NOT EXISTS "core" AUTHORIZATION "dbuser_myapp";

指定模式属主

使用 owner 字段可以为模式指定属主,这在多租户或权限隔离场景中非常有用:

- name: multi_tenant_db
  owner: dbuser_admin
  schemas:
    - { name: tenant_a, owner: dbuser_tenant_a }
    - { name: tenant_b, owner: dbuser_tenant_b }
    - { name: shared, owner: dbuser_admin }

删除模式

使用 state: absent 标记要删除的模式:

- name: myapp
  schemas:
    - { name: deprecated_schema, state: absent }

执行的 SQL:

DROP SCHEMA IF EXISTS "deprecated_schema" CASCADE;

管理扩展

扩展通过 extensions 数组配置,支持安装和卸载操作。

安装扩展

- name: myapp
  extensions:
    # 简单形式:只指定扩展名
    - postgis
    - pg_trgm

    # 完整形式:指定 schema 和版本
    - { name: vector, schema: public }
    - { name: pg_stat_statements, schema: monitor, version: '1.10' }

执行的 SQL:

CREATE EXTENSION IF NOT EXISTS "postgis" CASCADE;
CREATE EXTENSION IF NOT EXISTS "pg_trgm" CASCADE;
CREATE EXTENSION IF NOT EXISTS "vector" WITH SCHEMA "public" CASCADE;
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "monitor" VERSION '1.10' CASCADE;

卸载扩展

使用 state: absent 标记要卸载的扩展:

- name: myapp
  extensions:
    - { name: pg_trgm, state: absent }    # 卸载扩展
    - { name: postgis }                    # 保留扩展

执行的 SQL:

DROP EXTENSION IF EXISTS "pg_trgm" CASCADE;
CREATE EXTENSION IF NOT EXISTS "postgis" CASCADE;

删除数据库

要删除数据库,将其 state 设置为 absent 并执行剧本:

pg_databases:
  - name: olddb
    state: absent
bin/pgsql-db <cls> olddb

删除操作会:

  1. 如果数据库标记为 is_template: true,先执行 ALTER DATABASE ... IS_TEMPLATE false
  2. 使用 DROP DATABASE ... WITH (FORCE) 强制删除数据库(PG13+)
  3. 终止所有到该数据库的活动连接
  4. 从 Pgbouncer 连接池中移除该数据库
  5. 从 Grafana 数据源中取消注册

保护机制:

  • 系统数据库 postgrestemplate0template1 无法删除
  • 删除操作仅在主库上执行,流复制会自动同步到从库

重建数据库

recreate 状态用于重建数据库,等效于先删除再创建:

pg_databases:
  - name: testdb
    state: recreate
    owner: dbuser_test
    baseline: test_init.sql    # 重建后执行初始化
bin/pgsql-db <cls> testdb

适用场景:

  • 测试环境重置
  • 清空开发数据库
  • 修改不可变属性(编码、本地化等)
  • 恢复数据库到初始状态

与手动 DROP + CREATE 的区别:

  • 单条命令完成,无需两次操作
  • 自动保留 Pgbouncer 和 Grafana 配置
  • 执行后自动加载 baseline 初始化脚本

克隆数据库

可以使用现有数据库作为模板创建新数据库,实现数据库结构的快速复制。

基本克隆

pg_databases:
  # 1. 首先定义模板数据库
  - name: app_template
    owner: dbuser_app
    schemas: [core, api]
    extensions: [postgis, pg_trgm]
    baseline: app_schema.sql

  # 2. 使用模板创建业务数据库
  - name: app_prod
    template: app_template
    owner: dbuser_app

指定克隆策略(PG15+)

- name: app_staging
  template: app_template
  strategy: FILE_COPY        # 或 WAL_LOG
  owner: dbuser_app
策略说明适用场景
FILE_COPY直接复制数据文件大模板,通用场景
WAL_LOG通过 WAL 日志复制小模板,不阻塞模板连接

使用自定义模板数据库

当使用非系统模板(非 template0/template1)时,Pigsty 会自动终止模板数据库上的连接以允许克隆。

- name: new_db
  template: existing_db      # 使用现有业务数据库作为模板
  owner: dbuser_app

标记为模板数据库

默认只有超级用户或数据库所有者可以使用普通数据库作为模板。 使用 is_template: true 可以允许任何有 CREATEDB 权限的用户克隆:

- name: shared_template
  is_template: true          # 允许任何有 CREATEDB 权限的用户克隆
  owner: dbuser_app

使用 ICU 本地化提供者

使用 icu 本地化提供者时,必须指定 template: template0

- name: myapp_icu
  template: template0        # 必须使用 template0
  locale_provider: icu
  icu_locale: en-US
  encoding: UTF8

连接池配置

默认情况下,所有业务数据库都会添加到 Pgbouncer 连接池中。

数据库级连接池参数

- name: myapp
  pgbouncer: true              # 是否加入连接池(默认 true)
  pool_mode: transaction       # 池化模式:transaction/session/statement
  pool_size: 64                # 默认池大小
  pool_size_min: 0             # 最小池大小
  pool_reserve: 32             # 保留连接数
  pool_connlimit: 100          # 最大数据库连接
  pool_auth_user: dbuser_meta  # 认证查询用户

生成的配置

配置文件位于 /etc/pgbouncer/database.txt

myapp                       = host=/var/run/postgresql pool_mode=transaction pool_size=64

隐藏数据库

某些内部数据库可能不需要通过连接池访问:

- name: internal_db
  pgbouncer: false           # 不加入连接池

池化模式说明

模式说明适用场景
transaction事务结束后归还连接(默认)大多数 OLTP 应用
session会话结束后归还连接需要会话状态的应用
statement语句结束后归还连接无状态查询

本地化提供者

PostgreSQL 15+ 引入了 locale_provider 参数,支持不同的本地化实现。

使用 ICU 提供者(PG15+)

- name: myapp_icu
  template: template0        # ICU 必须使用 template0
  locale_provider: icu
  icu_locale: en-US          # ICU 本地化规则
  encoding: UTF8

使用内置提供者(PG17+)

- name: myapp_builtin
  template: template0
  locale_provider: builtin
  builtin_locale: C.UTF-8    # 内置本地化规则
  encoding: UTF8

ICU 排序规则(PG16+)

- name: myapp_custom_icu
  template: template0
  locale_provider: icu
  icu_locale: en-US
  icu_rules: '&V << w <<< W'  # 自定义 ICU 排序规则

提供者对比

提供者版本要求特点
libc-传统方式,依赖操作系统
icuPG15+跨平台一致,功能丰富
builtinPG17+最高效的 C/C.UTF-8 排序

操作速查

常用命令

操作命令
创建数据库bin/pgsql-db <cls> <dbname>
修改数据库bin/pgsql-db <cls> <dbname>
删除数据库设置 state: absent 后执行 bin/pgsql-db <cls> <dbname>
重建数据库设置 state: recreate 后执行 bin/pgsql-db <cls> <dbname>
查看数据库列表psql -c '\l'
查看连接池数据库cat /etc/pgbouncer/database.txt

常见操作示例

# 创建基本数据库
- name: myapp
  owner: dbuser_myapp
  comment: my application database

# 创建带扩展的数据库
- name: geodata
  owner: dbuser_geo
  extensions: [postgis, postgis_topology]

# 限制连接的私有数据库
- name: secure_db
  owner: dbuser_secure
  revokeconn: true
  connlimit: 10

# 设置数据库级参数
- name: analytics
  owner: dbuser_analyst
  parameters:
    work_mem: '512MB'
    statement_timeout: '5min'

# 使用 ICU 本地化
- name: i18n_db
  template: template0
  locale_provider: icu
  icu_locale: zh-Hans
  encoding: UTF8

# 删除数据库
- name: old_db
  state: absent

# 重建数据库
- name: test_db
  state: recreate
  baseline: test_init.sql

执行流程

bin/pgsql-db 执行时会依次:

  1. 验证 - 检查 dbname 参数和数据库定义
  2. 删除(如果 state=absent/recreate)- 执行 DROP DATABASE
  3. 创建(如果 state=create/recreate)- 执行 CREATE DATABASE
  4. 配置 - 执行 ALTER DATABASE 设置属性
  5. 初始化 - 创建 schema、安装扩展、执行 baseline
  6. 注册 - 更新 Pgbouncer 和 Grafana 数据源

关于数据库的访问权限,请参考 ACL:数据库权限 一节。

2 - HBA 管理

PostgreSQL 与 Pgbouncer HBA 规则的日常管理操作:刷新、重载、验证与故障排查。

HBA 规则的变更需要重新渲染配置文件并重载服务。本文介绍 HBA 规则的日常管理操作。


快速参考

操作命令
刷新集群 HBAbin/pgsql-hba <cls>
刷新特定实例bin/pgsql-hba <cls> <ip>...
仅刷新 PostgreSQL./pgsql.yml -l <cls> -t pg_hba,pg_reload
仅刷新 Pgbouncer./pgsql.yml -l <cls> -t pgbouncer_hba,pgbouncer_reload
查看当前 HBApsql -c "TABLE pg_hba_file_rules"
验证 HBA 配置psql -c "SELECT pg_reload_conf()"

刷新 HBA 规则

修改 pigsty.yml 中的 HBA 规则后,需要重新渲染配置文件并让服务重载。

使用管理脚本

推荐使用 bin/pgsql-hba 脚本,一键完成 PostgreSQL 和 Pgbouncer 的 HBA 刷新:

# 刷新整个集群的 HBA 规则
bin/pgsql-hba pg-meta

# 刷新特定实例(多个 IP 空格分隔)
bin/pgsql-hba pg-meta 10.10.10.10
bin/pgsql-hba pg-meta 10.10.10.11 10.10.10.12

# 查看脚本帮助
bin/pgsql-hba --help

脚本内部执行:

./pgsql.yml -l <cluster> -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload

使用 Ansible Playbook

直接使用 pgsql.yml playbook 的相关 tags:

# 刷新 PostgreSQL HBA 并重载
./pgsql.yml -l pg-meta -t pg_hba,pg_reload

# 刷新 Pgbouncer HBA 并重载
./pgsql.yml -l pg-meta -t pgbouncer_hba,pgbouncer_reload

# 同时刷新两者
./pgsql.yml -l pg-meta -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload

# 使用额外变量强制重载
./pgsql.yml -l pg-meta -e pg_reload=true -t pg_hba,pg_reload

相关 Tags

Tag说明
pg_hba渲染 PostgreSQL HBA 配置文件
pg_reload重载 PostgreSQL 配置(需配合 pg_reload=true
pgbouncer_hba渲染 Pgbouncer HBA 配置文件
pgbouncer_reload重载 Pgbouncer 配置

配置文件位置

HBA 配置文件由 Ansible 渲染生成:

服务配置文件路径模板文件
PostgreSQL/pg/data/pg_hba.confroles/pgsql/templates/pg_hba.conf
Pgbouncer/etc/pgbouncer/pgb_hba.confroles/pgsql/templates/pgbouncer.hba

警告:不要直接编辑这些文件,下次执行 playbook 时会被覆盖。所有变更应在 pigsty.yml 中进行。


验证 HBA 规则

查看当前生效的 HBA 规则

# 使用 psql 查看 PostgreSQL HBA 规则
psql -c "TABLE pg_hba_file_rules"

# 或者直接查看配置文件
cat /pg/data/pg_hba.conf

# 查看 Pgbouncer HBA 规则
cat /etc/pgbouncer/pgb_hba.conf

检查 HBA 配置语法

# PostgreSQL 重载配置(会验证语法)
psql -c "SELECT pg_reload_conf()"

# 如果有语法错误,查看日志
tail -f /pg/log/postgresql-*.log

测试连接认证

# 测试特定用户从特定地址的连接
psql -h <host> -p 5432 -U <user> -d <database> -c "SELECT 1"

# 查看连接被哪条 HBA 规则匹配
psql -c "SELECT * FROM pg_hba_file_rules WHERE database @> ARRAY['<dbname>']::text[]"

常见管理场景

添加新的 HBA 规则

  1. 编辑 pigsty.yml,在集群的 pg_hba_rules 中添加规则:
pg-meta:
  vars:
    pg_hba_rules:
      - {user: new_user, db: new_db, addr: '192.168.1.0/24', auth: pwd, title: 'new app access'}
  1. 执行刷新:
bin/pgsql-hba pg-meta

紧急封禁 IP

当发现恶意 IP 时,可以快速添加黑名单规则:

  1. 添加高优先级(order: 0)的拒绝规则:
pg_hba_rules:
  - {user: all, db: all, addr: '10.1.1.100/32', auth: deny, order: 0, title: 'emergency block'}
  1. 立即刷新:
bin/pgsql-hba pg-meta

按角色区分规则

为主库和从库配置不同的 HBA 规则:

pg_hba_rules:
  # 仅主库允许写入用户
  - {user: writer, db: all, addr: intra, auth: pwd, role: primary, title: 'writer on primary'}

  # 从库允许只读用户
  - {user: reader, db: all, addr: world, auth: ssl, role: replica, title: 'reader on replica'}

执行刷新后,规则会根据实例的 pg_role 自动启用或禁用。

集群扩容后刷新 HBA

当集群新增实例后,使用 addr: cluster 的规则需要刷新才能包含新成员:

# 扩容新实例
./pgsql.yml -l 10.10.10.14

# 刷新所有实例的 HBA(包含新成员 IP)
bin/pgsql-hba pg-meta

主从切换后刷新 HBA

Patroni 故障转移后,实例的 pg_role 可能与配置不一致。如果 HBA 规则使用了 role 过滤,需要:

  1. 更新 pigsty.yml 中的角色定义
  2. 刷新 HBA 规则
# 更新配置文件中的角色后刷新
bin/pgsql-hba pg-meta

故障排查

连接被拒绝

症状FATAL: no pg_hba.conf entry for host "x.x.x.x", user "xxx", database "xxx"

排查步骤

  1. 检查当前 HBA 规则:
psql -c "TABLE pg_hba_file_rules"
  1. 确认客户端 IP、用户名、数据库是否匹配任何规则

  2. 检查规则顺序(首条匹配生效)

  3. 添加对应规则并刷新

认证失败

症状FATAL: password authentication failed for user "xxx"

排查步骤

  1. 确认密码正确
  2. 检查密码加密方式(pg_pwd_enc)与客户端兼容性
  3. 检查用户是否存在:\duSELECT * FROM pg_roles WHERE rolname = 'xxx'

HBA 规则未生效

排查步骤

  1. 确认已执行刷新命令
  2. 检查 Ansible 执行是否成功
  3. 确认 PostgreSQL 已重载:
psql -c "SELECT pg_reload_conf()"
  1. 检查配置文件是否更新:
head -20 /pg/data/pg_hba.conf

规则顺序问题

HBA 是首条匹配生效,如果规则未按预期工作:

  1. 检查 order
  2. 使用 psql -c "TABLE pg_hba_file_rules" 查看实际顺序
  3. 调整 order 值或规则位置

在线修改 HBA(不推荐)

虽然可以直接编辑 /pg/data/pg_hba.conf 并重载,但不推荐这样做:

# 直接编辑(不推荐)
vi /pg/data/pg_hba.conf

# 重载配置
psql -c "SELECT pg_reload_conf()"
# 或
pg_ctl reload -D /pg/data
# 或
systemctl reload postgresql

问题:下次执行 Ansible playbook 时,手动修改会被覆盖。

正确做法:始终在 pigsty.yml 中修改,然后执行 bin/pgsql-hba 刷新。


Pgbouncer HBA 管理

Pgbouncer 的 HBA 管理与 PostgreSQL 类似,但有一些差异:

配置差异

  • 配置文件:/etc/pgbouncer/pgb_hba.conf
  • 不支持 db: replication
  • 认证方式:本地连接使用 peer 而非 ident

刷新命令

# 仅刷新 Pgbouncer HBA
./pgsql.yml -l pg-meta -t pgbouncer_hba,pgbouncer_reload

# 或使用统一脚本(同时刷新 PostgreSQL 和 Pgbouncer)
bin/pgsql-hba pg-meta

查看 Pgbouncer HBA

cat /etc/pgbouncer/pgb_hba.conf

最佳实践

  1. 始终在配置文件中管理:不要直接编辑 pg_hba.conf,所有变更通过 pigsty.yml
  2. 测试环境先验证:HBA 变更可能导致连接问题,先在测试环境验证
  3. 使用 order 控制优先级:黑名单规则使用 order: 0,确保优先匹配
  4. 及时刷新:添加/删除实例、主从切换后及时刷新 HBA
  5. 最小权限原则:只开放必要的访问,避免使用 addr: world + auth: trust
  6. 监控认证失败:关注 pg_stat_activity 中的认证失败记录
  7. 备份配置:重要变更前备份 pigsty.yml

相关命令速查

# 刷新 HBA(推荐)
bin/pgsql-hba <cluster>

# 查看 PostgreSQL HBA
psql -c "TABLE pg_hba_file_rules"
cat /pg/data/pg_hba.conf

# 查看 Pgbouncer HBA
cat /etc/pgbouncer/pgb_hba.conf

# 重载 PostgreSQL 配置
psql -c "SELECT pg_reload_conf()"

# 测试连接
psql -h <host> -U <user> -d <db> -c "SELECT 1"

# 查看认证失败日志
tail -f /pg/log/postgresql-*.log | grep -i auth

相关文档

3 - 常用预案

Pigsty 中常用的 PostgreSQL 管理预案,用于维护生产环境中的数据库集群。

本文整理了 Pigsty 中常用的 PostgreSQL 管理预案,用于维护生产环境中的数据库集群。

以下是常见 PostgreSQL 管理任务的标准操作程序:


命令速查

PGSQL 剧本与快捷方式:

bin/pgsql-add   <cls>                   # 创建 pgsql 集群 <cls>
bin/pgsql-user  <cls> <username>        # 在 <cls> 上创建 pg 用户 <username>
bin/pgsql-db    <cls> <dbname>          # 在 <cls> 上创建 pg 数据库 <dbname>
bin/pgsql-svc   <cls> [...ip]           # 重新加载集群 <cls> 的 pg 服务
bin/pgsql-hba   <cls> [...ip]           # 重新加载集群 <cls> 的 postgres/pgbouncer HBA 规则
bin/pgsql-add   <cls> [...ip]           # 为集群 <cls> 添加从库副本
bin/pgsql-rm    <cls> [...ip]           # 从集群 <cls> 移除实例
bin/pgsql-rm    <cls>                   # 删除 pgsql 集群 <cls>

Patroni 管理命令与快捷方式:

pg list        <cls>                    # 打印集群信息
pg edit-config <cls>                    # 编辑集群配置
pg reload      <cls> [ins]              # 重新加载集群配置
pg restart     <cls> [ins]              # 重启 PostgreSQL 集群
pg reinit      <cls> [ins]              # 重新初始化集群成员
pg pause       <cls>                    # 进入维护模式(自动故障转移暂停)
pg resume      <cls>                    # 退出维护模式
pg switchover  <cls>                    # 在集群 <cls> 上进行主动主从切换(主库健康)
pg failover    <cls>                    # 在集群 <cls> 上进行故障转移(主库故障)

pgBackRest 备份/恢复命令与快捷方式:

pb info                                 # 打印 pgbackrest 备份仓库信息
pg-backup                               # 进行备份,默认进行增量备份,如果没有完整备份过就做全量备份
pg-backup full                          # 进行全量备份
pg-backup diff                          # 进行差异备份
pg-backup incr                          # 进行增量备份
pg-pitr -i                              # 恢复到最近备份完成的时间(不常用)
pg-pitr --time="2022-12-30 14:44:44+08" # 恢复到特定时间点(如在删除数据库或表的情况下)
pg-pitr --name="my-restore-point"       # 恢复到由 pg_create_restore_point 创建的命名还原点
pg-pitr --lsn="0/7C82CB8" -X            # 恢复到 LSN 之前
pg-pitr --xid="1234567" -X -P           # 恢复到特定的事务ID之前,然后将其提升为主库
pg-pitr --backup=latest                 # 恢复到最新的备份集
pg-pitr --backup=20221108-105325        # 恢复到特定的备份集,使用名称指定,可以使用 pgbackrest info 进行检查

使用 Systemd 管理系统组件的命令:

systemctl stop patroni                  # 启动 停止 重启 重载
systemctl stop pgbouncer                # 启动 停止 重启 重载
systemctl stop pg_exporter              # 启动 停止 重启 重载
systemctl stop pgbouncer_exporter       # 启动 停止 重启 重载
systemctl stop node_exporter            # 启动 停止 重启
systemctl stop haproxy                  # 启动 停止 重启 重载
systemctl stop vip-manager              # 启动 停止 重启 重载
systemctl stop postgres                 # 仅当 patroni_mode == 'remove' 时使用这个服务

创建集群

要创建一个新的Postgres集群,请首先在配置清单中定义,然后进行初始化:

bin/node-add <cls>                # 为集群 <cls> 初始化节点                  # ./node.yml  -l <cls> 
bin/pgsql-add <cls>               # 初始化集群 <cls> 的pgsql实例             # ./pgsql.yml -l <cls>

请注意,PGSQL 模块需要在 Pigsty 纳管的节点上安装,请先使用 bin/node-add 纳管节点。

示例:创建集群

asciicast


创建用户

要在现有的Postgres集群上创建一个新的业务用户,请将用户定义添加到 all.children.<cls>.pg_users,然后使用以下命令将其创建:

bin/pgsql-user <cls> <username>   # ./pgsql-user.yml -l <cls> -e username=<username>
示例:创建业务用户

asciicast


删除用户

要从现有的Postgres集群上安全删除一个用户,请将用户定义中的 state 设置为 absent,然后执行:

bin/pgsql-user <cls> <username>   # ./pgsql-user.yml -l <cls> -e username=<username>

Pigsty 使用 pg-drop-role 脚本安全删除用户,会自动处理所有依赖对象:

  • 自动禁用用户登录,终止活跃连接
  • 自动转移数据库、表空间所有权
  • 自动处理所有数据库中的对象所有权和权限
  • 撤销所有角色成员关系
  • 创建审计日志便于追溯
示例:使用剧本删除用户
  1. 修改配置清单,将用户标记为删除:
pg-meta:
  vars:
    pg_users:
      - name: dbuser_old
        state: absent          # 标记用户为删除状态
  1. 执行删除命令:
bin/pgsql-user pg-meta dbuser_old
示例:手动删除用户

您也可以直接在数据库节点上使用 pg-drop-role 脚本手动删除用户:

# 检查用户依赖关系(只读操作)
sudo -u postgres pg-drop-role dbuser_old --check

# 预览删除操作(不实际执行)
sudo -u postgres pg-drop-role dbuser_old --dry-run -v

# 执行删除(强制终止活跃连接)
sudo -u postgres pg-drop-role dbuser_old --force

删除流程:

  1. 预检查 - 验证连接、检查用户是否存在
  2. 创建审计快照 - 记录用户的所有依赖关系到 /tmp/pg_drop_role_<user>_<timestamp>.log
  3. 禁用登录 - 执行 ALTER ROLE ... NOLOGIN
  4. 终止连接 - 使用 --force 时终止活跃连接
  5. 转移共享对象 - 转移数据库、表空间所有权到 postgres
  6. 处理各数据库 - 在每个数据库中执行 REASSIGN OWNED + DROP OWNED
  7. 撤销成员关系 - 撤销所有角色成员关系
  8. 删除角色 - 执行 DROP ROLE
受保护的系统用户

以下系统用户无法通过 state: absent 删除,会被自动跳过:

  • postgres(超级用户)
  • replicator(或 pg_replication_username 配置的用户)
  • dbuser_dba(或 pg_admin_username 配置的用户)
  • dbuser_monitor(或 pg_monitor_username 配置的用户)

创建数据库

要在现有的Postgres集群上创建一个新的数据库用户,请将数据库定义添加到 all.children.<cls>.pg_databases,然后按照以下方式创建数据库:

bin/pgsql-db <cls> <dbname>       # ./pgsql-db.yml -l <cls> -e dbname=<dbname>

注意:如果数据库指定了一个非默认的属主,该属主用户应当已存在,否则您必须先创建用户

示例:创建业务数据库

asciicast


重载服务

服务是 PostgreSQL 对外提供能力的访问点(PGURL可达),由主机节点上的 HAProxy 对外暴露。

当集群成员发生变化时使用此任务,例如:添加移除副本,主从切换/故障转移 / 暴露新服务,或更新现有服务的配置(例如,LB权重)

要在整个代理集群,或特定实例上创建新服务或重新加载现有服务:

bin/pgsql-svc <cls>               # pgsql.yml -l <cls> -t pg_service -e pg_reload=true
bin/pgsql-svc <cls> [ip...]       # pgsql.yml -l ip... -t pg_service -e pg_reload=true
示例:重载PG服务以踢除一个实例

asciicast


重载HBA

当您的 Postgres/Pgbouncer HBA 规则发生更改时,您 可能 需要重载 HBA 以应用更改。

如果您有任何特定于角色的 HBA 规则,或者在IP地址段中引用了集群成员的别名,那么当主从切换/集群扩缩容后也可能需要重载HBA。

要在整个集群或特定实例上重新加载 postgres 和 pgbouncer 的 HBA 规则:

bin/pgsql-hba <cls>               # pgsql.yml -l <cls> -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true
bin/pgsql-hba <cls> [ip...]       # pgsql.yml -l ip... -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true
示例:重载集群 HBA 规则

asciicast


配置集群

要更改现有的 Postgres 集群配置,您需要在管理节点上使用管理员用户(安装Pigsty的用户,nopass ssh/sudo)发起控制命令:

另一种方式是在数据库集群中的任何节点上,使用 dbsu (默认为 postgres) ,也可以执行管理命令,但只能管理本集群。

pg edit-config <cls>              # interactive config a cluster with patronictl

更改 patroni 参数和 postgresql.parameters,根据提示保存并应用更改即可。

示例:非交互式方式配置集群

您可以跳过交互模式,并使用 -p 选项覆盖 postgres 参数,例如:

pg edit-config -p log_min_duration_statement=1000 pg-test
pg edit-config --force -p shared_preload_libraries='timescaledb, pg_cron, pg_stat_statements, auto_explain'
示例:使用 Patroni REST API 更改集群配置

您还可以使用 Patroni REST API 以非交互式方式更改配置,例如:

$ curl -s 10.10.10.11:8008/config | jq .  # get current config
$ curl -u 'postgres:Patroni.API' \
        -d '{"postgresql":{"parameters": {"log_min_duration_statement":200}}}' \
        -s -X PATCH http://10.10.10.11:8008/config | jq .

注意:Patroni 敏感API(例如重启等) 访问仅限于从基础设施/管理节点发起,并且有 HTTP 基本认证(用户名/密码)以及可选的 HTTPS 保护。

示例:使用 patronictl 配置集群

asciicast


添加实例

若要将新从库添加到现有的 PostgreSQL 集群中,您需要将其定义添加到配置清单:all.children.<cls>.hosts 中,然后:

bin/node-add <ip>                 # 将节点 <ip> 纳入 Pigsty 管理                
bin/pgsql-add <cls> <ip>          # 初始化 <ip> ,作为集群 <cls> 的新从库

这将会把节点 <ip> 添加到 pigsty 并将其初始化为集群 <cls> 的一个副本。

集群服务将会重新加载以接纳新成员。

示例:为 pg-test 添加从库

asciicast

例如,如果您想将 pg-test-3 / 10.10.10.13 添加到现有的集群 pg-test,您首先需要更新配置清单:

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 }

然后按如下方式应用更改:

bin/node-add          10.10.10.13   # 将节点添加到 pigsty
bin/pgsql-add pg-test 10.10.10.13   # 在 10.10.10.13 上为集群 pg-test 初始化新的副本

这与集群初始化相似,但只在单个实例上工作:

[ OK ] 初始化实例  10.10.10.11 到 pgsql 集群 'pg-test' 中:
[WARN]   提醒:先将节点添加到 pigsty 中,然后再安装模块 'pgsql'
[HINT]     $ bin/node-add  10.10.10.11  # 除 infra 节点外,先运行此命令
[WARN]   从集群初始化实例:
[ OK ]     $ ./pgsql.yml -l '10.10.10.11,&pg-test'
[WARN]   重新加载现有实例上的 pg_service:
[ OK ]     $ ./pgsql.yml -l 'pg-test,!10.10.10.11' -t pg_service

移除实例

若要从现有的 PostgreSQL 集群中移除副本:

bin/pgsql-rm <cls> <ip...>        # ./pgsql-rm.yml -l <ip>

这将从集群 <cls> 中移除实例 <ip>。 集群服务将会重新加载以从负载均衡器中踢除已移除的实例。

示例:从 pg-test 移除从库

asciicast

例如,如果您想从现有的集群 pg-test 中移除 pg-test-3 / 10.10.10.13

bin/pgsql-rm pg-test 10.10.10.13  # 从 pg-test 中移除 pgsql 实例 10.10.10.13
bin/node-rm  10.10.10.13          # 从 pigsty 中移除该节点(可选)
vi pigsty.yml                     # 从目录中移除实例定义
bin/pgsql-svc pg-test             # 刷新现有实例上的 pg_service,以从负载均衡器中踢除已移除的实例
[ OK ]'pg-test' 移除 10.10.10.13 的 pgsql 实例:
[WARN]   从集群中移除实例:
[ OK ]     $ ./pgsql-rm.yml -l '10.10.10.13,&pg-test'

并从配置清单中移除实例定义:

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服务并从负载均衡器中踢除已移除的实例:

bin/pgsql-svc pg-test             # 重载 pg-test 上的服务

下线集群

要移除整个 Postgres 集群,只需运行:

bin/pgsql-rm <cls>                # ./pgsql-rm.yml -l <cls>
示例:移除集群

asciicast

示例:强制移除集群

注意:如果为这个集群配置了pg_safeguard(或全局设置为 true),pgsql-rm.yml 将中止,以避免意外移除集群。

您可以使用 playbook 命令行参数明确地覆盖它,以强制执行清除:

./pgsql-rm.yml -l pg-meta -e pg_safeguard=false    # 强制移除 pg 集群 pg-meta

主动切换

您可以使用 patroni 命令行工具执行 PostgreSQL 集群的切换操作。

pg switchover <cls>   # 交互模式,您可以使用下面的参数组合直接跳过此交互向导
pg switchover --leader pg-test-1 --candidate=pg-test-2 --scheduled=now --force pg-test
示例:pg-test 主从切换

asciicast

$ pg switchover pg-test
Master [pg-test-1]:
Candidate ['pg-test-2', 'pg-test-3'] []: pg-test-2
When should the switchover take place (e.g. 2022-12-26T07:39 )  [now]: now
Current cluster topology
+ Cluster: pg-test (7181325041648035869) -----+----+-----------+-----------------+
| Member    | Host        | Role    | State   | TL | Lag in MB | Tags            |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Leader  | running |  1 |           | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-2 | 10.10.10.12 | Replica | running |  1 |         0 | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-3 | 10.10.10.13 | Replica | running |  1 |         0 | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+
Are you sure you want to switchover cluster pg-test, demoting current master pg-test-1? [y/N]: y
2022-12-26 06:39:58.02468 Successfully switched over to "pg-test-2"
+ Cluster: pg-test (7181325041648035869) -----+----+-----------+-----------------+
| Member    | Host        | Role    | State   | TL | Lag in MB | Tags            |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.11 | Replica | stopped |    |   unknown | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-2 | 10.10.10.12 | Leader  | running |  1 |           | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+
| pg-test-3 | 10.10.10.13 | Replica | running |  1 |         0 | clonefrom: true |
|           |             |         |         |    |           | conf: tiny.yml  |
|           |             |         |         |    |           | spec: 1C.2G.50G |
|           |             |         |         |    |           | version: '15'   |
+-----------+-------------+---------+---------+----+-----------+-----------------+

要通过 Patroni API 来执行此操作(例如,在指定时间将主库从 2号实例 切换到 1号实例)

curl -u 'postgres:Patroni.API' \
  -d '{"leader":"pg-test-2", "candidate": "pg-test-1","scheduled_at":"2022-12-26T14:47+08"}' \
  -s -X POST http://10.10.10.11:8008/switchover

无论是主动切换还是故障切换,您都需要在集群成员身份发生变化后,重新刷新服务与HBA规则。您应当在变更发生后及时(例如几个小时,一天内),完成此操作:

bin/pgsql-svc <cls>
bin/pgsql-hba <cls>

备份集群

使用 pgBackRest 创建备份,需要以本地 dbsu (默认为 postgres)的身份运行以下命令:

pg-backup       # 执行备份,如有必要,执行增量或全量备份
pg-backup full  # 执行全量备份
pg-backup diff  # 执行差异备份
pg-backup incr  # 执行增量备份
pb info         # 打印备份信息 (pgbackrest info)

参阅备份恢复获取更多信息。

示例:创建备份

asciicast

示例:创建定时备份任务

您可以将 crontab 添加到 node_crontab 以指定您的备份策略。

# 每天凌晨1点做一次全备份
- '00 01 * * * postgres /pg/bin/pg-backup full'

# 周一凌晨1点进全量备份,其他工作日进行增量备份
- '00 01 * * 1 postgres /pg/bin/pg-backup full'
- '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'

恢复集群

要将集群恢复到先前的时间点 (PITR),请以本地 dbsu 用户(默认为postgres)运行 Pigsty 提供的辅助脚本 pg-pitr

pg-pitr -i                              # 恢复到最近备份完成的时间(不常用)
pg-pitr --time="2022-12-30 14:44:44+08" # 恢复到指定的时间点(在删除数据库或表的情况下使用)
pg-pitr --name="my-restore-point"       # 恢复到使用 pg_create_restore_point 创建的命名恢复点
pg-pitr --lsn="0/7C82CB8" -X            # 在LSN之前立即恢复
pg-pitr --xid="1234567" -X -P           # 在指定的事务ID之前立即恢复,然后将集群直接提升为主库
pg-pitr --backup=latest                 # 恢复到最新的备份集
pg-pitr --backup=20221108-105325        # 恢复到特定备份集,备份集可以使用 pgbackrest info 列出

该命令会输出操作手册,请按照说明进行操作。查看备份恢复-PITR获取详细信息。

示例:使用原始pgBackRest命令进行 PITR
# 恢复到最新可用的点(例如硬件故障)
pgbackrest --stanza=pg-meta restore

# PITR 到特定的时间点(例如意外删除表)
pgbackrest --stanza=pg-meta --type=time --target="2022-11-08 10:58:48" \
   --target-action=promote restore

# 恢复特定的备份点,然后提升(或暂停|关闭)
pgbackrest --stanza=pg-meta --type=immediate --target-action=promote \
  --set=20221108-105325F_20221108-105938I restore

添加软件

要添加新版本的 RPM 包,你需要将它们加入到 repo_packagesrepo_url_packages 中。

使用 ./infra.yml -t repo_build 子任务在 Infra 节点上重新构建本地软件仓库。然后,你可以使用 ansiblepackage 模块安装这些包:

ansible pg-test -b -m package -a "name=pg_cron_15,topn_15,pg_stat_monitor_15*"  # 使用 ansible 安装一些包
示例:手动更本地新软件源中的包
# 在基础设施/管理节点上添加上游软件仓库,然后手工下载所需的软件包
cd ~/pigsty; ./infra.yml -t repo_upstream,repo_cache # 添加上游仓库(互联网)
cd /www/pigsty;  repotrack "some_new_package_name"   # 下载最新的 RPM 包

# 更新本地软件仓库元数据
cd ~/pigsty; ./infra.yml -t repo_create              # 重新创建本地软件仓库
./node.yml -t node_repo                              # 刷新所有节点上的 YUM/APT 缓存

# 也可以使用 Ansible 手工刷新节点上的 YUM/APT 缓存
ansible all -b -a 'yum clean all'                    # 清理节点软件仓库缓存
ansible all -b -a 'yum makecache'                    # 从新的仓库重建yum/apt缓存
ansible all -b -a 'apt clean'                        # 清理 APT 缓存(Ubuntu/Debian)
ansible all -b -a 'apt update'                       # 重建 APT 缓存(Ubuntu/Debian)

例如,你可以使用以下方式安装或升级包:

ansible pg-test -b -m package -a "name=postgresql15* state=latest"

安装扩展

如果你想在 PostgreSQL 集群上安装扩展,请将它们加入到 pg_extensions 中,并执行:

./pgsql.yml -t pg_extension     # 安装扩展

一部分扩展需要在 shared_preload_libraries 中加载后才能生效。你可以将它们加入到 pg_libs 中,或者配置一个已有的集群。

最后,在集群的主库上执行 CREATE EXTENSION <extname>; 来完成扩展的安装。

示例:在 pg-test 集群上安装 pg_cron 扩展
ansible pg-test -b -m package -a "name=pg_cron_15"          # 在所有节点上安装 pg_cron 包
# 将 pg_cron 添加到 shared_preload_libraries 中
pg edit-config --force -p shared_preload_libraries='timescaledb, pg_cron, pg_stat_statements, auto_explain'
pg restart --force pg-test                                  # 重新启动集群
psql -h pg-test -d postgres -c 'CREATE EXTENSION pg_cron;'  # 在主库上安装 pg_cron

更多细节,请参考PGSQL扩展安装


小版本升级

要执行小版本的服务器升级/降级,您首先需要在本地软件仓库中添加软件:最新的PG小版本 RPM/DEB。

首先对所有从库执行滚动升级/降级,然后执行集群主从切换以升级/降级主库。

ansible <cls> -b -a "yum upgrade/downgrade -y <pkg>"    # 升级/降级软件包
pg restart --force <cls>                                # 重启集群
示例:将PostgreSQL 15.2降级到15.1

将15.1的包添加到软件仓库并刷新节点的 yum/apt 缓存:

cd ~/pigsty; ./infra.yml -t repo_upstream               # 添加上游仓库
cd /www/pigsty; repotrack postgresql15-*-15.1           # 将15.1的包添加到yum仓库
cd ~/pigsty; ./infra.yml -t repo_create                 # 重建仓库元数据
ansible pg-test -b -a 'yum clean all'                   # 清理节点仓库缓存
ansible pg-test -b -a 'yum makecache'                   # 从新仓库重新生成yum缓存

# 对于 Ubutnu/Debian 用户,使用 apt 替换 yum
ansible pg-test -b -a 'apt clean'                       # 清理节点仓库缓存
ansible pg-test -b -a 'apt update'                      # 从新仓库重新生成apt缓存

执行降级并重启集群:

ansible pg-test -b -a "yum downgrade -y postgresql15*"  # 降级软件包)
pg restart --force pg-test                              # 重启整个集群以完成升级
示例:将PostgreSQL 15.1升级回15.2

这次我们采用滚动方式升级:

ansible pg-test -b -a "yum upgrade -y postgresql15*"    # 升级软件包(或 apt upgrade)
ansible pg-test -b -a '/usr/pgsql/bin/pg_ctl --version' # 检查二进制版本是否为15.2
pg restart --role replica --force pg-test               # 重启从库
pg switchover --leader pg-test-1 --candidate=pg-test-2 --scheduled=now --force pg-test    # 切换主从
pg restart --role primary --force pg-test               # 重启主库

大版本升级

实现大版本升级的最简单办法是:创建一个使用新版本的新集群,然后通过逻辑复制,蓝绿部署,并进行在线迁移

您也可以进行原地大版本升级,当您只使用数据库内核本身时,这并不复杂,使用 PostgreSQL 自带的 pg_upgrade 即可:

假设您想将 PostgreSQL 大版本从 14 升级到 15,您首先需要在仓库中添加软件,并确保两个大版本两侧安装的核心扩展插件也具有相同的版本号。

./pgsql.yml -t pg_pkg -e pg_version=15                         # 安装pg 15的包
sudo su - postgres; mkdir -p /data/postgres/pg-meta-15/data/   # 为15准备目录
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ -v -c # 预检
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ --link -j8 -v -c
rm -rf /usr/pgsql; ln -s /usr/pgsql-15 /usr/pgsql;             # 修复二进制链接
mv /data/postgres/pg-meta-14 /data/postgres/pg-meta-15         # 重命名数据目录
rm -rf /pg; ln -s /data/postgres/pg-meta-15 /pg                # 修复数据目录链接

4 - 集群管理

创建/销毁 PostgreSQL 集群,以及对现有集群进行扩容与缩容的标准操作指南。

创建集群

要创建一个新的Postgres集群,请首先在配置清单中定义,然后进行初始化:

bin/node-add <cls>                # 为集群 <cls> 初始化节点                  # ./node.yml  -l <cls> 
bin/pgsql-add <cls>               # 初始化集群 <cls> 的pgsql实例             # ./pgsql.yml -l <cls>

请注意,PGSQL 模块需要在 Pigsty 纳管的节点上安装,请先使用 bin/node-add 纳管节点。

示例:创建集群

asciicast


创建用户

要在现有的Postgres集群上创建一个新的业务用户,请将用户定义添加到 all.children.<cls>.pg_users,然后使用以下命令将其创建:

bin/pgsql-user <cls> <username>   # ./pgsql-user.yml -l <cls> -e username=<username>
示例:创建业务用户

asciicast


创建数据库

要在现有的Postgres集群上创建一个新的数据库用户,请将数据库定义添加到 all.children.<cls>.pg_databases,然后按照以下方式创建数据库:

bin/pgsql-db <cls> <dbname>       # ./pgsql-db.yml -l <cls> -e dbname=<dbname>

注意:如果数据库指定了一个非默认的属主,该属主用户应当已存在,否则您必须先创建用户

示例:创建业务数据库

asciicast


重载服务

服务是 PostgreSQL 对外提供能力的访问点(PGURL可达),由主机节点上的 HAProxy 对外暴露。

当集群成员发生变化时使用此任务,例如:添加移除副本,主从切换/故障转移 / 暴露新服务,或更新现有服务的配置(例如,LB权重)

要在整个代理集群,或特定实例上创建新服务或重新加载现有服务:

bin/pgsql-svc <cls>               # pgsql.yml -l <cls> -t pg_service -e pg_reload=true
bin/pgsql-svc <cls> [ip...]       # pgsql.yml -l ip... -t pg_service -e pg_reload=true
示例:重载PG服务以踢除一个实例

asciicast


重载HBA

当您的 Postgres/Pgbouncer HBA 规则发生更改时,您 可能 需要重载 HBA 以应用更改。

如果您有任何特定于角色的 HBA 规则,或者在IP地址段中引用了集群成员的别名,那么当主从切换/集群扩缩容后也可能需要重载HBA。

要在整个集群或特定实例上重新加载 postgres 和 pgbouncer 的 HBA 规则:

bin/pgsql-hba <cls>               # pgsql.yml -l <cls> -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true
bin/pgsql-hba <cls> [ip...]       # pgsql.yml -l ip... -t pg_hba,pg_reload,pgbouncer_hba,pgbouncer_reload -e pg_reload=true
示例:重载集群 HBA 规则

asciicast


配置集群

要更改现有的 Postgres 集群配置,您需要在管理节点上使用管理员用户(安装Pigsty的用户,nopass ssh/sudo)发起控制命令:

另一种方式是在数据库集群中的任何节点上,使用 dbsu (默认为 postgres) ,也可以执行管理命令,但只能管理本集群。

pg edit-config <cls>              # interactive config a cluster with patronictl

更改 patroni 参数和 postgresql.parameters,根据提示保存并应用更改即可。

示例:非交互式方式配置集群

您可以跳过交互模式,并使用 -p 选项覆盖 postgres 参数,例如:

pg edit-config -p log_min_duration_statement=1000 pg-test
pg edit-config --force -p shared_preload_libraries='timescaledb, pg_cron, pg_stat_statements, auto_explain'
示例:使用 Patroni REST API 更改集群配置

您还可以使用 Patroni REST API 以非交互式方式更改配置,例如:

$ curl -s 10.10.10.11:8008/config | jq .  # get current config
$ curl -u 'postgres:Patroni.API' \
        -d '{"postgresql":{"parameters": {"log_min_duration_statement":200}}}' \
        -s -X PATCH http://10.10.10.11:8008/config | jq .

注意:Patroni 敏感API(例如重启等) 访问仅限于从基础设施/管理节点发起,并且有 HTTP 基本认证(用户名/密码)以及可选的 HTTPS 保护。

示例:使用 patronictl 配置集群

asciicast


添加实例

若要将新从库添加到现有的 PostgreSQL 集群中,您需要将其定义添加到配置清单:all.children.<cls>.hosts 中,然后:

bin/node-add <ip>                 # 将节点 <ip> 纳入 Pigsty 管理                
bin/pgsql-add <cls> <ip>          # 初始化 <ip> ,作为集群 <cls> 的新从库

这将会把节点 <ip> 添加到 pigsty 并将其初始化为集群 <cls> 的一个副本。

集群服务将会重新加载以接纳新成员。

示例:为 pg-test 添加从库

asciicast

例如,如果您想将 pg-test-3 / 10.10.10.13 添加到现有的集群 pg-test,您首先需要更新配置清单:

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 }

然后按如下方式应用更改:

bin/node-add          10.10.10.13   # 将节点添加到 pigsty
bin/pgsql-add pg-test 10.10.10.13   # 在 10.10.10.13 上为集群 pg-test 初始化新的副本

这与集群初始化相似,但只在单个实例上工作:

[ OK ] 初始化实例  10.10.10.11 到 pgsql 集群 'pg-test' 中:
[WARN]   提醒:先将节点添加到 pigsty 中,然后再安装模块 'pgsql'
[HINT]     $ bin/node-add  10.10.10.11  # 除 infra 节点外,先运行此命令
[WARN]   从集群初始化实例:
[ OK ]     $ ./pgsql.yml -l '10.10.10.11,&pg-test'
[WARN]   重新加载现有实例上的 pg_service:
[ OK ]     $ ./pgsql.yml -l 'pg-test,!10.10.10.11' -t pg_service

移除实例

若要从现有的 PostgreSQL 集群中移除副本:

bin/pgsql-rm <cls> <ip...>        # ./pgsql-rm.yml -l <ip>

这将从集群 <cls> 中移除实例 <ip>。 集群服务将会重新加载以从负载均衡器中踢除已移除的实例。

示例:从 pg-test 移除从库

asciicast

例如,如果您想从现有的集群 pg-test 中移除 pg-test-3 / 10.10.10.13

bin/pgsql-rm pg-test 10.10.10.13  # 从 pg-test 中移除 pgsql 实例 10.10.10.13
bin/node-rm  10.10.10.13          # 从 pigsty 中移除该节点(可选)
vi pigsty.yml                     # 从目录中移除实例定义
bin/pgsql-svc pg-test             # 刷新现有实例上的 pg_service,以从负载均衡器中踢除已移除的实例
[ OK ]'pg-test' 移除 10.10.10.13 的 pgsql 实例:
[WARN]   从集群中移除实例:
[ OK ]     $ ./pgsql-rm.yml -l '10.10.10.13,&pg-test'

并从配置清单中移除实例定义:

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服务并从负载均衡器中踢除已移除的实例:

bin/pgsql-svc pg-test             # 重载 pg-test 上的服务

下线集群

要移除整个 Postgres 集群,只需运行:

bin/pgsql-rm <cls>                # ./pgsql-rm.yml -l <cls>
示例:移除集群

asciicast

示例:强制移除集群

注意:如果为这个集群配置了pg_safeguard(或全局设置为 true),pgsql-rm.yml 将中止,以避免意外移除集群。

您可以使用 playbook 命令行参数明确地覆盖它,以强制执行清除:

./pgsql-rm.yml -l pg-meta -e pg_safeguard=false    # 强制移除 pg 集群 pg-meta

5 - 用户管理

用户管理:创建、修改、删除用户,管理角色成员关系,连接池用户配置

在 Pigsty 中,用户管理采用 IaC 的风格,先在配置清单中定义,然后执行剧本执行。

执行 pgsql-user.yml 剧本是幂等的。它会将指定集群中的指定用户调整至配置清单中的目标状态。

请注意,用户的所有属性都可以通过重新执行剧本进行修改,剧本是幂等的。


定义用户

Pigsty通过两个配置参数定义数据库集群中的角色与用户:

  • pg_default_roles:定义全局统一使用的角色和用户
  • pg_users:在数据库集群层面定义业务用户和角色

前者用于定义了整套环境中共用的角色与用户,后者定义单个集群中特有的业务角色与用户。二者形式相同,均为用户定义对象的数组。

下面是 Pigsty 演示环境中默认集群 pg-meta 中的业务用户定义:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - { name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [{name: postgis, schema: public}, {name: timescaledb}]}
      - { name: grafana  ,owner: dbuser_grafana  ,revokeconn: true ,comment: grafana primary database }
      - { name: bytebase ,owner: dbuser_bytebase ,revokeconn: true ,comment: bytebase primary database }

唯一必选的字段是 name,它应该是当前 PostgreSQL 集群中有效且唯一的用户名,其他参数都有合理的默认值。 关于用户定义参数的完整参考,请查阅 用户配置参考


创建用户

要在现有的 PostgreSQL 集群上创建新的业务用户,请将用户定义添加到 all.children.<cls>.pg_users,然后执行:

bin/pgsql-user <cls> <username>    # 等效于:pgsql-user.yml -l <cls> -e username=<username>

示例:创建名为 dbuser_app 的业务用户

  1. 在配置文件中添加用户定义:
pg-meta:
  vars:
    pg_users:
      - name: dbuser_app
        password: DBUser.App
        pgbouncer: true
        roles: [dbrole_readwrite]
        comment: application user for myapp
  1. 执行创建命令:
bin/pgsql-user pg-meta dbuser_app

执行效果:

  • 在主库上创建用户 dbuser_app
  • 设置用户密码
  • 授予 dbrole_readwrite 角色
  • 将用户添加到 Pgbouncer 连接池用户列表
  • 重载 Pgbouncer 配置使其生效

示例:创建业务用户

asciicast


修改用户

修改用户属性可以通过更新配置并重新执行剧本来完成:

bin/pgsql-user <cls> <username>    # 幂等操作,可重复执行

不同于数据库,创建用户的剧本总是幂等的。当目标用户已经存在时,Pigsty会修改目标用户的属性使其符合配置。

可修改属性

所有用户属性都可以通过重新执行剧本进行修改:

属性说明示例
password用户密码password: NewPassword
login是否允许登录login: false
superuser是否为超级用户superuser: true
createdb是否可创建数据库createdb: true
createrole是否可创建角色createrole: true
inherit是否继承角色权限inherit: false
replication是否可进行复制replication: true
bypassrls是否可绕过 RLSbypassrls: true
connlimit连接数限制connlimit: 100
expire_in过期天数expire_in: 30
expire_at过期日期expire_at: '2024-12-31'
comment备注信息comment: 新的备注
roles角色成员(增量操作)管理角色成员
parameters角色级参数管理用户参数
pgbouncer是否加入连接池pgbouncer: true
pool_mode连接池模式pool_mode: session
pool_connlimit连接池连接限制pool_connlimit: 50

修改密码

- name: dbuser_app
  password: NewSecretPassword     # 修改密码
bin/pgsql-user pg-meta dbuser_app

执行的 SQL:

SET log_statement TO 'none';
ALTER USER "dbuser_app" PASSWORD 'NewSecretPassword';
SET log_statement TO DEFAULT;

注意:密码修改时会临时禁用日志记录,避免密码泄露到日志中。

修改权限属性

- name: dbuser_app
  createdb: true                  # 允许创建数据库
  connlimit: 50                   # 限制连接数

执行的 SQL:

ALTER USER "dbuser_app" CREATEDB;
ALTER USER "dbuser_app" CONNECTION LIMIT 50;

修改备注

- name: dbuser_app
  comment: '应用主账号 - 已更新'

执行的 SQL:

COMMENT ON ROLE "dbuser_app" IS '应用主账号 - 已更新';

删除用户

要删除用户,将其 state 设置为 absent 并执行剧本:

pg_users:
  - name: dbuser_old
    state: absent
bin/pgsql-user <cls> dbuser_old

删除操作会:

  1. 使用 pg-drop-role 脚本安全删除用户
  2. 自动禁用用户登录,终止活跃连接
  3. 自动转移数据库/表空间所有权到 postgres
  4. 自动处理所有数据库中的对象所有权和权限
  5. 撤销所有角色成员关系
  6. 创建审计日志以便追溯
  7. 从 Pgbouncer 用户列表中移除(如果之前添加过)
  8. 重载 Pgbouncer 配置

受保护的系统用户:

以下系统用户无法通过 state: absent 删除,会被自动跳过:

  • postgres(超级用户)
  • replicator(或 pg_replication_username 配置的用户)
  • dbuser_dba(或 pg_admin_username 配置的用户)
  • dbuser_monitor(或 pg_monitor_username 配置的用户)

pg-drop-role 脚本

pg-drop-role 是 Pigsty 提供的安全删除用户脚本,位于 /pg/bin/pg-drop-role

使用方法:

pg-drop-role <role_name> [successor_role] [options]

常用选项:

选项说明
--check只检查依赖关系,不执行删除
--dry-run显示将要执行的 SQL,不实际执行
--force强制终止活跃连接后删除
-v, --verbose显示详细输出
-h, --host数据库主机
-p, --port数据库端口

示例:

# 检查用户依赖关系(只读操作)
pg-drop-role dbuser_old --check

# 预览删除操作(不实际执行)
pg-drop-role dbuser_old --dry-run -v

# 删除用户,转移对象给 postgres
pg-drop-role dbuser_old

# 删除用户,转移对象给指定用户
pg-drop-role dbuser_old dbuser_new

# 强制删除(终止活跃连接)
pg-drop-role dbuser_old --force

删除流程:

  1. 预检查 - 验证连接、检查用户存在、检查是否受保护
  2. 创建审计快照 - 记录用户的所有依赖关系
  3. 禁用登录 - ALTER ROLE ... NOLOGIN
  4. 终止连接 - 使用 --force 时终止活跃连接
  5. 转移共享对象 - 转移数据库、表空间所有权
  6. 处理各数据库 - 在每个数据库中执行 REASSIGN OWNED + DROP OWNED
  7. 撤销成员关系 - 撤销所有角色成员关系
  8. 删除角色 - 执行 DROP ROLE

手动删除用户

如果需要手动删除用户,可以直接使用 pg-drop-role 脚本:

# 登录到数据库主机
ssh <host>

# 检查依赖关系
sudo -u postgres pg-drop-role dbuser_old --check

# 执行删除
sudo -u postgres pg-drop-role dbuser_old --force

或使用传统 SQL 方式:

-- 查看用户拥有的对象
SELECT n.nspname as schema, c.relname as name, c.relkind as type
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_roles r ON r.oid = c.relowner
WHERE r.rolname = 'dbuser_old';

-- 转移对象所有权
REASSIGN OWNED BY dbuser_old TO postgres;

-- 删除用户拥有的权限
DROP OWNED BY dbuser_old;

-- 删除用户
DROP ROLE dbuser_old;

管理角色成员

角色成员关系通过 roles 数组配置,支持简单格式和扩展格式。

授予角色

- name: dbuser_app
  roles:
    - dbrole_readwrite
    - pg_read_all_data

执行的 SQL:

GRANT "dbrole_readwrite" TO "dbuser_app";
GRANT "pg_read_all_data" TO "dbuser_app";

授予带选项的角色

使用对象格式可以指定更精细的授权选项:

- name: dbuser_admin
  roles:
    # 普通授予
    - dbrole_readwrite

    # 可以将此角色授予其他用户
    - { name: dbrole_admin, admin: true }

    # PG16+: 不能 SET ROLE 到此角色
    - { name: pg_monitor, set: false }

    # PG16+: 不自动继承此角色的权限
    - { name: pg_execute_server_program, inherit: false }

生成的 SQL(PostgreSQL 16+):

GRANT "dbrole_readwrite" TO "dbuser_admin";
GRANT "dbrole_admin" TO "dbuser_admin" WITH ADMIN TRUE;
GRANT "pg_monitor" TO "dbuser_admin";
REVOKE SET OPTION FOR "pg_monitor" FROM "dbuser_admin";
GRANT "pg_execute_server_program" TO "dbuser_admin";
REVOKE INHERIT OPTION FOR "pg_execute_server_program" FROM "dbuser_admin";

撤销角色成员关系

使用 state: absent 撤销角色成员关系:

- name: dbuser_app
  roles:
    - dbrole_readwrite                      # 保留此角色
    - { name: old_role, state: absent }     # 撤销此角色

执行的 SQL:

GRANT "dbrole_readwrite" TO "dbuser_app";
REVOKE "old_role" FROM "dbuser_app";

PostgreSQL 16+ 角色选项

PostgreSQL 16 引入了更细粒度的角色成员关系控制:

选项说明用途
adminADMIN OPTION允许将角色授予其他用户
setSET OPTION允许 SET ROLE 切换到该角色
inheritINHERIT OPTION是否自动继承角色权限

注意setinherit 选项仅在 PostgreSQL 16+ 中有效,在早期版本会被忽略并在生成的 SQL 中添加警告注释。


管理用户参数

用户级参数通过 parameters 字典配置,会生成 ALTER USER ... SET 语句。

设置参数

- name: dbuser_analyst
  parameters:
    work_mem: '256MB'
    statement_timeout: '5min'
    search_path: 'analytics,public'

执行的 SQL:

ALTER USER "dbuser_analyst" SET "work_mem" = '256MB';
ALTER USER "dbuser_analyst" SET "statement_timeout" = '5min';
ALTER USER "dbuser_analyst" SET "search_path" = 'analytics,public';

重置参数为默认值

使用特殊值 DEFAULT(大小写不敏感)将参数重置为 PostgreSQL 默认值:

- name: dbuser_app
  parameters:
    work_mem: DEFAULT             # 重置为默认值
    statement_timeout: DEFAULT

执行的 SQL:

ALTER USER "dbuser_app" SET "work_mem" = DEFAULT;
ALTER USER "dbuser_app" SET "statement_timeout" = DEFAULT;

常用用户级参数

参数说明示例值
work_mem查询工作内存'64MB'
statement_timeout语句超时时间'30s'
lock_timeout锁等待超时'10s'
idle_in_transaction_session_timeout空闲事务超时'10min'
search_pathSchema 搜索路径'app,public'
log_statement日志记录级别'ddl'
temp_file_limit临时文件大小限制'10GB'

管理用户有效期

设置相对过期时间

使用 expire_in 设置从当前日期起 N 天后过期:

- name: temp_user
  expire_in: 30                   # 30 天后过期

执行的 SQL(假设当前日期为 2024-01-15):

ALTER USER "temp_user" VALID UNTIL '2024-02-14';

注意:每次执行剧本时会重新计算过期时间,适合需要定期续期的临时用户。

设置绝对过期时间

使用 expire_at 设置固定的过期日期:

- name: contractor_user
  expire_at: '2024-12-31'         # 指定日期过期

执行的 SQL:

ALTER USER "contractor_user" VALID UNTIL '2024-12-31';

设置永不过期

- name: permanent_user
  expire_at: 'infinity'           # 永不过期

执行的 SQL:

ALTER USER "permanent_user" VALID UNTIL 'infinity';

查看用户过期时间

SELECT rolname, rolvaliduntil
FROM pg_roles
WHERE rolvaliduntil IS NOT NULL
ORDER BY rolvaliduntil;

连接池用户

默认情况下,所有带有 pgbouncer: true 标志的用户都会添加到 Pgbouncer 连接池中。

添加用户到连接池

- name: dbuser_app
  password: DBUser.App
  pgbouncer: true                 # 添加到连接池
  pool_mode: transaction          # 池化模式
  pool_connlimit: 50              # 用户最大连接数

执行效果:

  1. 将用户添加到 /etc/pgbouncer/userlist.txt
  2. 如果指定了 pool_modepool_connlimit,添加到 /etc/pgbouncer/useropts.txt
  3. 重载 Pgbouncer 配置

连接池配置文件

用户列表 /etc/pgbouncer/userlist.txt

"postgres" ""
"dbuser_app" "SCRAM-SHA-256$4096:xxx$yyy:zzz"
"dbuser_view" "SCRAM-SHA-256$4096:xxx$yyy:zzz"

用户选项 /etc/pgbouncer/useropts.txt

dbuser_dba                  = pool_mode=session max_user_connections=16
dbuser_monitor              = pool_mode=session max_user_connections=8
dbuser_app                  = pool_mode=transaction max_user_connections=50

池化模式说明

模式说明适用场景
transaction事务结束后归还连接(默认)大多数 OLTP 应用
session会话结束后归还连接需要会话状态的应用(如 DBA 操作)
statement语句结束后归还连接无状态简单查询

从连接池移除用户

当用户设置 state: absentpgbouncer: false 时,会从连接池中移除。

- name: dbuser_old
  state: absent                   # 删除用户时自动从连接池移除

或:

- name: dbuser_internal
  pgbouncer: false                # 仅从连接池移除,保留用户

使用 Auth Query 动态认证

如果启用了 pgbouncer_auth_query,可以不在 userlist.txt 中维护用户,而是通过查询数据库动态认证。这种方式适合用户数量多、变动频繁的场景。


操作速查

常用命令

操作命令
创建用户bin/pgsql-user <cls> <username>
修改用户bin/pgsql-user <cls> <username>
删除用户设置 state: absent 后执行 bin/pgsql-user <cls> <username>
查看用户列表psql -c '\du'
查看用户详情psql -c '\du+ <username>'
查看连接池用户cat /etc/pgbouncer/userlist.txt
查看用户选项cat /etc/pgbouncer/useropts.txt

常见操作示例

# 创建基本业务用户
- name: dbuser_app
  password: DBUser.App
  pgbouncer: true
  roles: [dbrole_readwrite]
  comment: application user

# 创建只读用户
- name: dbuser_readonly
  password: DBUser.Readonly
  pgbouncer: true
  roles: [dbrole_readonly]

# 创建 DBA 用户(使用 session 模式)
- name: dbuser_dba
  password: DBUser.DBA
  pgbouncer: true
  pool_mode: session
  roles: [dbrole_admin]
  parameters:
    log_statement: 'all'

# 创建临时用户(30天后过期)
- name: temp_contractor
  password: TempPassword
  expire_in: 30
  roles: [dbrole_readonly]

# 创建角色(不可登录)
- name: custom_role
  login: false
  comment: custom role for special permissions

# 创建带高级角色选项的用户(PG16+)
- name: dbuser_admin
  password: DBUser.Admin
  pgbouncer: true
  roles:
    - dbrole_readwrite
    - { name: dbrole_admin, admin: true }
    - { name: pg_monitor, set: false }

# 删除用户
- name: dbuser_old
  state: absent

执行流程

bin/pgsql-user 执行时会依次:

  1. 验证 - 检查 username 参数和用户定义,验证用户名格式
  2. 删除(如果 state=absent)- 执行 DROP ROLE(跳过受保护的系统用户)
  3. 创建/修改 - 执行 CREATE USER 或 ALTER USER
  4. 设置密码 - 执行 ALTER USER PASSWORD(临时禁用日志)
  5. 设置有效期 - 执行 ALTER USER VALID UNTIL
  6. 设置连接限制 - 执行 ALTER USER CONNECTION LIMIT
  7. 设置参数 - 执行 ALTER USER SET
  8. 设置备注 - 执行 COMMENT ON ROLE
  9. 授予角色 - 执行 GRANT/REVOKE 处理角色成员关系
  10. 更新连接池 - 刷新 Pgbouncer 用户列表并重载

SQL 查询参考

-- 查看所有用户
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
       rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil
FROM pg_roles
WHERE rolname NOT LIKE 'pg_%'
ORDER BY rolname;

-- 查看用户的角色成员关系
SELECT r.rolname AS member, g.rolname AS role,
       m.admin_option, m.set_option, m.inherit_option
FROM pg_auth_members m
JOIN pg_roles r ON r.oid = m.member
JOIN pg_roles g ON g.oid = m.roleid
WHERE r.rolname = 'dbuser_app';

-- 查看用户级参数设置
SELECT rolname, setconfig
FROM pg_db_role_setting s
JOIN pg_roles r ON r.oid = s.setrole
WHERE s.setdatabase = 0;  -- 0 表示用户级设置

-- 查看用户拥有的对象
SELECT n.nspname AS schema, c.relname AS name,
       CASE c.relkind
         WHEN 'r' THEN 'table'
         WHEN 'v' THEN 'view'
         WHEN 'm' THEN 'materialized view'
         WHEN 'i' THEN 'index'
         WHEN 'S' THEN 'sequence'
         WHEN 'f' THEN 'foreign table'
       END AS type
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_roles r ON r.oid = c.relowner
WHERE r.rolname = 'dbuser_app'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema');

-- 查看即将过期的用户
SELECT rolname, rolvaliduntil,
       rolvaliduntil - CURRENT_TIMESTAMP AS time_remaining
FROM pg_roles
WHERE rolvaliduntil IS NOT NULL
  AND rolvaliduntil < CURRENT_TIMESTAMP + INTERVAL '30 days'
ORDER BY rolvaliduntil;

关于用户定义的完整参数参考,请参考 用户配置 一节。

关于用户的访问权限,请参考 ACL:角色权限 一节。

6 - 参数优化

调整 postgres 参数

Pigsty 默认提供了四套场景化参数模板,可以通过 pg_conf 参数指定并使用。

  • tiny.yml:为小节点、虚拟机、小型演示优化(1-8核,1-16GB)
  • oltp.yml:为OLTP工作负载和延迟敏感应用优化(4C8GB+)(默认模板)
  • olap.yml:为OLAP工作负载和吞吐量优化(4C8G+)
  • crit.yml:为数据一致性和关键应用优化(4C8G+)

Pigsty 会针对这四种默认场景,采取不同的参数优化策略,如下所示:


内存参数调整

Pigsty 默认会检测系统的内存大小,并以此为依据设定最大连接数量与内存相关参数。

默认情况下,Pigsty 使用 25% 的内存作为 PostgreSQL 共享缓冲区,剩余的 75% 作为操作系统缓存。

默认情况下,如果用户没有设置一个 pg_max_conn 最大连接数,Pigsty 会根据以下规则使用默认值:

  • oltp: 500 (pgbouncer) / 1000 (postgres)
  • crit: 500 (pgbouncer) / 1000 (postgres)
  • tiny: 300
  • olap: 300

其中对于 OLTP 与 CRIT 模版来说,如果服务没有指向 pgbouncer 连接池,而是直接连接 postgres 数据库,最大连接会翻倍至 1000 条。

决定最大连接数后,work_mem 会根据共享内存数量 / 最大连接数计算得到,并限定在 64MB ~ 1GB 的范围内。

{% raw %}
{% if pg_max_conn != 'auto' and pg_max_conn|int >= 20 %}{% set pg_max_connections = pg_max_conn|int %}{% else %}{% if pg_default_service_dest|default('postgres') == 'pgbouncer' %}{% set pg_max_connections = 500 %}{% else %}{% set pg_max_connections = 1000 %}{% endif %}{% endif %}
{% set pg_max_prepared_transactions = pg_max_connections if 'citus' in pg_libs else 0 %}
{% set pg_max_locks_per_transaction = (2 * pg_max_connections)|int if 'citus' in pg_libs or 'timescaledb' in pg_libs else pg_max_connections %}
{% set pg_shared_buffers = (node_mem_mb|int * pg_shared_buffer_ratio|float) | round(0, 'ceil') | int %}
{% set pg_maintenance_mem = (pg_shared_buffers|int * 0.25)|round(0, 'ceil')|int %}
{% set pg_effective_cache_size = node_mem_mb|int - pg_shared_buffers|int  %}
{% set pg_workmem =  ([ ([ (pg_shared_buffers / pg_max_connections)|round(0,'floor')|int , 64 ])|max|int , 1024])|min|int %}
{% endraw %}

CPU参数调整

在 PostgreSQL 中,有 4 个与并行查询相关的重要参数,Pigsty 会自动根据当前系统的 CPU 核数进行参数优化。 在所有策略中,总并行进程数量(总预算)通常设置为 CPU 核数 + 8,且保底为 16 个,从而为逻辑复制与扩展预留足够的后台 worker 数量,OLAP 和 TINY 模板根据场景略有不同。

OLTP设置逻辑范围限制
max_worker_processesmax(100% CPU + 8, 16)核数 + 4,保底 12,
max_parallel_workersmax(ceil(50% CPU), 2)1/2 CPU 上取整,最少两个
max_parallel_maintenance_workersmax(ceil(33% CPU), 2)1/3 CPU 上取整,最少两个
max_parallel_workers_per_gathermin(max(ceil(20% CPU), 2),8)1/5 CPU 下取整,最少两个,最多 8 个
OLAP设置逻辑范围限制
max_worker_processesmax(100% CPU + 12, 20)核数 + 12,保底 20,
max_parallel_workersmax(ceil(80% CPU, 2))4/5 CPU 上取整,最少两个
max_parallel_maintenance_workersmax(ceil(33% CPU), 2)1/3 CPU 上取整,最少两个
max_parallel_workers_per_gathermax(floor(50% CPU), 2)1/2 CPU 上取整,最少两个
CRIT设置逻辑范围限制
max_worker_processesmax(100% CPU + 8, 16)核数 + 8,保底 16,
max_parallel_workersmax(ceil(50% CPU), 2)1/2 CPU 上取整,最少两个
max_parallel_maintenance_workersmax(ceil(33% CPU), 2)1/3 CPU 上取整,最少两个
max_parallel_workers_per_gather0, 按需启用
TINY设置逻辑范围限制
max_worker_processesmax(100% CPU + 4, 12)核数 + 4,保底 12,
max_parallel_workersmax(ceil(50% CPU) 1)50% CPU 下取整,最少1个
max_parallel_maintenance_workersmax(ceil(33% CPU), 1)33% CPU 下取整,最少1个
max_parallel_workers_per_gather0, 按需启用

请注意,CRIT 和 TINY 模板直接通过设置 max_parallel_workers_per_gather = 0 关闭了并行查询。 用户可以按需在需要时设置此参数以启用并行查询。

OLTP 和 CRIT 模板都额外设置了以下参数,将并行查询的 Cost x 2,以降低使用并行查询的倾向。

parallel_setup_cost: 2000           # double from 100 to increase parallel cost
parallel_tuple_cost: 0.2            # double from 0.1 to increase parallel cost
min_parallel_table_scan_size: 16MB  # double from 8MB to increase parallel cost
min_parallel_index_scan_size: 1024  # double from 512 to increase parallel cost

请注意 max_worker_processes 参数的调整必须在重启后才能生效。此外,当从库的本参数配置值高于主库时,从库将无法启动。 此参数必须通过 patroni 配置管理进行调整,该参数由 Patroni 管理,用于确保主从配置一致,避免在故障切换时新从库无法启动。


存储空间参数

Pigsty 默认检测 /data/postgres 主数据目录所在磁盘的总空间,并以此作为依据指定下列参数:

{% raw %}
min_wal_size: {{ ([pg_size_twentieth, 200])|min }}GB                  # 1/20 disk size, max 200GB
max_wal_size: {{ ([pg_size_twentieth * 4, 2000])|min }}GB             # 2/10 disk size, max 2000GB
max_slot_wal_keep_size: {{ ([pg_size_twentieth * 6, 3000])|min }}GB   # 3/10 disk size, max 3000GB
temp_file_limit: {{ ([pg_size_twentieth, 200])|min }}GB               # 1/20 of disk size, max 200GB
{% endraw %}
  • temp_file_limit 默认为磁盘空间的 5%,封顶不超过 200GB。
  • min_wal_size 默认为磁盘空间的 5%,封顶不超过 200GB。
  • max_wal_size 默认为磁盘空间的 20%,封顶不超过 2TB。
  • max_slot_wal_keep_size 默认为磁盘空间的 30%,封顶不超过 3TB。

作为特例, OLAP 模板允许 20% 的 temp_file_limit ,封顶不超过 2TB


手工调整参数

除了使用 Pigsty 自动配置的参数外,您还可以手工调整 PostgreSQL 参数。

使用 pg edit-config <cluster> 命令可以交互式编辑集群配置:

pg edit-config pg-meta

或者使用 -p 参数直接设置参数:

pg edit-config -p log_min_duration_statement=1000 pg-meta
pg edit-config --force -p shared_preload_libraries='timescaledb, pg_cron, pg_stat_statements, auto_explain' pg-meta

您也可以使用 Patroni REST API 来修改配置:

curl -u 'postgres:Patroni.API' \
    -d '{"postgresql":{"parameters": {"log_min_duration_statement":200}}}' \
    -s -X PATCH http://10.10.10.10:8008/config | jq .

7 - 故障排查

常见故障与分析排查思路

本文档列举了 PostgreSQL 和 Pigsty 中可能出现的故障,以及定位、处理、分析问题的 SOP。


磁盘空间写满

磁盘空间写满是最常见的故障类型。

现象

当数据库所在磁盘空间耗尽时,PostgreSQL 将无法正常工作,可能出现以下现象:数据库日志反复报错"no space left on device"(磁盘空间不足), 新数据无法写入,甚至 PostgreSQL 可能触发 PANIC 强制关闭。

Pigsty 带有 NodeFsSpaceFull 告警规则,当文件系统可用空间不足 10% 时触发告警。 使用监控系统 NODE Instance 面板查阅 FS 指标面板定位问题。

诊断

您也可以登录数据库节点,使用 df -h 查看各挂载盘符使用率,确定哪个分区被写满。 对于数据库节点,重点检查以下目录及其大小,以判断是哪个类别的文件占满了空间:

  • 数据目录/pg/data/base):存放表和索引的数据文件,大量写入与临时文件需要关注
  • WAL目录(如 pg/data/pg_wal):存放 PG WAL,WAL 堆积/复制槽保留是常见的磁盘写满原因。
  • 数据库日志目录(如 pg/log):如果 PG 日志未及时轮转写大量报错写入,也可能占用大量空间。
  • 本地备份目录(如 data/backups):使用 pgBackRest 等在本机保存备份时,也有可能撑满磁盘。

如果问题出在 Pigsty 管理节点或监控节点,还需考虑:

  • 监控数据:VictoriaMetrics 的时序指标和 VictoriaLogs 日志存储都会占用磁盘,可检查保留策略。
  • 对象存储数据:Pigsty 集成的 MinIO 对象存储可能会被用于 PG 备份保存。

明确占用空间最大的目录后,可进一步使用 du -sh <目录> 深入查找特定大型文件或子目录。

处理

磁盘写满属于紧急问题,需立即采取措施释放空间并保证数据库继续运行。 当数据盘并未与系统盘区分时,写满磁盘可能导致 Shell 命令无法执行。这种情况下,可以删除 /pg/dummy 占位文件,释放少量应急空间以便 shell 命令恢复正常。 如果数据库由于 pg_wal 写满已经宕机,清理空间后需要重启数据库服务并仔细检查数据完整性。


事务号回卷

PostgreSQL 循环使用 32 位事务ID (XID),耗尽时会出现"事务号回卷"故障(XID Wraparound)。

现象

第一阶段的典型征兆是 PGSQL Persist - Age Usage 面板年龄饱和度进入警告区域。 数据库日志开始出现:WARNING: database "postgres" must be vacuumed within xxxxxxxx transactions 字样的信息。

若问题持续恶化,PostgreSQL 会进入保护模式:当剩余事务ID不到约100万时数据库切换为只读模式;达到上限约21亿(2^31)时则拒绝任何新事务并迫使服务器停机以避免数据错误。

诊断

PostgreSQL 与 Pigsty 默认启用自动垃圾回收(AutoVacuum),因此此类故障出现通常有更深层次的根因。 常见的原因包括:超长事务(SAGE),Autovacuum 配置失当,复制槽阻塞,资源不足,存储引擎/扩展BUG,磁盘坏块。

首先定位年龄最大的数据库,然后可通过 Pigsty PGCAT Database - Tables 面板来确认表的年龄分布。 同时查阅数据库错误日志,通常可以找到定位根因的线索。

处理

  1. 立即冻结老事务:如果数据库尚未进入只读保护状态,立刻对受影响的库执行一次手动 VACUUM FREEZE。可以从老化最严重的表开始逐个冻结,而不是整库一起做,以加快效果。使用超级用户连接数据库,针对识别出的 relfrozenxid 最大的表运行 VACUUM FREEZE 表名;,优先冻结那些XID年龄最大的表元组。这样可以迅速回收大量事务ID空间。
  2. 单用户模式救援:如果数据库已经拒绝写入或宕机保护,此时需要启动数据库到单用户模式执行冻结操作。在单用户模式下运行 VACUUM FREEZE database_name; 对整个数据库进行冻结清理。完成后再以多用户模式重启数据库。这样做可以解除回卷锁定,让数据库重新可写。需要注意在单用户模式下操作要非常谨慎,并确保有足够的事务ID余量完成冻结。
  3. 备用节点接管:在某些复杂场景(例如遭遇硬件问题导致 vacuum 无法完成),可考虑提升集群中的只读备节点为主,以获取一个相对干净的环境来处理冻结。例如主库因坏块导致无法 vacuum,此时可以手动Failover提升备库为新的主库,再对其进行紧急 vacuum freeze。确保新主库已冻结老事务后,再将负载切回来。

连接耗尽

PostgreSQL 有一个最大连接数配置 (max_connections),当客户端连接数超过此上限时,新的连接请求将被拒绝。典型现象是在应用端看到数据库无法连接,并报出类似 FATAL: remaining connection slots are reserved for non-replication superuser connectionstoo many clients already 的错误。 这表示普通连接数已用完,仅剩下保留给超管或复制的槽位

诊断

连接耗尽通常由客户端大量并发请求引起。您可以通过 PGCAT Instance / PGCAT Database / PGCAT Locks 直接查阅数据库当前的活跃会话。 并判断是什么样的查询填满了系统,并进行进一步的处理。特别需要关注是否存在大量 Idle in Transaction 状态的连接以及长时间运行的事务(以及慢查询)。

处理

杀查询:对于已经耗尽导致业务受阻的情况,通常立即使用 pg_terminate_backend(pid) 进行紧急降压。 对于使用连接池的情况,则可以调整连接池大小参数,并执行 reload 重载的方式减少数据库层面的连接数量。

您也可以修改 max_connections 参数为更大的值,但本参数需要重启数据库后才能生效。


etcd 配额写满

etcd 配额写满将导致 PG 高可用控制面失效,无法进行配置变更。

诊断

Pigsty 在实现高可用时使用 etcd 作为分布式配置存储(DCS),etcd 自身有一个存储配额(默认约为2GB)。 当 etcd 存储用量达到配额上限时,etcd 将拒绝写入操作,报错 “etcdserver: mvcc: database space exceeded"。在这种情况下,Patroni 无法向 etcd 写入心跳或更新配置,从而导致集群管理功能失效。

解决

在 Pigsty v2.0.0 - v2.5.1 之间的版本默认受此问题影响。Pigsty v2.6.0 为部署的 etcd 新增了自动压实的配置项,如果您仅将其用于 PG 高可用租约,则常规用例下不会再有此问题。


有缺陷的存储引擎

目前,TimescaleDB 的试验性存储引擎 Hypercore 被证实存在缺陷,已经出现 VACUUM 无法回收出现 XID 回卷故障的案例。 请使用该功能的用户及时迁移至 PostgreSQL 原生表或者 TimescaleDB 默认引擎

详细介绍:《PG新存储引擎故障案例

8 - 误删处理

处理误删数据,误删表,误删数据库

误删数据

如果是小批量 DELETE 误操作,可以考虑使用 pg_surgery 或者 pg_dirtyread 扩展进行原地手术恢复。

-- 立即关闭此表上的 Auto Vacuum 并中止 Auto Vacuum 本表的 worker 进程
ALTER TABLE public.some_table SET (autovacuum_enabled = off, toast.autovacuum_enabled = off);

CREATE EXTENSION pg_dirtyread;
SELECT * FROM pg_dirtyread('tablename') AS t(col1 type1, col2 type2, ...);

如果被删除的数据已经被 VACUUM 回收,那么使用通用的误删处理流程。

误删对象

当出现 DROP/DELETE 类误操作,通常按照以下流程决定恢复方案。

  1. 确认此数据是否可以通过业务系统或其他数据系统找回,如果可以,直接从业务侧修复。
  2. 确认是否有延迟从库,如果有,推进延迟从库至误删时间点,查询出来恢复。
  3. 如果数据已经确认删除,确认备份信息,恢复范围是否覆盖误删时间点,如果覆盖,开始 PITR
  4. 确认是整集群原地 PITR 回滚,还是新开服务器重放,还是用从库来重放,并执行恢复策略

误删集群

如果出现整个数据库集群通过 Pigsty 管理命令被误删的情况,例如错误的执行 pgsql-rm.yml 剧本或 bin/pgsql-rm 命令。 除非您指定了 pg_rm_backup 参数为 false,否则备份会与数据库集群一起被删除。

警告:在这种情况,您的数据将无法找回!请务必三思而后行!

建议:对于生产环境,您可以在配置清单中全局配置此参数为 false,在移除集群时保留备份。

9 - 克隆副本

如何克隆数据库,克隆数据库实例,克隆数据库集群?

PostgreSQL 已经可以通过物理从库,逻辑从库的方式复制数据, 但有时候你可能需要高速 克隆 一个数据库,数据库实例,或者整个数据库集群。 克隆出来的数据库可以写入,独立演进,不影响原有数据库。在 Pigsty 中,有以下几种克隆方法:

  • 克隆数据库:在同一个集群内,克隆一个新的数据库
  • 克隆实例:在同一个 PG 节点上,克隆一个新的实例
  • 克隆集群:使用 PITR 机制创建一个新的数据库集群,并恢复到指定集群的任意时间点

克隆数据库

你可以通过 template 机制复制一个 PostgreSQL 数据库,但在此期间不允许有任何连接到模版数据库的活动连接。

假设你想要克隆 postgres 数据库,那么必须一次性同时执行下面两条语句。 确保清理掉所有连接到 postgres 数据库的连接后执行 Clone

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'postgres'; 
CREATE DATABASE pgcopy TEMPLATE postgres STRATEGY FILE_COPY;

瞬间克隆

如果你使用的是 PostgreSQL 18 以上的版本,Pigsty 默认为您设置了 file_copy_method。 该参数允许你以 O(1) (约 200ms)的时间复杂度克隆一个数据库,而不需要复制数据文件。

但是您必须显式使用 FILE_COPY 策略来创建数据库。 CREATE DATABASESTRATEGY 参数自 PostgreSQL 15 引入以来的默认值为 WAL_LOG,你需要显式指定 FILE_COPY 来进行瞬间克隆。

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'postgres';
CREATE DATABASE pgcopy TEMPLATE postgres STRATEGY FILE_COPY;

例如,克隆一个 30 GB 的数据库,普通克隆(WAL_LOG)用时 18 秒,而瞬间克隆(FILE_COPY)仅需常数时间 200 毫秒。

但是,您仍然需要确保在克隆期间没有任何连接到模版数据库的活动连接,但这个时间可以非常短暂,因此具有生产环境的实用性。 如果您需要一个新的数据库副本用于测试或开发,瞬间克隆是一个非常好的选择。它并不会引入额外的存储开销,因为它使用了文件系统的 CoW(Copy on Write)机制。

Pigsty v4.0 起,您可以在 pg_databases 参数中使用 strategy: FILE_COPY 来实现瞬间克隆数据库。

    pg-meta:
      hosts:
        10.10.10.10: { pg_seq: 1, pg_role: primary }
      vars:
        pg_cluster: pg-meta
        pg_version: 18
        pg_databases:

          - name: meta

          - name: meta_dev
            template: meta
            strategy: FILE_COPY         # <---- PG 15 引入, PG18 瞬间生效 
            #comment: "meta clone"      # <---- 数据库注释
            #pgbouncer: false           # <---- 不加入 连接池?
            #register_datasource: false # <---- 不加入 Grafana 数据源?        

配置完毕后,使用标准数据库创建 SOP 创建该数据库即可:

bin/pgsql-db pg-meta meta_dev

局限性与注意事项

请注意,这个特性仅在支持的文件系统上可用(xfs,brtfs,zfs,apfs),如果文件系统不支持,PostgreSQL 将会报错失败。 默认情况下,主流操作系统发行版的 xfs 都已经默认启用 reflink=1 选项,因此大多数情况下您不需要担心这个问题。 OpenZFS 需要显式配置才能支持 CoW,但因为存在数据损坏的先例,不建议将此特性用于生产。

如果您使用的 PostgreSQL 版本低于 15,指定 strategy 不会有任何效果。

请不要使用 postgres 数据库作为模版数据库进行克隆,因为管理链接通常会连接到 postgres 数据库,这阻止了克隆操作的进行。 如果您确实需要克隆 postgres 数据库,请你手动连接到其他数据库上后,自行执行 SQL 实现。

在极高并发/吞吐的生产环境中使用瞬间克隆需要谨慎,它需要在克隆窗口(200ms)内清理掉所有连接到模版数据库的连接,否则克隆会失败。

10 - 维护保养

常见系统维护任务

要确保 Pigsty 与 PostgreSQL 集群健康稳定运行,需要进行一些例行维护保养工作。


定期查阅监控

Pigsty 提供了开箱即用的监控平台,我们建议您每天浏览一次监控大盘,关注系统状态。 极端情况下,我们建议您每周至少查阅一次监控,关注出现的告警事件,这样可以提前规避绝大多数故障与问题。

这里列举了 Pigsty 中预先定义的 告警规则 列表。


故障切换善后

Pigsty 的高可用架构允许 PostgreSQL 集群自动进行主从切换,这意味着运维与 DBA 无需即时介入与响应。 然而用户仍然需要在合适的时机(例如第二天工作日)进行以下善后工作,包括:

  • 调查并确认故障出现的原因,避免再次出现
  • 视情况恢复集群原本的主从拓扑,或者修改配置清单以匹配新的主从状态。
  • 通过 bin/pgsql-svc 刷新负载均衡器配置,更新服务的路由状态
  • 通过 bin/pgsql-hba 刷新集群的 HBA 规则,避免主从特定的规则漂移
  • 如果有必要,使用 bin/pgsql-rm 移除故障服务器,并通过 bin/pgsql-add 扩容一台新从库

表膨胀治理

长时间运行的 PostgreSQL 会出现 “表膨胀” / “索引膨胀” 现象, 导致系统性能劣化。

定期使用 pg_repack 对表与索引进行在线重建,有助于维护 PostgreSQL 的良好性能表现。 Pigsty 已经默认在所有数据库中安装并启用了此扩展,因此您可以直接使用。

您可以通过 Pigsty 的 PGCAT Database - Table Bloat 面板, 确认数据库中的表膨胀情况与索引膨胀情况。并选择膨胀率较高(膨胀率高于 50% 的较大表)的表与索引,使用 pg_repack 进行在线重整:

pg_repack dbname -t schema.table

重整期间不会影响正常读写,但重整完毕之后的 切换瞬间 需要获取表上的 AccessExclusive 锁阻塞一切访问。 因此对于高吞吐量业务,建议在业务低峰期或者维护窗口进行。更多细节,请参考:关系膨胀的治理


VACUUM FREEZE

冻结过期事务ID(VACUUM FREEZE)是PostgreSQL重要的维护任务,用于防止事务ID (XID) 用尽导致停机。 尽管 PostgreSQL 已经提供了自动垃圾回收(AutoVacuum)机制,然而对于高标准的生产环境, 我们依然建议结合自动和手动两种方式,定期执行全库级别的 VACUUM FREEZE ,以确保 XID 安全。

您可以使用以下命令手工对数据库执行 VACUUM FREEZE:

-- 对整个数据库执行 VACUUM FREEZE
VACUUM FREEZE;

-- 对特定表执行 VACUUM FREEZE
VACUUM FREEZE schema.table_name;

或者通过 crontab 设置定时任务,例如每周日凌晨执行:

# 每周日凌晨3点对所有数据库执行 VACUUM FREEZE
0 3 * * 0 postgres psql -c 'VACUUM FREEZE;' dbname

11 - 版本升级

如何升级(或降级) PostgreSQL 小版本内核,以及如何进行大版本升级。

小版本升级

要执行小版本的服务器升级/降级,您首先需要在本地软件仓库中 添加软件:最新的PG小版本 RPM/DEB。

首先对所有从库执行滚动升级/降级,然后执行集群 主从切换以升级/降级主库。

ansible <cls> -b -a "yum upgrade/downgrade -y <pkg>"    # 升级/降级软件包
pg restart --force <cls>                                # 重启集群

这次我们采用滚动方式升级:

ansible pg-test -b -a "yum upgrade -y postgresql15*"    # 升级软件包(或 apt upgrade)
ansible pg-test -b -a '/usr/pgsql/bin/pg_ctl --version' # 检查二进制版本是否为15.2
pg restart --role replica --force pg-test               # 重启从库
pg switchover --leader pg-test-1 --candidate=pg-test-2 --scheduled=now --force pg-test    # 切换主从
pg restart --role primary --force pg-test               # 重启主库

小版本降级

将15.1的包添加到软件仓库并刷新节点的 yum/apt 缓存:

cd ~/pigsty; ./infra.yml -t repo_upstream               # 添加上游仓库
cd /www/pigsty; repotrack postgresql15-*-15.1           # 将15.1的包添加到yum仓库
cd ~/pigsty; ./infra.yml -t repo_create                 # 重建仓库元数据
ansible pg-test -b -a 'yum clean all'                   # 清理节点仓库缓存
ansible pg-test -b -a 'yum makecache'                   # 从新仓库重新生成yum缓存

# 对于 Ubutnu/Debian 用户,使用 apt 替换 yum
ansible pg-test -b -a 'apt clean'                       # 清理节点仓库缓存
ansible pg-test -b -a 'apt update'                      # 从新仓库重新生成apt缓存

执行降级并重启集群:

ansible pg-test -b -a "yum downgrade -y postgresql15*"  # 降级软件包)
pg restart --force pg-test                              # 重启整个集群以完成升级

大版本升级

实现大版本升级的最简单办法是:创建一个使用新版本的新集群,然后通过逻辑复制,蓝绿部署,并进行 在线迁移

您也可以进行原地大版本升级,当您只使用数据库内核本身时,这并不复杂,使用 PostgreSQL 自带的 pg_upgrade 即可:

假设您想将 PostgreSQL 大版本从 14 升级到 15,您首先需要在仓库中 添加软件,并确保两个大版本两侧安装的核心扩展插件也具有相同的版本号。

./pgsql.yml -t pg_pkg -e pg_version=15                         # 安装pg 15的包
sudo su - postgres; mkdir -p /data/postgres/pg-meta-15/data/   # 为15准备目录
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ -v -c # 预检
pg_upgrade -b /usr/pgsql-14/bin/ -B /usr/pgsql-15/bin/ -d /data/postgres/pg-meta-14/data/ -D /data/postgres/pg-meta-15/data/ --link -j8 -v -c
rm -rf /usr/pgsql; ln -s /usr/pgsql-15 /usr/pgsql;             # 修复二进制链接
mv /data/postgres/pg-meta-14 /data/postgres/pg-meta-15         # 重命名数据目录
rm -rf /pg; ln -s /data/postgres/pg-meta-15 /pg                # 修复数据目录链接