# 模板参数与集群变更

LLMS 索引： [llms.txt](/llms.txt)

---

Pigsty 把 PostgreSQL 参数放进：

```text
hardware/workload template
  + cluster inventory
      + instance override
          + Patroni dynamic configuration
              + database/role defaults
```

这解决的是：

> 如何从同一份 desired state 可重复生成、分发、验证集群配置？

它不自动回答：

> 这个值是否适合我的 workload？

模板负责起点，实验负责偏离模板的理由。

## 27.6.1 从模板生成实例配置 {#item-27-6-1}

### 四类起点

当前 Pigsty 官方模板：

| `pg_conf` | 目标 |
|---|---|
| `tiny.yml` | 小节点、开发/演示、受限资源 |
| `oltp.yml` | 延迟敏感交易 |
| `olap.yml` | 扫描、分析、较低并发与较高并行 |
| `crit.yml` | 更保守的关键业务策略 |

示例：

```yaml
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
  vars:
    pg_cluster: pg-test
    pg_conf: oltp.yml
    node_tune: oltp
```

模板会根据 CPU、memory、disk/workload profile 计算多项参数。Pigsty
[optimization policy](https://pigsty.io/docs/pgsql/template/tune/)
当前以 25% memory 作为 shared buffer 默认起点，并按 profile 处理 connection、
parallel、vacuum、WAL 与 timeout。

### profile 不是标签

选择 `olap` 不只是：

```text
work_mem larger
```

还可能改变：

```text
connections
parallel workers
maintenance
vacuum
timeout
I/O settings
logging
```

因此把 existing production cluster 从 `oltp.yml` 换到 `olap.yml` 是 multi-factor
change。不要用一次切换来“测试 OLAP 参数”；先生成 diff，拆成可归因变更。

### hardware 与 profile 必须匹配

Pigsty 官方把 `tiny` 用于小型/受限节点；`oltp`/`olap` 文档面向更大的常规实例。
如果 1C2G 节点使用 aggressive profile：

- template 仍可能生成 syntactically valid 配置；
- server 仍可能启动；
- 但 worst-concurrency memory/worker budget 可能不安全。

参考沙箱就是一个值得审阅的事实：

```text
server RAM       ~1.91 GiB
max_connections  500
work_mem          64 MiB
shared_buffers   ~489 MiB
```

这不代表当前 idle/8-client workload 已 OOM；它表示 platform limit 不能被应用当作
“500 条复杂 query 的安全并发”。

### 先预览 recommendation

当前 `pig` CLI 提供 tuning output：

```bash
pig pg tune
pig pg tune -p tiny
pig pg tune -p olap
pig pg tune -c 8 -m 32768 -d 500 -o yaml
```

这些命令用于生成/查看 recommendation；先确认本机安装版本的 `pig pg tune --help`。
输出不是自动批准的生产变更。保存：

```text
Pigsty version
PostgreSQL major
detected/overridden CPU memory disk
profile
generated output hash
```

同一 profile 在不同 Pigsty release 可能演进，升级后要 diff。

### `pg_parameters` 显式覆盖

```yaml
pg-test:
  hosts:
    10.10.10.11:
      pg_seq: 1
      pg_role: primary
    10.10.10.12:
      pg_seq: 2
      pg_role: replica
      pg_parameters:
        recovery_min_apply_delay: '5min'
  vars:
    pg_cluster: pg-test
    pg_conf: oltp.yml
    pg_parameters:
      log_min_duration_statement: 250
      track_io_timing: on
```

用途：

```text
template baseline
  + reviewed cluster exception
      + reviewed instance exception
```

不是把所有 template 输出再复制一遍。重复复制会失去 template upgrade 能力。

### inventory precedence

Pigsty 的 inventory 可以在 global、cluster、host 定义 `pg_parameters`，越具体的
inventory 变量覆盖越通用。还要叠加 PostgreSQL 自己的 file/DCS/catalog/session
precedence。

因此两层问题：

```text
Ansible variable resolution
  -> rendered configuration
      -> PostgreSQL/Patroni precedence
          -> effective session value
```

只看 YAML 不能证明最后生效。

### list 参数的 YAML quoting

```yaml
pg_parameters:
  shared_preload_libraries: 'timescaledb, pg_stat_statements, auto_explain'
  search_path: '"$user", public, app'
```

list-like GUC 必须作为一个 string 正确引用，避免 YAML 误解析或引号层级错误。render
后还要用 `pg_file_settings` 检查。

### database 与 role 参数

某个 workload 特有的：

```text
statement_timeout
work_mem
max_parallel_workers_per_gather
search_path
default_transaction_read_only
```

优先落到 Pigsty business object 的 database/user parameter，而不是 cluster global。
它们最终进入 `pg_db_role_setting`，新 session 生效。

scope 要匹配：

```text
all workload on cluster     cluster/instance
one database                database
one application identity    role-in-database
one job                     transaction/session
```

### 参数 exception 的元数据

YAML 本身不保存“为什么”。在 ADR/注释/变更系统记录：

```yaml
parameter: work_mem
scope: role dbuser_report in database analytics
value: 256MB
reason: report-v4 spill experiment
evidence_run: ...
owner: data-platform
expires/review: 2026-10-01
rollback: 32MB
```

没有 expiry 的 exception 会永久累积。

## 27.6.2 区分 reload、restart 与滚动执行 {#item-27-6-2}

### 先从 context 生成动作

```sql
SELECT
    name,
    context,
    setting,
    pending_restart
FROM pg_settings
WHERE name = ANY (ARRAY[
    'work_mem',
    'checkpoint_timeout',
    'shared_buffers',
    'max_connections',
    'shared_preload_libraries'
])
ORDER BY name;
```

动作矩阵：

| context/scope | 持久层 | 应用 |
|---|---|---|
| role/database | catalog/IaC | 新 session |
| `user`/`superuser` global default | config | reload + session lifecycle |
| `sighup` | config/DCS | reload |
| `backend` | config | reload + reconnect |
| `postmaster` | config/DCS | restart |
| init/build | cluster/binary | migration/rebuild |

### 应用 `pg_parameters`

Pigsty 官方当前给出的 instance 参数应用入口：

```bash
./pgsql.yml -l pg-test -t pg_param
```

它会把 `pg_parameters` 渲染到受管配置。执行前：

```text
确认 inventory 与 limit
查看 playbook version/help
做 diff/preview
确认 target 是 cluster 而非全环境
确认 secrets 不进命令行/log
```

执行后仍需 reload/restart 语义验证；“Ansible changed=1”不是 effective。

### Patroni dynamic configuration

Patroni 的 cluster dynamic config 存在 DCS，由所有 member 消费；local config 又可能
覆盖 DCS。改变 Patroni 管理的 PostgreSQL 参数时要识别 owner。

Pigsty/Patroni 文档说明：

- dynamic config 会传播到成员；
- 非 restart 参数随后 reload；
- postmaster 参数会标 `pending_restart`/`restart_pending`；
- local Patroni config 可能优先于 dynamic；
- bootstrap DCS config 只用于初始建群，之后应改 dynamic config。

不要只编辑最初 inventory 里的 bootstrap fragment，期待现有 DCS 自动变化。

### reload

集群：

```bash
pg reload pg-test
```

本机 PostgreSQL：

```bash
pig pg reload
```

命令面与版本有关，执行前看 `--help`。两者 scope 不同：一个通过 Patroni 面向
cluster/member，一个是本机 service 操作。

reload 后：

```sql
SELECT pg_reload_conf();
```

返回 true 只表示 signal 发出，不等于每项应用成功。查：

```sql
SELECT * FROM pg_file_settings WHERE error IS NOT NULL;
SELECT name, setting, source, pending_restart
FROM pg_settings
WHERE name IN (...);
```

### restart

restart 会断开本 member 的 session；HA cluster 中可能由 replica 承载重启，但：

- primary restart 仍需 switchover/connection behavior；
- replica 重启时丧失一份冗余；
- catch-up 产生 I/O/WAL load；
- sync quorum 可能变化；
- pool/client 会 reconnect；
- session state/prepared statement 消失。

执行前必须有：

```text
healthy replica count
lag within gate
backup/archive healthy
N+1 capacity
restart duration/RTO
client retry budget
rollback restart time
```

`immediate` restart 会触发 crash recovery，不是普通快速捷径。

### rolling 顺序

典型而非万能：

```text
1. one replica
2. wait streaming/caught-up + effective validation
3. next replica
4. controlled switchover if primary must change
5. former primary
6. end-to-end service validation
```

每一步 gate：

```text
Patroni role/state
timeline/LSN
replication slots
archive
service route
pending_restart
SLO/resource
```

若 candidate 导致 member 起不来，不应继续下一个。

### mixed-config window

滚动期间：

```text
member A candidate
member B baseline
```

要回答：

- replication compatible？
- failover 到 A/B 各如何？
- read route 结果/性能不同？
- logical worker/preload plugin compatible？
- monitoring/alert 能区分？
- rollback 是否仍可启动？

若不能容忍 mixed state，就不能称为 rolling change，需要 maintenance/migration design。

### canary member 的局限

在 replica 测 `work_mem`/planner 参数：

- read-only workload 可以；
- primary write/WAL/commit 行为不能；
- cache、data freshness、route 不同；
- replica conflict/recovery 干扰；
- promote 后 workload 变化。

canary 必须代表目标 mechanism。

## 27.6.3 用 SQL 和文件事实验证最终生效值 {#item-27-6-3}

### desired inventory

保存：

```text
git commit
inventory path
cluster/member limit
profile and explicit overrides
render/playbook version
review/approval
```

不要在 public artifact 中发布 secret inventory。

### configured file

```sql
SELECT
    sourcefile,
    sourceline,
    seqno,
    name,
    setting,
    applied,
    error
FROM pg_file_settings
WHERE name IN (...) OR error IS NOT NULL
ORDER BY seqno;
```

它能发现：

```text
syntax error
unknown parameter
invalid value
duplicate overridden entry
restart-required entry not applied to runtime
```

但 view 反映 file 当前内容，不是 last applied。

### effective server/session

```sql
SELECT
    inet_server_addr() AS server,
    current_setting('cluster_name') AS cluster,
    pg_is_in_recovery() AS in_recovery,
    name,
    setting,
    unit,
    context,
    source,
    pending_restart
FROM pg_settings
WHERE name = ANY (ARRAY[
    'shared_buffers',
    'work_mem',
    'max_connections',
    'checkpoint_timeout',
    'max_wal_size',
    'plan_cache_mode'
])
ORDER BY name;
```

每个 member、每种 service path、新旧 session 都要取样。

### normalized units

比较时用 canonical bytes/ms：

```sql
SELECT
    name,
    current_setting(name) AS display,
    setting,
    unit
FROM pg_settings
WHERE name IN ('shared_buffers', 'work_mem', 'checkpoint_timeout');
```

`setting=62592, unit=8kB` 与 `489MB` 可能同值。字符串 diff 会制造假 drift。

### Patroni/HA fact

```text
cluster config in DCS
member pending_restart
member role/state/timeline/lag
PostgreSQL effective setting
```

四层要对齐。DCS 有 candidate 但 member 仍 pending restart，不算完成；PostgreSQL
effective candidate 但 inventory 仍 baseline，也不算完成。

### workload fact

变更生效不等于 hypothesis 成立。继续验证：

```text
objective
non-regression
resource
failure/recovery
observation window
```

第 27 章实验同时保存：

- global settings before/after；
- session requested/effective `plan_cache_mode`；
- prepared custom/generic count；
- representative plan-shape hash；
- raw transaction latency；
- cleanup。

所以能证明：

```text
candidate was actually tested
global config was not changed
auto already selected generic after early custom plans
material gain rule did not pass
```

### 验收矩阵

| 层 | 证据 | pass |
|---|---|---|
| desired | inventory commit/diff | exact candidate |
| rendered | file/DCS | no unexpected entries |
| syntax | `pg_file_settings` | no error |
| effective | `pg_settings` every member | value/source/context |
| lifecycle | pending restart/new session | complete |
| HA | Patroni/replication | healthy |
| behavior | plans/SLO/resources | gates pass |
| rollback | restored desired/effective | tested |

少任意一层，都只能标记 `partially applied` 或 `pending`。

### 配置变更的最终原则

```text
Pigsty template gives a reviewed starting point
IaC gives repeatability
Patroni gives HA-aware distribution
PostgreSQL views give runtime truth
experiment gives causal confidence
ADR gives memory and accountability
```

任何单层都不能替代其余层。

---

[上一节：参数作用域与变更方式](../05/) · [返回本章目录](../) · [下一节：实战：只调一个已证实的瓶颈](../07/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
