万法归宗:PostgreSQL 数据平台与替代边界
18 万法归宗:PostgreSQL 数据平台与替代边界
前 17 章分别回答了许多“PostgreSQL 能不能”的问题:
第 18 章换一个问法:
即使都能做,哪些能力应当留在 PostgreSQL,哪些只适合试点,哪些应当交给 外部系统;又由谁对组合后的整体服务负责?
这是从“数据库产品”走向“数据平台”的分水岭。
数据库不会因为装了更多扩展就自动成为平台;HA、备份、连接池和监控也不会 因为有了部署脚本就自动成为服务。平台至少还要给出:
本章把上卷的功能证据组合成一份 1.6-proposal,同时把所有尚未证明的生产
结论交给下卷 18 个证据门。它不是庆功章,而是一份边界清单。
本章完成后
你应当能够:
- 把事务、检索、时空、分析和异步任务拆成独立能力,而不是用一个产品名概括;
- 区分数据/计算、存储、接入、控制与观察平面;
- 解释为什么多个健康组件仍可能交付一个不健康的端到端服务;
- 用延迟、新鲜度、正确性、可用性、RPO/RTO、容量和成本描述组合目标;
- 说明 PostgreSQL 的关系一致性、类型系统、扩展机制和统一查询为何有价值;
- 同时说明通用数据库中的 CPU、内存、I/O、WAL、连接、锁和维护竞争;
- 把“扩展已经安装”与“扩展已经生产准入”分开;
- 为缓存、消息、对象存储、湖仓和外部检索划分逐数据域的权威;
- 为每个派生副本写出新鲜度、顺序、幂等、重建、对账、删除和退出契约;
- 用测量触发器而不是技术潮流决定何时引入专用检索、流处理或分析系统;
- 设计开发、标准 HA、关键 HA 和离线分析四类服务产品;
- 在 schema、database、instance 和 cluster 隔离之间按信任与故障边界选择;
- 把连接、存储、临时文件、语句时间和维护窗口变成可执行配额;
- 说明 Pigsty 4.5 如何映射 PostgreSQL、Patroni、etcd、HAProxy、PgBouncer、 pgBackRest 与观测能力;
- 分清 Pigsty 开箱能力、环境验收和组织流程;
- 运行一个完全只读的上卷能力审计;
- 读懂服务目录、外置数据契约、架构 ADR 与下卷证据门之间的引用关系;
- 拒绝“无证据宣称 L1 已通过”“缓存成为业务权威”“实验 FDW 准入生产”等 反例;
- 把
pg36_shop的当前结论准确表达为架构提案,而不是生产完成声明。
一张平台地图
图的关键不是方框数量,而是箭头上的合同。只要发生跨系统复制,就必须回答:
这些字段被固化在
external-data-contracts.json,
不是留给实现阶段再想的“细节”。
当前提案,不是当前承诺
第 18 章的服务目录有四个 offering:
| ID | 用途 | 关键边界 |
|---|---|---|
pg-dev |
有期限的开发/测试 | 不承诺 HA |
pg-ha-standard |
默认生产事务服务 | 目标待下卷证明 |
pg-ha-critical |
更强隔离与同步策略候选 | 必须先定义故障域 |
pg-analytics-offline |
ETL、慢读、交互分析 | 非权威、非 read-your-writes |
service_objectives 中出现的可用性、RPO、RTO 和新鲜度都是 proposal。节点数
不能证明故障域,配置文件不能证明收敛,备份成功不能证明可恢复,仪表盘存在
不能证明告警可行动。
因此蓝图明确写着:
这个诚实程度是架构质量的一部分。
PostgreSQL 的能力与代价同时成立
PostgreSQL 把关系约束、事务、丰富类型、函数、操作符、索引方法和查询规划器 放在同一个一致性边界中。官方 Extending SQL 列出的扩展点包括函数、聚合、数据类型、操作符、索引操作符类和扩展包。第 14–17 章已经证明这套机制可以把检索、向量、空间与远端数据纳入 SQL。
同一事实还有另一面:
PostgreSQL 18 的
Resource Consumption
特别提醒,work_mem 是每个 sort/hash 操作的基础上限;一条复杂查询可有
多个操作,同时还有多个会话。因此“通用”不是“无限”,统一也不是“免费”。
本章不在这两个事实中二选一。架构工作正是保留统一带来的收益,同时给资源 竞争、生命周期和失败传播设边界。
Pigsty 的位置
Pigsty 4.5 的 Architecture 以声明式 inventory 和模块组合交付环境;PGSQL、INFRA、NODE、ETCD 等模块 把 PostgreSQL 与 HA、接入、备份和观察组件连接起来。
本书把它作为参考实现,因为它能让抽象职责落到可读配置:
| 抽象职责 | Pigsty 参考映射 |
|---|---|
| 数据服务 | PostgreSQL cluster / database / role |
| HA 控制 | Patroni + etcd |
| 服务接入 | HAProxy,按需配 PgBouncer / VIP / DNS |
| 恢复 | pgBackRest 与仓库策略 |
| 主机与软件 | NODE / 软件仓库 / inventory |
| 指标与日志 | exporter、VictoriaMetrics/Logs、Grafana、Alertmanager |
| 分析隔离 | pg_role: offline 或 pg_offline_query |
Pigsty 官方 Service Access 把 service 定义为封装底层拓扑的访问抽象;本书沿用这个语义,不把某个节点 IP 叫作生产服务。
但参考实现不替团队完成:
这也是为什么
pigsty-declaration.example.yml
只能叫 proposal。
只读总验收
本章没有 setup,也没有 reset。它只读前面章节已经保留的 fixture:
然后连续抓取两轮:
两轮必须逐字节一致,最终输出:
通过只表示“当前开发证据与提案内部一致”,不表示任何生产 SLO 已经实现。
本章目录
18.1 从数据库产品到能力组合
18.2 PostgreSQL 的强项与代价
18.3 明确替代边界
18.4 平台服务目录与多租户
18.5 Pigsty 作为参考实现
18.6 实战:设计 pg36_shop 生产蓝图
写作与验收提示
- 本章实验合同:
lab-contract.md; - 服务目录:
service-catalog.json; - 外置数据契约:
external-data-contracts.json; - 生产蓝图提案:
baseline-v1.6-proposal.json; - 下卷证据门:
lower-volume-gates.json; - 架构决定:
architecture-adr.md; - 可执行入口:
task.sh。
下一章从第一个 pending gate 开始:不谈抽象生产级,而是建立 部署基线与环境验收。
上一章:合纵连横:分析加速与分布式选型 · 返回上卷导读 · 下一章:开天辟地:环境规划与部署基线 · 查看全书目录 · 查看索引中心
18.1 从数据库产品到能力组合
当团队说“我们用 PostgreSQL”时,这句话通常混合了三种不同事实:
产品可以启动,不代表所有能力可用;能力可以运行,不代表服务可承诺。平台 设计的第一步,是把这三层重新拆开。
18.1.1 事务、检索、时空、分析与任务能力
从业务问题开始,而不是从组件清单开始
pg36_shop 至少需要处理以下业务问题:
| 业务问题 | 需要的能力 | 首选正确性边界 |
|---|---|---|
| 订单与库存能否保持不变量 | 关系约束、事务、并发控制 | PostgreSQL commit |
| 应用如何安全调用数据能力 | 角色、schema、prepared SQL、API 合同 | DB + 应用 |
| 商品如何被关键词找到 | 全文/模糊检索、排序、质量 golden | PostgreSQL 起步 |
| 商品如何按语义相似找到 | embedding、向量距离、ANN 质量 | 试点 |
| 配送事件在哪里、何时有效 | 空间、时间、边界和参考系 | PostgreSQL 起步 |
| 月报如何快速且不过度影响交易 | 并行、索引、汇总、离线读 | PostgreSQL + 隔离 |
| 订单事件如何送到其他服务 | outbox、relay、消息投递 | 跨系统合同 |
| 图片字节放在哪里 | 对象存储与元数据协调 | 按数据域拆分 |
先写能力,能避免两种常见偷换:
例如,vector 扩展已经在第 14 章的开发 fixture 中安装,<=> 距离查询也在
第 15 章得到正确结果。这证明“语义检索试验可运行”,没有证明:
- embedding 模型会被稳定版本化;
- ANN 在生产规模下满足质量与延迟;
- 索引重建落在维护窗口;
- 副本、备份、恢复和升级路径全部可用;
- 团队可以在模型漂移时诊断结果变化。
所以它的生命周期是 pilot,不是 accepted。
事务能力是权威状态的锚
对订单、库存和支付,最重要的不是“能存 JSON”或“QPS 很高”,而是所有 写入者看到同一套不可违反的事实:
第 4、10、13 章分别从约束、并发与数据库逻辑证明了这些能力。只要这类状态 仍由 PostgreSQL 定夺,缓存、搜索索引、事件流和湖仓都只能是派生物。
“source of truth” 容易变成口号,更精确的写法是按数据域声明 authority:
冲突时相信谁,必须在发生冲突前写清楚。
检索是一组能力,不是一条 LIKE
商品检索至少可以分成:
第 15 章的质量 golden 证明 pg_trgm 适合当前模糊检索基线,vector 适合
受控 pilot。它没有把“搜索”宣布为永久留在 PostgreSQL。蓝图保存一个明确
触发器:
当质量、规模、语言能力或独立可用性目标击败 PostgreSQL 基线时,才启用
external-search-projection-v1。
这样,外部检索是一个由证据触发、可重建、可退出的投影,而不是架构图里一 开始就存在的时髦方框。
时空能力先统一语义,再比较引擎
空间系统最危险的错误往往不是查询慢,而是答案看起来合理:
第 16 章把 EPSG:4326、UTC、区间与边界规则固化为 fixture。PostGIS 让空间 类型、操作符与索引进入 SQL,但业务合同仍然高于扩展:迁移到其他引擎时, 这些语义必须不变。
因此 capability 的结构至少要包含:
分析能力先减少工作,再横向扩展
第 17 章已经建立顺序:
当前蓝图选择“本地汇总 + offline replica”作为第一步,而不是选择 Citus。 这不是永久拒绝分布式;第 26 章的容量证据可以触发新 ADR。
同理,postgres_fdw 的 loopback fixture 只证明过滤、聚合和部分失败的
查询形状。PostgreSQL 官方
Foreign Data
说明外表通过 wrapper 访问外部数据,并通过 user mapping 提供认证信息。
本书实验使用的 password_required=false 没有生产身份、网络与故障域,
所以严格标为 lab-only。
任务能力要分事务内与事务外
“数据库里能执行函数”不等于“任何业务任务都应放进事务”。一个实用边界:
留在事务内:
移到事务外:
事务内代码失败可以回滚;远端世界通常不能跟着 PostgreSQL 回滚。把两者硬塞 在一起,会产生持锁时间、连接占用、重试重复和不可控级联。
能力清单必须有状态
本章采用以下生命周期词汇:
| 状态 | 含义 |
|---|---|
accepted |
已有当前范围证据,仍受版本与服务门约束 |
accepted-with-scope |
只在明确狭窄边界内接受 |
accepted-first-step |
当前首选,但容量触发器仍待观察 |
conditional |
前置门尚未全部通过 |
pilot |
只允许受控试验,不进入默认生产服务 |
lab-only |
教学机制证据,禁止生产准入 |
deferred |
触发条件未出现,不增加系统 |
没有状态的能力清单会把“听说过”“装得上”“试过一次”和“可值班”放在同一 列,最终无法治理。
18.1.2 计算、存储、接入、控制与观察平面
能力回答“要做什么”,平面回答“由哪类机制完成”。
数据与计算平面
数据/计算平面直接处理请求与业务状态:
它的典型证据是:
- SQL 结果和业务校验和;
- 执行计划与实际行数;
- 事务、锁与 SQLSTATE;
- replica replay 位置;
- 投影 watermark。
在这个平面上,“成功”意味着某次业务操作按合同完成,不代表平台整体健康。
存储平面
存储平面不仅是 PGDATA:
| 存储 | 保存什么 | 关键风险 |
|---|---|---|
| PostgreSQL data files | heap、index、catalog | 损坏、容量、延迟 |
| WAL | 恢复与复制所需变化 | 归档缺口、保留爆炸 |
| backup repository | base/diff/incr 与 WAL | 不可恢复、凭据、同域失效 |
| temp | sort/hash 等中间结果 | 磁盘耗尽、I/O 争用 |
| object storage | 媒体或备份对象 | 清单、版本、删除 |
| cache | 可丢弃派生数据 | 陈旧、穿透、错误权威 |
| analytical/search index | 可重建投影 | lag、语义漂移、删除遗漏 |
“数据有三份”不是恢复策略。三份流复制副本会复制同一条误删;三个位于同一 存储故障域的节点也不是三个独立副本。第 21、32、35 章会分别验证备份恢复、 PITR 与损坏取证。
接入平面
接入平面把服务语义变成客户端可连接的端点:
一个主库 IP 只是位置,不是服务。客户端需要知道:
Pigsty 的
Service Access
用端口和 selector 表达 primary、replica、default、offline 等服务。
第 22 章会把这些入口与 PgBouncer 语义、连接预算一起验收。
控制平面
控制平面改变系统的期望状态:
控制平面故障与数据平面故障不同。数据库可以继续处理流量,而 inventory 已经 漂移;也可能数据库本身健康,但错误的健康检查把流量切走。
控制平面必须回答:
- 谁能改;
- 改什么对象;
- 如何审阅;
- 是否幂等;
- 如何观察收敛;
- 哪一步是最后可逆点;
- 失败时由谁接管。
Ansible 成功退出只说明某次自动化执行没有报告失败;它不是业务 SLO 证据。
观察平面
观察平面收集并解释:
观察平面不应只回答“图绿不绿”,还要让值班者完成因果链:
第 25 章会验证信号到 runbook 的连接。本章只列出必须覆盖的八类信号,不声称 告警已经有效。
管理平面与业务平面不要共用身份
一个常见反模式:
本书 fixture 已把角色分开:
| 身份 | 当前边界 |
|---|---|
pg36_app |
LOGIN、非 superuser、运行时 |
pg36_owner |
NOLOGIN、对象所有者 |
postgres |
本地正式实验管理员 |
这还不是完整生产角色模型。第 23 章要继续拆出迁移、监控、备份、复制、审计 与应急身份。
一项能力会穿过多个平面
以“商品模糊搜索”为例:
只验证 SQL 正确,会漏掉四个平面;只部署组件,则连第一项也未必正确。
18.1.3 组件组合必须有统一服务目标
局部健康不推出整体健康
假设请求路径是:
每个组件都有自己的“up”,但业务关心的是:
如果数据库 20ms 提交、relay 卡 40 分钟,订单 API 的数据库延迟指标仍然很 漂亮,业务事件服务却已经违约。
先定义服务,再分配组件目标
一个服务目标模板:
| 维度 | 示例 |
|---|---|
| 用户动作 | 创建订单 |
| 成功定义 | 返回稳定 order_id,状态可读,重复请求无第二次效果 |
| 延迟 | API P95/P99 |
| 正确性 | 约束、金额、状态、幂等全部成立 |
| 可用性 | 测量窗口、排除项、错误预算 |
| 新鲜度 | 提交后 read-your-writes;事件 publish lag |
| 持久性 | 故障模型内的 RPO |
| 恢复 | 场景化 RTO,不只“启动成功” |
| 容量 | 峰值并发、增长与余量 |
| 安全 | 身份、租户、数据分类 |
| 成本 | 服务单位成本与预算 |
组件指标从这个目标推导。不要反过来因为某仪表盘有一个指标,就把它升格为 服务 SLI。
可用性相乘只是一种初步直觉
若一条同步路径必须经过多个独立组件,在非常简化的独立假设下:
三个各 99.9% 的串行依赖约为:
这提醒我们“组件都三个九”并不保证路径三个九。但不能把这个公式当精确生产 模型,因为:
- 故障往往相关,共享网络/电源/配置/身份;
- 重试、缓存和降级会改变路径;
- 读写操作依赖不同;
- 维护与区域故障不是独立伯努利事件;
- 业务正确性失败可能不表现为组件 down。
真正的目标要通过故障模型、演练和真实 SLI 验证。
新鲜度预算也会叠加
派生投影可能经历:
总 lag 不是只看 broker lag。每一段要有时间戳或位置:
| 阶段 | 证据 |
|---|---|
| source commit | commit LSN / business version |
| publication intent | outbox timestamp |
| broker | partition/offset |
| consumer | acknowledged source identity |
| projection | applied version / watermark |
| query | served generation |
没有共同 identity,端到端新鲜度无法对账。
正确性优先于可用性包装
“失败时返回旧缓存”可能提高响应可用性,也可能把已取消订单重新显示为有效。 降级必须按数据和操作分类:
同一个缓存组件不能用一个统一 fallback 策略覆盖所有字段。
SLO 目标必须与证据状态绑定
本章目录中的:
故意带有 -proposal。要去掉它,至少要经过:
一项数字若没有:
它只是愿望。
统一目标不等于统一部署
服务目标统一,是为了让组件协作;并不要求把组件放在同一主机、同一进程或 同一团队。
反过来,部署在同一 PostgreSQL cluster 也不自动意味着目标相同:
- 交易写入需要 read-your-writes;
- 离线分析允许 60 秒 lag;
- 备份任务关注可恢复性;
- 搜索 pilot 关注质量与构建时间。
平台要把这些 workload class 显式分开,并为冲突设优先级。
用一个“合同—证据—动作”闭环
每项服务能力最终应形成:
本章的四份 JSON 和一个 ADR 正是在演示这个闭环。它们的价值不在文件格式, 而在于架构结论可以被程序拒绝、被证据更新,也可以在条件变化时退出。
18.2 PostgreSQL 的强项与代价
PostgreSQL 最容易被两种叙事误读:
前者低估它,后者透支它。本节把收益与代价放在同一张账上。
18.2.1 关系一致性、可扩展类型与统一查询
关系模型把错误状态变成不可提交状态
应用校验常写成:
当有多个写入者、并发事务、批处理和人工运维时,这段逻辑很容易被绕过。 PostgreSQL 的独特价值不是“也能做校验”,而是把很多不变量放在最终提交 边界:
不合法状态不只是“应用不建议写”,而是任何没有绕过权限边界的写入者都不能 提交。
第 4 章模型的关系校验和:
第 18 章每轮只读审计都会重新计算。它不是通用完整性证明,但把平台蓝图锚定 到一份确定的业务数据,而不是空白数据库。
一致性靠多个层次共同完成
“用事务”仍然太笼统。一个可靠模型通常组合:
| 层次 | 适合表达 |
|---|---|
| type/domain | 单值表示和基本范围 |
| column constraint | 必填、局部规则 |
row CHECK |
同一行字段关系 |
| unique/exclusion | 跨行唯一或不重叠 |
| foreign key | 引用存在与生命周期 |
| transaction | 多对象原子变更 |
| lock/isolation | 并发可见性与冲突 |
| function/trigger | SQL 约束难以表达的短原子规则 |
| application workflow | 远端调用、长流程、人机审批 |
越靠近数据的规则覆盖写入路径越广,但也越应短小、稳定、可解释。第 13 章
保留 accepted-with-scope,就是防止把业务编排全部塞进触发器。
类型不是列上的装饰
PostgreSQL 的类型参与:
所以 timestamptz、range、jsonb、vector 和 PostGIS geometry 不只是
不同的文本格式。类型、操作符和索引方法共同决定什么语义可以被查询与加速。
PostgreSQL 官方 Extending SQL 把数据类型、函数、聚合、操作符和索引操作符类都列为扩展点。这使一个扩展 能够进入规划器和执行器,而不只是作为应用旁边的黑盒服务。
第 16 章的:
之所以能与普通关系条件组合,正是因为空间与 range 语义进入了 PostgreSQL 的类型和索引体系。
统一查询减少一致性缝隙
如果订单、商品、空间和搜索投影都在同一事务边界,应用可以:
潜在收益:
- 一个快照;
- 一套权限;
- 一个查询计划;
- 一次网络往返;
- 一个可解释的事务边界;
- 少一条跨系统同步链;
- 少一套重试、对账和删除流程。
这不是说“一条大 SQL 总是最好”,而是跨系统拆分具有固定税:
只有当拆分收益超过这组税,外置才有充分理由。
MVCC 让读写共存,但不消除冲突
PostgreSQL 的多版本并发控制让读者通常不阻塞普通写者,事务按快照看数据。 这使 OLTP、报表和维护可以在同一引擎协作。
但 MVCC 并不表示:
- 长事务没有代价;
- DDL 不需要重锁;
- 所有隔离级别结果相同;
- 副本读没有延迟;
- dead tuple 会自动即时消失;
- 写写冲突不需要处理。
统一引擎减少系统间一致性缝隙,内部仍要管理锁、快照、vacuum、序列化失败与 重试。这些成本在第 10、28、34 章分别展开。
统一系统目录让证据可查询
PostgreSQL 对象不是散落在配置文件中的传说。可以从 catalog 读取:
本章
extension-catalog.sql
固定扩展名、版本、schema、owner、relocatable 与 comment;
schema-catalog.sql
固定教学 schema 的 owner、marker 和对象计数。
这种自描述能力让平台可以做:
但 catalog snapshot 只说明数据库内部状态;操作系统 package、共享库和各 副本节点仍需要主机层 inventory。
可移植性要按层讨论
“SQL 标准”不能概括可移植性。至少分:
| 层 | 可能的锁定 |
|---|---|
| schema/SQL | PostgreSQL 语法、函数、行为差异 |
| types | range、array、JSONB、vector、geometry |
| indexes | GIN/GiST/BRIN/opclass |
| routines | PL/pgSQL 与 trigger |
| extensions | binary/package/version |
| operations | backup、replication、HA、monitoring |
| semantics | collation、time zone、SRID、isolation |
有价值的 PostgreSQL 特性不必因为“可能锁定”就不用;应为高锁定能力写 export、 restore、替代与退出测试。锁定被管理,和假装不存在,是两种完全不同的架构。
18.2.2 通用性带来的资源竞争与维护责任
同一进程体系,共享多种稀缺资源
当交易、搜索、空间和分析都进入 PostgreSQL,它们共享:
“查询彼此不锁”只覆盖其中一类竞争。
内存预算会按节点、worker 和并发放大
PostgreSQL 18 官方
Resource Consumption
说明 work_mem 是一个查询操作开始写临时文件前的基础内存限制;复杂查询可
同时有多个 sort/hash 操作,多个会话也会并行运行。
因此粗略风险模型不是:
而更接近:
它不是精确容量公式,但足以拒绝:
第 17 章的单查询反例只证明两种执行路径,不授权全局参数变更。第 27 章必须 在真实并发预算下做单变量实验。
CPU 并行会把延迟问题变成吞吐问题
并行查询可缩短一个聚合,但 worker 不是免费核心:
当机器已经饱和,更多并行可能让每条查询和系统总吞吐都变差。平台要同时看:
- 单查询 latency;
- 总吞吐;
- runnable queue;
- worker 是否实际获得;
- 交易查询 tail latency;
- background maintenance 债务。
搜索与空间索引有写放大和生命周期
一个索引的成本不仅是磁盘:
GIN、GiST、HNSW、BRIN 与 B-tree 的目标和代价不同。给每个新查询“加个索引” 可能把读延迟转移成写入、恢复与维护事故。
服务目录因此按 extension bundle 准入,而不是允许每个租户自由安装任意扩展。
WAL 是统一持久性的收益,也是共享压力面
在一个 PostgreSQL cluster 内,许多变更进入同一 WAL/复制/归档链。收益是 恢复语义统一;代价是:
- 大批量索引构建可能推动 WAL;
- 分析汇总刷新可能影响 replica lag;
- 一个归档故障会积压整个 cluster;
- logical slot 可能阻止 WAL 回收;
- 恢复需要理解所有扩展对象。
把某能力外置也不会让代价消失,只会把它变成跨系统日志与对账。选择应比较 两边完整成本。
长快照会制造维护债务
慢报表在 primary 上运行时,即使是只读,也可能:
offline replica 可以隔离一部分 CPU/I/O 与连接,但 replica 上的长查询可能 与 WAL replay 冲突,导致查询取消或 replay 延迟。它是新的合同,不是免费 读扩容。
一个 cluster 的爆炸半径必须被命名
共享 cluster 中,以下事件可能影响多个 database:
schema 隔离挡不住这些;database 隔离也挡不住 cluster 级失败。只有在信任、 性能、升级、恢复或故障影响需要时,才上升到 instance/cluster 隔离。
维护责任不会被“开源免费”消除
每一项能力都要有人承担:
许可证成本为零,运行责任仍然存在。一个无人负责的扩展,比一个功能较少但 有人值班的基线更危险。
用资源账本评估组合
对每项 workload 建议记录:
| 项 | 峰值 | 隔离/配额 | 超限动作 |
|---|---|---|---|
| active connections | 待测 | pool + role limit | queue/reject |
| CPU | 待测 | service/host class | shed/defer |
| work memory | 待测 | role/session policy | spill/cancel |
| temp bytes | 待测 | temp_file_limit |
fail query |
| statement time | 待测 | workload timeout | cancel |
| storage growth | 待测 | forecast/alarm | expand/archive |
| WAL rate | 待测 | capacity/repository | throttle/fix |
| replica lag | 待测 | endpoint freshness | remove target |
| maintenance debt | 待测 | vacuum window | remediate |
未知值要写 unknown,然后由第 25–28 章补证据;不要填一个未经测量的舒服
数字。
18.2.3 扩展能力不自动等于生产就绪
CREATE EXTENSION 实际做了什么
PostgreSQL 18 官方
CREATE EXTENSION
说明,命令根据 control 与 SQL 脚本创建函数、类型、操作符、索引支持方法等
对象,并在 catalog 中记录它们的归属。
它还明确提醒:
- 支持文件必须先安装在 server;
- 某些扩展需要 superuser;
- 安装脚本本身属于信任边界;
- 不安全
search_path/可写 schema 可能带来风险; IF NOT EXISTS不保证现有同名扩展就是期望对象。
因此:
扩展准入的九道门
| 门 | 需要回答 |
|---|---|
| 来源 | 包来自哪里,如何验证与更新? |
| 版本 | PostgreSQL major/minor、扩展版本是否钉住? |
| 节点一致 | primary、replica、恢复目标都有相同 binary? |
| 安装安全 | trusted/superuser、schema、owner、search_path? |
| 配置 | 是否需要 preload、GUC、worker、restart? |
| 数据行为 | 类型、索引、collation、序列化语义是否固定? |
| 运行成本 | CPU、内存、WAL、vacuum、存储与构建窗口? |
| 恢复升级 | dump/physical restore/replica/PITR/major upgrade? |
| 退出 | 如何导出、降级、替代、删除? |
任一关键门为 unknown,生命周期就不能写 accepted。
relocatable 只回答 schema 迁移的一小部分
本章 catalog 会看到:
extrelocatable=true 只表示扩展控制文件允许改变其对象所在 schema。它不说明:
- data portable;
- binary cross-version compatible;
- replica package 已存在;
- upgrade 可回滚;
- 对象 owner 安全;
- 业务语义不变。
不要从一个 catalog 布尔值推导整个生命周期。
preload 失败可能阻止实例启动
一些扩展需要 shared_preload_libraries。Pigsty 4.5 的
Extension Config
说明可用 pg_libs 和 pg_parameters 声明 preload 与参数,并提醒 preload
库缺失或加载失败可阻止 PostgreSQL 启动,修改 preload 还需要重启。
这把扩展从 database 范围提升到 instance 范围:
所以多租户平台不能允许任意 database owner 自助改变 preload。
本书当前扩展账本
只读实验在 PostgreSQL 18.6 上固定:
| 扩展 | 版本 | schema | owner | 生命周期 |
|---|---|---|---|---|
pg_trgm |
1.6 | shop_ch14 |
pg36_owner |
accepted |
vector |
0.8.4 | shop_ch14 |
postgres |
pilot |
btree_gist |
1.8 | shop_ch16_ext |
pg36_owner |
conditional |
postgis |
3.6.4 | shop_ch16_ext |
postgres |
conditional |
postgres_fdw |
1.2 | shop_ch17_ext |
postgres |
lab-only |
plpgsql |
1.0 | pg_catalog |
postgres |
core |
版本表是 fixture 事实,不是对所有 PostgreSQL 18 环境的要求。平台应按自己的 软件仓库与升级策略重新验收。
owner 差异是需要解释的证据
为什么部分扩展 owner 是 postgres,部分是 pg36_owner?
- trusted 与非 trusted 安装权限不同;
- 扩展脚本可能创建需要高级权限的对象;
- extension object owner 与内部对象 owner 可能不同;
- dump/restore 与后续 upgrade 会使用这些身份。
本章只记录现状。第 23、30 章要决定生产 owner 模型,并证明升级和恢复不依赖 一个无人管理的超级用户流程。
物理复制不等于安装包复制
physical replica 会复制 data files 和 catalog 状态,不会替你把共享库包安装
到新主机。若 primary catalog 依赖某 .so,目标节点缺包,相关查询会失败;
若该库需要 preload,实例启动也会失败;逻辑恢复和升主后的业务验收同样无法
通过。
因此节点准入应核对:
第 19 章负责基线,第 30 章负责升级顺序。
备份成功不是扩展恢复成功
要验收扩展恢复,至少在隔离空目标中:
- 安装目标 PostgreSQL 与确切扩展 package;
- 恢复 base backup / WAL 或 logical dump;
- 验证
pg_extension、types、operators、indexes 与 dependencies; - 运行该扩展的业务 golden;
- 验证 replica 与应用权限;
- 保存 manifest 和校验和。
只看 pgBackRest 命令 exit 0,不能证明 PostGIS geometry、vector index 或 自定义 opclass 可用。
用 bundle 控制组合,而不是逐扩展放任
本章服务目录定义:
offering 引用 bundle,bundle 有生命周期与 gate。这带来三个好处:
- 同一组相互依赖的 package/config 一起审阅;
- 服务等级明确允许什么;
- 升级与退出有完整影响面。
例如 pg-ha-standard 默认不允许 federation-lab,vector-pilot 只能走
exception;这比“机器上有包,所以谁都能 CREATE”更可治理。
反例:把实验 FDW 变成生产
第 17 章为了在本地 loopback 无密码访问,明确写了:
第 18 章的负例把 production_permitted 改成 true,validator 必须报:
这个反例传达一种重要写作纪律:实验里为了隔离机制而采用的简化,必须被机器 可见地阻止进入生产蓝图,而不是靠读者记得某段警告。
准入是可撤销决定
即使已经 accepted,也需要 review trigger:
生产就绪不是一次性勋章,而是一项持续有证据支持的状态。
上一节:从数据库产品到能力组合 · 返回本章目录 · 下一节:明确替代边界 · 查看全书目录 · 查看索引中心
18.3 明确替代边界
替代边界不是“PostgreSQL 对阵某产品”的功能打勾表。真正的决策单位是:
同一个系统可以对一类数据是权威,对另一类数据只是派生副本。
18.3.1 缓存、消息、对象存储与离线湖仓
缓存解决重复读取,不解决权威
缓存适合:
缓存不适合被默认为:
本章 product-cache-v1 的 authority:
这不是文字游戏。假如缓存中 product.version=8,PostgreSQL 已是
version=10,规则必须保证版本 8 永远不能覆盖版本 10。
cache-aside 的完整状态机
常见读路径:
写路径不能只写成“更新 DB 后删缓存”。必须考虑:
因此合同包含:
| 字段 | product-cache-v1 |
|---|---|
| ordering | source version 单调,旧版本不覆盖新版本 |
| idempotency | product_id + source version |
| rebuild | 丢弃 namespace,从 PostgreSQL 重建 |
| failure | miss/outage 回退到有界 PostgreSQL 读 |
| reconciliation | 抽样 version + payload checksum |
| deletion | tombstone 或 version bump |
| exit | 关闭 cache 路由并删除派生 namespace |
若无法丢弃 namespace,缓存已经悄悄变成了数据库。
消息系统拥有投递,不拥有原事务
事件总线擅长:
但“先更新数据库,再发送消息”有经典双写:
order-events-v1 选择 transactional outbox:
原子边界只到 outbox。外部投递是 at-least-once,消费者必须幂等。
“at-least-once”要写清谁至少一次
这几个语义不同:
稳定 event_id 是起点,不是终点。消费者需要把“已处理 identity”与业务
效果放在同一原子边界,或使用业务幂等键。
全局顺序通常也不应承诺。本章只承诺:
否则团队会为一个业务不需要的全序付出吞吐与可用性成本。
对象存储拥有大字节,数据库拥有元数据状态
把图片、视频和归档大对象直接塞入 PostgreSQL 并非绝对错误,但常见代价:
product-media-v1 采用逐域 authority:
可靠上传状态机可以是:
失败处理:
这里不存在跨 PostgreSQL 与对象存储的传统 ACID transaction,所以显式状态、 幂等与对账比“调用顺序看起来正确”重要。
湖仓拥有分析投影,不拥有在线业务解释
离线湖仓适合:
analytics-export-v1 的权威拆分:
每个数据集必须显示:
当 CDC 断裂时,正确行为不是悄悄继续展示旧数据,而是标记 last complete watermark,并根据合同停止或降级消费者。
snapshot + change stream 的一致性接缝
建立新投影通常需要:
如果 snapshot 与 stream 之间没有稳定接缝,会漏掉或重复一段变化。第 29 章 会把它写成迁移状态机并验收。
外置并不自动降低数据库压力
反例:
- cache miss storm 反而打爆 primary;
- CDC slot 积压使 WAL 无法回收;
- 搜索全量重建持续扫描并推高 I/O;
- 湖仓导出占据 replica 和网络;
- object reconciliation 每晚做无界全表 join;
- outbox relay 用无索引轮询。
每个外置系统都要预算它对 PostgreSQL 的读取、WAL、连接与保留压力。
18.3.2 超大规模检索、流处理与专用分析
“超大规模”必须换成阈值
以下句子都不能触发架构:
需要换成可测量的问题:
当前值可以是 unknown,但触发器不能是形容词。
外部检索的真正触发器
PostgreSQL 内搜索常在这些条件下很有优势:
- 数据与交易状态同库,提交即搜索;
- 过滤与关系 join 很重要;
- 规模在单 cluster 预算内;
- 搜索功能较集中;
- 团队不想承担额外索引同步系统;
- 正确性与删除要求高。
专用检索可能在以下证据出现后更合适:
切换前必须用同一 query set 与 relevance golden 比较,不能只比一条延迟。
搜索外置仍要保留 PostgreSQL fallback 分类
不是所有查询都能回退:
| 查询类 | 外部搜索故障时 |
|---|---|
| exact product ID | 直接 PostgreSQL |
| 简单关键词 | 可按容量回退 PostgreSQL |
| 复杂 facet | 返回有标签旧 generation 或失败 |
| semantic ANN | 可能没有等价 fallback |
| admin reconciliation | 延后,不影响交易 |
fallback 本身也需要容量演练。平时 1% 回源的 primary,未必承受 100% 搜索 流量。
流处理适合持续状态与事件时间计算
PostgreSQL + outbox + worker 可以完成大量异步任务。当需求升级为:
专用流平台可能更合适。
但流系统不会自动给出业务 exactly-once。即使框架内部有 exactly-once checkpoint,外部数据库、HTTP 副作用和人工操作仍需幂等与对账。应写:
而不是笼统宣布“端到端 exactly once”。
专用分析的触发器是工作负载边界
单节点或 offline replica 先做:
当代表性证据仍显示:
- scan/compute 越过单节点;
- retention/压缩成本不可接受;
- 并发与查询形状冲突;
- 维护窗口不可满足;
- OLTP 和分析无法充分隔离;
- 横向扩展收益足以覆盖分布代价;
才比较 Citus、兼容分布式 PostgreSQL、列式引擎或湖仓。
第 17 章已经展示分布式新增问题:
专用系统不是“更高级的 PostgreSQL 参数”,而是新的数据与故障模型。
查询兼容和业务兼容要分开
候选声称“PostgreSQL compatible”时,至少核对:
能连上 psql 只证明协议入口;能跑 SELECT 只证明一个查询子集。
用两道门防止过早与过晚拆分
进入外置评审:
继续留在 PostgreSQL:
门是双向的。不能因为过去留在 PostgreSQL,就永远拒绝新证据。
18.3.3 用数据所有权、时效和一致性决定分工
第一步:把名词换成数据域
不要问“谁是 source of truth”,问:
同一个订单事件流程可能有三个权威:
它们不冲突,因为 authority 的数据域不同。
第二步:为每个读者写时效
“最终一致”没有时钟。改写为:
还要说明超限:
第三步:把一致性写成可观察规则
示例:
这些规则可以生成 negative test;“保持一致”无法测试。
第四步:定义失败矩阵
对每个跨系统合同列:
| 故障 | 写入结果 | 读取结果 | 重试 | 告警/对账 |
|---|---|---|---|---|
| PostgreSQL down | 是否拒绝 | cache 是否可陈旧读 | 谁重试 | 哪个 owner |
| external sink down | source 是否可提交 | 是否 fallback | backlog 边界 | lag |
| network partition | 双方如何判定 | 是否可能分歧 | 幂等 identity | reconciliation |
| projection corrupt | source 不受影响 | 停止/旧 generation | rebuild | checksum |
| credential expired | source 不受影响 | 外置路径失败 | 更新 secret | auth alert |
如果团队只讨论 happy path,外置系统只是把事故推迟到上线后设计。
第五步:写重建,而不只写备份
派生系统最强的恢复策略通常是从权威重建。但“可重建”必须证明:
若不能满足,它就不是“随时可丢的副本”。
第六步:删除是一等数据流
创建与更新容易被关注,删除经常遗漏:
每个合同必须有 deletion 字段,并与法律/业务保留策略对齐。删除不能简单理解
为对每个系统发一条 DELETE;它需要 tombstone、generation、审计和最终
验证。
第七步:退出路线与进入路线同时审批
一个可退出设计至少回答:
本章蓝图包含五条退出原则。validator 的反例把 exit_paths 清空时必须返回:
一个通用决策表
| 问题 | 留在 PostgreSQL 倾向 | 外置倾向 |
|---|---|---|
| 是否需与业务写原子提交 | 强 | 需 outbox/saga |
| 是否频繁关系 join/filter | 强 | 需复制/反规范化 |
| 是否可容忍陈旧 | 弱 | 强 |
| 是否可从权威重建 | 非必要 | 必须 |
| 是否需独立扩缩/故障域 | 弱 | 强 |
| 单节点资源是否越界 | 可先优化/隔离 | 证据越界后强 |
| 专用语义是否关键 | 扩展可满足则强 | 缺口明确则强 |
| 团队是否能多系统值班 | 简化更强 | 能力充足才强 |
| 删除/对账是否成熟 | 简单 | 必须成熟 |
| 退出成本是否可接受 | 低 | 必须明示 |
表不会自动给出答案;它强迫评审把隐性成本说出来。
用合同 ID 连接能力
本章九项 capability 中,外置或可能外置的能力引用:
引用让我们可以回答:
- 某合同被哪些能力使用;
- 某合同删除后哪些蓝图断裂;
- 是否有无人引用的外置系统;
- 是否所有外置能力都有 owner;
- 外置触发器是否存在。
validate.py
会拒绝悬空引用,但业务评审仍要判断合同内容是否真实可行。
不选择具体产品也是有效决定
本章故意不为 cache、event bus、object storage、search 或 lakehouse 指定 品牌。原因不是逃避,而是先稳定更长寿的合同:
候选产品要在这些合同下比较。若先选产品,团队很容易把产品默认行为冒充业务 需求。
最终判断句式
一项成熟决定应能写成:
对数据域 X,由系统 A 保持权威;系统 B 以语义 Y 持有派生投影,目标新鲜度 为 Z,失败时按 F 退化,以 identity I 幂等并按 R 对账,可通过步骤 E 重建 和退出。只有触发器 T 出现且 gate G 通过后,B 才进入生产。
能写清这句话,才算划定边界。
上一节:PostgreSQL 的强项与代价 · 返回本章目录 · 下一节:平台服务目录与多租户 · 查看全书目录 · 查看索引中心
18.4 平台服务目录与多租户
如果消费者只能申请“一台 PostgreSQL”,平台交付的是资源,不是服务。
一个可用目录应该让消费者在申请前就知道:
18.4.1 服务等级、规格、版本与扩展套餐
offering 是完整合同,不是机器型号
本章的
service-catalog.json
为每个 offering 保存:
机器 CPU/内存/磁盘规格当然重要,但它只是实现 offering 的一种资源配置。
同一 pg-ha-standard 可以在不同硬件代际上实现;只要服务合同和经过验证的
容量仍然成立,消费者不应绑定某台主机。
pg-dev
定位:
它允许较广的试验 extension bundle,包括 federation-lab。这不意味着开发
环境可以无治理:
- 必须有 consumer owner;
- 连接、存储、temp、statement time 要声明;
- 必须有到期日;
- 敏感生产数据不能因为“只是 dev”就复制进去;
- 实验凭据不能进入 Git;
- 删除仍需精确目标和恢复需求确认。
开发 offering 的目标是加快安全实验,不是变成永不下线的影子生产。
pg-ha-standard
这是 pg36_shop 的默认生产候选:
目录中:
这些是要进入第 20、21、24 章验证的目标。它们不能由“三节点”直接推出。
允许的 bundle:
vector-pilot 只能走 exception;federation-lab 不允许。
pg-ha-critical
critical 不是把 standard 的数字改得更漂亮。它意味着:
目录提出:
“RPO 0”必须带范围。同步副本若与 primary 共享电源/机房,不能自动覆盖整个 区域故障;若为可用性临时降级同步策略,也可能改变承诺。
pg-analytics-offline
它不是第二个 source of truth:
Pigsty 官方
Cluster / Instance
把 pg_role: offline 定位为慢查询、ETL、OLAP 与交互分析专用 read-only
replica。隔离的目的不是“让慢查询没人管”,而是不让它默认争用在线 replica。
规格要由容量曲线产生
不要先定义:
再假设业务会自然匹配。更可靠的顺序:
- 定义 workload class;
- 固定数据量、并发和增长;
- 测量容量曲线和饱和点;
- 选定安全 headroom;
- 映射到可采购/可部署资源;
- 定义升降配条件。
硬件 SKU 可以变化,基准 workload 与目标不应随意变化。
版本是一组矩阵
平台版本不只一个 postgresql=18:
兼容矩阵至少回答:
- 哪组是当前支持;
- 哪组是下一升级候选;
- 哪组已弃用;
- package repository 能否重建;
- backup/restore/replica/upgrade 是否覆盖;
- 安全补丁时限;
- 谁批准例外。
extension bundle 是服务的一部分
bundle 不只是安装清单:
offering 只引用已审阅 bundle,避免每个用户自行拼装无法升级的组合。
服务目标要分平台与消费者责任
平台可负责:
消费者仍负责:
责任边界要写 RACI,不能在事故时才争论“数据库没问题还是应用没问题”。
目录本身也需要版本与状态
本章目录:
当下卷 evidence 完成后,应产生新 release,而不是原地把历史 proposal 改成 “一直就是通过”。目录是决策记录的一部分。
18.4.2 数据库、模式、实例和集群隔离
四层隔离解决不同问题
| 层级 | 隔离了什么 | 没隔离什么 |
|---|---|---|
| schema | 名称、对象组织、部分权限 | database GUC/连接、WAL、资源、故障 |
| database | catalog、连接入口、部分设置 | instance CPU/I/O/WAL、superuser、升级 |
| instance | postmaster、内存、端口、WAL、配置 | host/kernel/storage/network |
| cluster/service | HA/恢复/生命周期边界 | 若共主机/控制平面仍可能相关 |
“tenant 一个 schema”与“tenant 一个 cluster”不只是成本不同,安全与爆炸半径 完全不同。
schema 隔离
适合:
风险:
安全做法:
- 不给不可信运行时角色在公共解析路径中的
CREATE; - 对安全定义函数固定安全
search_path; - 使用显式 schema qualification;
- 分离 NOLOGIN owner 与 LOGIN runtime;
- 定期审计 ACL 与 dependencies。
database 隔离
适合:
优点是 namespace 和连接边界更清楚;代价是:
- PostgreSQL 原生跨 database 查询不透明;
- connection pool/监控/迁移对象增多;
- extension 每 database 管理;
- 仍共享 WAL、CPU、I/O、backup 与 major upgrade;
- instance superuser 仍可越界。
database 不是强资源隔离。
instance 隔离
适合:
同一 host 上多个 instance 仍共享:
必须配 OS/cgroup/volume/port/backup 与监控隔离,否则只是多个 postmaster 抢 同一资源。
cluster 隔离
适合:
代价是节点、备份、监控、升级、值班和容量碎片化。不是所有小数据库都值得 一个三节点 cluster。
选择顺序从信任与故障开始
推荐问:
- tenant 是否互信?
- 数据分类是否允许共实例/共主机?
- 是否允许同一个 superuser/平台团队访问?
- workload 能否互相制造事故?
- 是否需要不同 PostgreSQL/extension/preload?
- 是否需要独立 failover/restore/upgrade?
- 跨 tenant 查询/事务是否必要?
- 成本和运营能力是否承受更高隔离?
安全硬边界优先于便利与成本。
多租户至少有三种模型
也可以混合:
但迁移路径要提前设计:如何从 shared 提升到 dedicated,identity、sequence、 外键、事件与投影如何移动。
RLS 是纵深防御,不是唯一边界
Row-Level Security 可以根据角色/会话上下文限制行,但需要审计:
第 23 章会用正负 tenant 测试验证。不能因为 CREATE POLICY 成功,就宣布
隔离完成。
隔离与可观测性必须同粒度
若配额按 tenant,指标却只能看整个 cluster,就无法:
- 识别 noisy neighbor;
- 归属成本;
- 证明公平性;
- 按 tenant 降级;
- 预测迁移。
需要在不暴露敏感 SQL/数据的前提下,保留 service/database/role/workload class/tenant 等必要维度,并控制 cardinality。
18.4.3 成本归属、配额和生命周期
没有成本归属,平台会奖励浪费
共享数据库中,使用者容易只看到:
平台承担的真实成本:
成本模型不一定要精确计费,但必须让 owner 看见边际影响。
用服务单位表达成本
可以按组织成熟度从简单到复杂:
避免只按主表大小计费:索引、TOAST、replica、backup 与 WAL 可能远大于主表。
配额是正确性保护,不只是节省
重要配额:
| 配额 | 保护 |
|---|---|
| connection | 防止 backend/内存/调度耗尽 |
| active query | 防止过度并发 |
| statement timeout | 限制无界执行 |
| lock timeout | 限制排队与级联 |
| idle transaction | 防止长快照/持锁 |
work_mem class |
限制乘法内存 |
temp_file_limit |
防止 spill 填盘 |
| storage/WAL growth | 保护恢复链与容量 |
| logical slot retention | 防止 WAL 无限保留 |
| maintenance window | 保证 vacuum/index/backup |
配额超限行为要确定:
静默超卖不是服务。
连接预算从端到端计算
不要让每个应用实例都配置:
预算应满足:
还要考虑:
- 每个应用副本数;
- failover 后流量汇聚;
- pool retry storm;
- transaction vs session pooling;
- offline/replica 独立预算;
- emergency admin 保留。
第 22、34 章会做饱和与过载演练。
生命周期从申请前开始
完整状态机:
若没有 expiry/decommission,临时数据库会永久占用备份、监控和升级路径。
变更要分普通与例外
普通变更:
例外:
例外必须有:
“临时允许”但没有到期日,通常等于永久。
删除与退役是高风险动作
本书实验里的 reset 有精确 token、target、marker 和 active-session guard; 生产退役还要更多:
- 确认 owner 与法律/业务 retention;
- 停止新连接和写入;
- 记录最后 backup/manifest;
- 导出需要保留的数据;
- drain 外部消费者和投影;
- 验证 DNS/service/secret/monitoring 依赖;
- 双人审批 destructive target;
- 擦除或进入保留;
- 保存完成证据。
“在 inventory 中删掉一段 YAML”不等于数据已经安全退役。
复审由事件与周期共同触发
周期复审:
事件复审:
平台目录最终要可验证
本章 validator 会检查:
- offering ID 唯一;
- production offering 有非空
service_objectives; - allowed bundle 全部存在;
- blueprint 引用的 offering 全部存在;
- bundle 与 lower-volume gate 引用闭合;
- lab-only FDW 不得生产准入。
反例把 pg-ha-standard.service_objectives 置空时,必须得到:
机器验证不能判断 99.9% 是否合理,但能阻止“生产服务连目标字段都没有”的 结构退化。
上一节:明确替代边界 · 返回本章目录 · 下一节:Pigsty 作为参考实现 · 查看全书目录 · 查看索引中心
18.5 Pigsty 作为参考实现
本书选择 Pigsty,不是为了把 PostgreSQL 原理隐藏在自动化后面,而是为了让 读者看到一套完整参考实现如何把原理变成可交付环境。
阅读方法始终是双向的:
18.5.1 把 PostgreSQL、HA、备份、接入和观察组合起来
声明期望状态
Pigsty 4.5 的 Architecture 说明,它用 config inventory 与参数描述部署环境,再由 Ansible playbook 实现。
最小 cluster 声明大致包含:
声明有两个重要作用:
- 让拓扑、身份、版本与参数进入版本化评审;
- 让重复执行与漂移修复有一个共同期望。
它没有证明:
- 四个地址真的跨故障域;
- 存储与网络满足容量;
- package repository 完整;
- secret 已正确交付;
- RPO/RTO 已演练;
- 业务应用兼容。
inventory 是控制平面输入,不是验收报告。
模块与职责映射
Pigsty 官方架构列出多个模块。本书关注:
| 模块/组件 | 本书中的职责 |
|---|---|
| NODE | 主机基线、监控、日志、HAProxy 等节点能力 |
| ETCD | HA 的分布式配置与 leader 协调 |
| PGSQL | PostgreSQL、Patroni、PgBouncer、pgBackRest、exporter |
| INFRA | 软件仓库、DNS/NTP、指标/日志/告警/可视化 |
| MINIO(可选) | S3-compatible 对象/备份仓库候选 |
| REDIS(可选) | 缓存候选,但 authority 仍由合同决定 |
模块安装并不改变业务边界。例如部署 REDIS 不会自动让它成为商品权威;部署 MINIO 也不会自动完成 media 两阶段状态机。
PostgreSQL 仍是数据面核心
自动化完成后,仍要从 PostgreSQL 验证:
以及:
Pigsty 提供实现路径,不改变这些原生事实的语义。
Patroni 与 etcd 形成 HA 控制
Pigsty 的 High Availability 描述了参考链:
理解边界:
- PostgreSQL replication 决定数据复制位置;
- Patroni 决定/执行 promotion 与成员管理;
- etcd 参与 leader 共识,不存业务表;
- HAProxy 决定新连接去哪,不复制数据;
- client 必须处理切换期间连接/事务失败。
“自动切换”不是“请求无感成功”。切换中的连接会中断,未决事务需要应用依据 幂等身份判断和重试。
复制不是备份
HA 复制会忠实传播:
Pigsty 官方 HA 文档也明确区分流复制故障覆盖与人为/软件错误恢复,后者需要 延迟副本或 PITR。
因此平台组合同时需要:
第 20、21、32、35 章分别承担这些证据。
pgBackRest 形成恢复链,但恢复仍需演练
参考实现可生成 pgBackRest 配置、执行 base/differential/incremental backup、 归档 WAL 并管理 repository。
验收不能止于:
还要证明:
第 21 章会在隔离目标恢复;第 32 章选择随机 recovery target。
HAProxy 把拓扑封装为服务
Pigsty 官方
Service/Access
说明 service 由访问端点与 selector 组成,并提供默认 primary、replica、
default、offline 等服务。
概念映射:
要进一步验收:
- health check 与实际 role 是否一致;
- failover 后多久摘除旧 primary;
- fallback 是否会把只读流量压回 primary;
- client DNS/VIP/port 怎样接入;
- TLS 在哪终止;
- health endpoint 是否越权;
- 连接失败与重试风暴怎样受控。
PgBouncer 把连接变成有限资源池
池化可减少 backend 数、平滑短连接,但会引入语义:
应用是否兼容,取决于 pool mode 与使用的 session feature。第 22 章会用实际 请求验证,不能只看 PgBouncer 端口可连。
offline replica 实现分析隔离候选
Pigsty 的
Offline Instance
把 pg_role: offline 用于慢查询、ETL、OLAP 与交互查询。
蓝图提出:
offline 服务合同:
- read-only;
- 不保证 read-your-writes;
- 显示 replay lag;
- 分析连接池独立;
- 查询 timeout/temp 配额独立;
- 不默认承接 online replica 流量;
- 长查询与 WAL replay 冲突有明确处理。
节点存在不证明这些条件,仍需第 22、26、27 章。
观察栈连接组件信号
Pigsty 4.5 Monitoring 描述了 Grafana、VictoriaMetrics、VictoriaLogs 与 PostgreSQL/PgBouncer/ Patroni/HAProxy/Node 等 exporter/日志源。
平台至少需要关联:
仪表盘只是表现层。alert owner、阈值依据、抑制、升级与 runbook 仍由团队 定义。
18.5.2 哪些能力开箱可用,哪些仍需组织流程
三层“可用”
讨论开箱能力时应分:
本章:
不要把不同对象的 L1 混在一起:本地 PostgreSQL 18.6 实验通过,不等于目标 Linux/Pigsty cluster 通过。
参考实现可直接提供的机制
按官方能力,Pigsty 可以自动化:
“提供机制”的准确含义:
- 有对应 module/parameter/playbook;
- 可以在支持环境中声明和部署;
- 有默认配置和可观察入口。
它不是针对 pg36_shop 的完成证明。
组织必须补齐的工作
| 工作 | 为什么不能由工具自动决定 |
|---|---|
| 数据 authority | 业务语义与冲突裁决 |
| SLO/error budget | 业务损失、成本与风险取舍 |
| RPO/RTO scope | 故障模型与恢复价值 |
| tenant trust | 法规、组织与威胁模型 |
| extension admission | 功能收益、生命周期、支持 |
| capacity headroom | 真实 workload 与增长 |
| on-call/RACI | 人与组织责任 |
| change approval | 风险、窗口与可逆性 |
| incident judgment | 不完整证据下的取舍 |
| postmortem actions | 系统性改进优先级 |
自动化可以检查字段非空,不能替业务 owner 承诺。
默认值是起点,不是证据
生产环境常见错误:
正确流程:
secret 永远不属于示例 inventory
本章
pigsty-declaration.example.yml
只放不可用 sentinel:
正式环境要决定:
示例中的明文不是“方便”,而是泄露路径。
配置成功后还要独立验收
部署命令成功后,验收从外到内:
每项证据要保存 target、时间、版本和执行者。截图可以辅助,不应是唯一机器证据。
环境与组织漂移
技术漂移:
组织漂移:
平台治理必须同时检测两类。第二类不会出现在 pg_settings。
何时可以说“生产就绪”
至少满足:
因此本章不会使用“部署完成,所以生产就绪”的句式。
18.5.3 不把参考实现冒充唯一架构
稳定的是合同,变化的是实现
应尽量稳定:
可以替换:
替换实现时,合同成为验收基线。
不要绕过组件理解
Pigsty 把复杂组件组合起来,但值班者仍需知道故障落在哪一层:
| 症状 | 可能层 |
|---|---|
| endpoint 不通 | DNS/VIP/HAProxy/network |
| pool queue 高 | PgBouncer/connection budget |
| primary 不明确 | Patroni/etcd/network partition |
| replica lag | PostgreSQL/WAL/I/O/long query |
| backup missing | archive/pgBackRest/repository/secret |
| dashboard blank | exporter/collection/storage/query |
| SQL wrong result | schema/data/extension/business semantics |
“重跑 playbook”不是通用诊断,更可能覆盖证据或扩大变更。
不要把云托管与自托管简化成好坏
托管服务可能减少:
但团队仍负责:
自托管给予更多控制和透明度,也带来更多直接责任。选择应基于组织能力、法规、 故障模型、成本与退出,而非身份认同。
保持原生证据层
无论实现是什么,都尽量保留:
这些证据比某个 UI 路径更可迁移。
用接口封装平台差异
消费者看到:
不应依赖:
这样平台升级或替换时,业务应用变更最小。
参考实现也必须有退出路线
从 Pigsty 迁出并不是“一条 pg_dump”:
- 冻结 PostgreSQL/extension/locale/role 依赖;
- 选择 physical 或 logical 路径;
- 重建 service endpoint 与 pooling 语义;
- 重建 backup/PITR;
- 重建 monitoring/alerts;
- 验证 HA 与 failover;
- 验证业务 golden;
- 切换并保留回退;
- 退役旧控制面与 secret。
反向迁入同样需要这些合同。
一个合格的参考映射
本章示例 YAML 明确注释:
有意保留 unknown,比填入未经证实的“最佳实践”更专业。
何时偏离 Pigsty 参考
可以偏离,只要有证据:
偏离应写 ADR,包含等价职责、差异、风险与退出,而不是静默拼装。
本书为什么仍然以 Pigsty 实战
因为读者要从 SQL 走到生产,必须面对:
Pigsty 给出一个可以落地、查看、运行与破坏性演练的完整对象;PostgreSQL 原生 证据则防止读者只会操作一个封装。两条线并行,才能真正迁移知识。
这一章的最终边界
我们接受:
Pigsty 4.5 是
pg36_shop下卷的参考实现。
我们尚未接受:
示例 inventory 已经可以部署生产,或提案中的 SLO 已经实现。
后一项只有在下卷 evidence 完成后才可能成立。
上一节:平台服务目录与多租户 · 返回本章目录 · 下一节:实战:设计 pg36_shop 生产蓝图 ·
查看全书目录 · 查看索引中心
18.6 实战:设计 `pg36_shop` 生产蓝图
第 18 章实战与前几章不同:
它把现有证据读出来,检查蓝图是否有资格进入下卷。风险等级是 L0。
18.6.1 选择保留在 PostgreSQL 内的能力
前置状态
实验要求第 4、13–17 章的最终 fixture 保留在同一本地开发实例:
缺失时本章直接失败,返回前章重建;它不会悄悄修复。
私有连接
沿用:
密码或其他 secret 放在批准的连接机制中,不放命令行、脚本、evidence 或 Git。
先读实验合同
lab-contract.md
规定:
本章脚本没有 setup/reset action。这不是遗漏,而是用接口形状表达安全
边界。
上卷前置复核
task.sh 依次调用:
它们只验证 retained fixture。第 17 章还分别连接 pg36_shard_a 和
pg36_shard_b,防止协调端看似正常而远端状态已经漂移。
只读事务
每个 catalog capture 都显式开始:
再 include
context.sql。
context 验证:
脚本结束 COMMIT,但 read-only 事务没有业务变更。
平台状态
platform-state.sql
输出稳定 key/value:
mutation=none 不是只靠自报:all 在前置复核后抓两轮,并对状态与 catalog
逐字节 cmp。
能力快照
capability-snapshot.sql
把数据库事实压成九行:
| capability | lifecycle | evidence |
|---|---|---|
| relational core | accepted | ch04-v1 |
| atomic database logic | accepted with scope | ch13-routine-guard-v1 |
| lexical/fuzzy search | accepted | pg_trgm:1.6 |
| semantic search | pilot | vector:0.8.4 |
| spatiotemporal | conditional | btree_gist:1.8,postgis:3.6.4 |
| analytical federation | lab-only | postgres_fdw:1.2 |
| search quality fixture | accepted | ch15-search-v1 |
| spatiotemporal fixture | accepted | ch16-spatiotemporal-v1 |
| analytics fixture | accepted | ch17-analytics-v1 |
这里有意把“扩展安装事实”和“生命周期判断”并列。SQL 能证明版本存在, 生命周期还来自前章的质量、安全与运维边界。
extension catalog
正式 fixture 精确包含六项:
教学扩展必须保留 pg36 chXX ... safe to rebuild marker。marker 只用于本书
精确识别,不应照搬成生产对象治理方案。
schema 与 role catalog
每项必须由 pg36_owner 拥有并保留精确 comment。
role-catalog.sql
只导出三种相关身份,避免把环境中其他角色误收入出版 fixture。审查器验证:
为什么不探测 Pigsty
当前实例不是本章声明的目标 Pigsty cluster。若脚本从本机进程名或目录猜测 Pigsty 状态,会产生伪证据。
所以蓝图准确写:
第 19 章在明确 target/inventory 后才执行环境验收。
18.6.2 选择外置组件及其数据契约
五份合同先于产品选型
external-data-contracts.json
包含:
每份必须有 17 个核心字段,包括:
这比简单画一条箭头严格得多。
cache 合同
关键规则:
validator 专门扫描 cache authority。若把:
则报:
order event 合同
publish lag 仍是 chapter 24 pending。写出 pending 比伪造一个 P99 更准确。
media 合同
它是 accepted-boundary,表示“字节外置”这一边界已选择,不表示具体 object
provider 已选择或 L1 已通过。
analytics 合同
这份合同会在第 29 章的数据迁移与 CDC 状态机中具体化。
external search 合同
状态:
进入条件不是“想用”,而是 PostgreSQL 搜索基线在质量、规模、语言或独立 SLO 上失败。
启用前必须证明:
正向文档关系
baseline-v1.6-proposal.json
引用所有合同。每项 capability 又引用它需要的合同。
validator 检查:
这能发现拼写、遗漏和结构漂移。
七个对抗性反例
negative-cases.json
不是伪造七份静态错误文件,而是对正确文档做 JSON path mutation:
| 反例 | 期望错误 |
|---|---|
| 无 evidence 宣称 Pigsty L1 passed | E_L1_EVIDENCE |
| 清空全部 exit path | E_EXIT_PATH |
| cache 成为商品业务权威 | E_CACHE_AUTHORITY |
| 删除消息合同 rebuild | E_CONTRACT_FIELD |
| loopback FDW 允许生产 | E_FDW_LAB_ONLY |
| 删除 production offering objective | E_SERVICE_OBJECTIVE |
| 删除 vector pilot gate | E_EXTENSION_GATE |
测试要求实际错误码与期望码精确相同。若错误文档意外通过,或被另一个更早的 无关规则拦截,negative suite 都失败。
为什么 validator 只用 Python 标准库
validate.py
只依赖:
目的不是排斥 schema 工具,而是让读者在最小环境中运行并看到业务策略代码。 生产平台可以再加 JSON Schema、OPA、CI policy 或签名。
canonical hash
报告为四份核心文档计算 canonical JSON SHA-256:
这避免 indentation/key order 影响内容身份,同时让 gate/capability 顺序仍然 有意义。
注意:canonical hash 证明文档未变,不证明内容正确;内容正确还靠人工决策、 数据库证据和负例。
18.6.3 输出服务目录草案、架构 ADR 与下卷验收问题
资产目录
没有生成的 evidence 被提交到源码目录。
单独验证文档
预期:
加负例:
预期 case_count=7 且全部 actual/expected code 相等。
单轮 capture
输出:
每个 cycle 的
review.py
验证:
- manifest target/version/hash;
- relation checksum;
- extension/schema/role exact identity;
- capability lifecycle;
- normal/negative policy report;
- stderr 为空。
两轮正式运行
正式 PostgreSQL 18.6 开发 fixture 的结果:
源码后续改变时 checksum 会改变;应以当次 manifest 与 validator report 为准。
两轮比较什么
manifest.txt 含 capture 时间,故不做 byte compare;其余确定性证据必须一致。
运行后状态不需要复位
本章没有数据库写操作。若运行前后出现状态变化,应当视为:
- 外部并发变更;
- 某个前置 verify 实现违反只读预期;
- capture SQL/任务脚本缺陷;
- 环境不再适合作为冻结 fixture。
不要用 reset 掩盖,应先保留 evidence 并诊断。
架构 ADR
明确拒绝:
ADR 的 status 是:
18 个下卷 gate
lower-volume-gates.json
严格映射第 19–36 章:
| 章 | gate |
|---|---|
| 19 | deployment baseline |
| 20 | HA |
| 21 | backup/restore |
| 22 | access/routing |
| 23 | security |
| 24 | governance |
| 25 | observability |
| 26 | capacity |
| 27 | tuning |
| 28 | vacuum/maintenance |
| 29 | migration |
| 30 | upgrade |
| 31 | incident framework |
| 32 | PITR |
| 33 | failover/rebuild |
| 34 | overload |
| 35 | forensics |
| 36 | postmortem/platform improvement |
每个 gate 有 owner、两个核心问题、required evidence 与 pending 状态。
gate 不是章节阅读打卡
chapter completed 不等于 gate passed。例如读完第 21 章但没有在目标环境
恢复,ch21-backup-restore 仍然 pending。
通过 gate 应产生:
服务目录如何升级
下卷完成后,不直接覆盖 1.6-proposal。应:
- 收集每个 gate evidence;
- 修改不成立的 topology/objective/bundle;
- 记录 ADR revision;
- 生成新的 catalog/blueprint release;
- 重新跑正负 policy;
- 由 owner 批准;
- 保留 proposal 历史。
本章通过后能说什么
可以说:
在 PostgreSQL 18.6 的受控本地 fixture 上,第 4、13–17 章证据仍然成立;
pg36_shop的服务目录、能力决策、五份外置合同和 18 个下卷 gate 引用闭合, 七个危险反例被拒绝,两次只读快照一致。
不能说:
Pigsty 生产 cluster 已部署、SLO 已实现、备份可恢复、HA 可达目标、安全已 通过、容量足够。
这条语言边界,也是本章最后一项验收。
进入下卷
上卷回答:
下卷开始回答:
下一步是 第 19 章:环境规划与部署基线: 把 proposal 中的主机、软件、网络、存储、故障域和 Pigsty inventory 变成第一 份目标环境证据。
上一节:Pigsty 作为参考实现 · 返回本章目录 · 下一章:开天辟地:环境规划与部署基线 · 查看全书目录 · 查看索引中心