# ADR-017：先证明单机边界，再决定是否分布式

- 状态：proposed
- 决策版本：1.5-proposal
- 冻结 fixture：ch17-analytics-v1
- 验证环境：PostgreSQL 18.4 / `postgres_fdw` 1.2
- Pigsty 参考版本：4.4；L1 尚未执行

## 背景

团队的月报、租户明细和交互查询开始争夺同一套 PostgreSQL 资源。仅凭
“数据在增长”就引入分布式，会同时增加路由、跨节点事务、再平衡、备份、
恢复、升级、观测和故障处理成本；仅凭“单机还能跑”而拒绝分布式，也可能
把明确的容量与故障域问题推迟到事故发生。

本 ADR 把选择改写为一组可证伪的问题：瓶颈是否来自坏 SQL、错误索引、
内存 spill、重复扫描或缺少预聚合？读压力能否隔离？必须跨节点的资源是哪
一个？分布键能否覆盖主要访问路径？新增系统的恢复与值班能力是否到位？

## 决策

默认采用逐级升级的路径，并且每一级都要有发布前后的同口径证据：

1. 修正数据口径、SQL、统计信息和访问路径。
2. 在单机上验证并行查询、覆盖索引、BRIN 候选、内存 spill 与物化汇总。
3. 对可容忍延迟的分析读，评估 Pigsty 中的离线副本或专用分析实例。
4. 只有当单节点 CPU、内存、存储、I/O、维护窗口或故障域在代表性负载下
   已越过目标，才进入分布式设计。
5. 需要 PostgreSQL 语义下的透明分片时，评估 Citus，并验证分布键、
   colocated join、reference table、重平衡、备份恢复、升级和 HA。
6. 当列式扫描、极高压缩、海量聚合或分析并发是主需求，且 PostgreSQL 路径
   无法满足 SLO 时，才比较专用 OLAP 系统；同时预算 CDC、双写一致性、
   数据新鲜度和第二套值班体系。

`postgres_fdw` 在本章只承担教学 PoC：它揭示远端过滤、远端聚合、传输行数、
身份映射和部分失败的机制，也能产生“同分片 JOIN 仍未下推”的反例。这个
loopback PoC 不作为 Citus 或任何生产分布式方案的性能替身。

## 证据摘要

| 问题 | 冻结证据 | 含义 |
|---|---|---|
| 单机能否并行 | 2 workers，24 万事实聚合为 32 行 | 可用，但不能据此推导容量 |
| 选择性查询 | 覆盖索引，7,500 行，heap fetch 0 | 先修访问路径 |
| 内存压力 | 64kB 外排，32MB 内排 | 调参必须按并发预算 |
| 重复聚合 | 原表 240,000 行，日汇总 2,880 行 | 用计算换刷新与治理成本 |
| 分片裁剪 | tenant 3 只访问 shard B | 分布键与谓词可对齐 |
| 朴素 FDW | 协调端接收 240,000 行 | 网络与协调端成为新边界 |
| 两阶段聚合 | 远端合计返回 960 行 | 运算应尽量靠近数据 |
| colocated join | 协调端仍执行 Hash Join | 必须验证计划，不能凭拓扑推断 |
| 单分片失败 | 健康租户可读，全局查询 `08001` | 需要定义部分可用语义 |

四种月报路径必须逐字节等于同一份冻结 CSV；否则性能或拓扑讨论没有意义。

## 候选路径

| 路径 | 适合解决 | 新增义务 | 本 ADR 结论 |
|---|---|---|---|
| 单机优化 | 坏计划、重复扫描、spill、低选择性 | 基线、容量与回归管理 | 默认第一步 |
| 物化汇总 | 可容忍新鲜度的重复聚合 | 刷新、锁、失败恢复、口径版本 | 本样本有效 |
| Pigsty 离线副本 | 可接受复制延迟的只读分析 | 延迟、冲突、路由、只读语义 | 下一层候选 |
| 原生分区 | 生命周期、裁剪、维护单元 | 分区键、边界、索引和 vacuum | 不是横向扩展本身 |
| `postgres_fdw` | 联邦访问、迁移、机制 PoC | 凭据、远端事务、下推、故障 | 仅作本章实验 |
| Citus | PostgreSQL 兼容分片与 colocated 查询 | 分布键、协调端、再平衡、HA/DR | 达到门槛后评估 |
| 专用 OLAP | 列式海量扫描与分析并发 | CDC、双系统一致性和运维 | 最后比较 |

## 进入分布式评审的硬门槛

以下材料缺一不可：

- 代表性数据量、数据倾斜、冷热分布和并发回放；
- 单机调优后的 P50/P95/P99、吞吐、CPU、I/O、缓存、temp、WAL、vacuum、
  checkpoint 与副本延迟；
- 三到十二个月容量预测与单节点上限的误差区间；
- 主要查询按分布键分类，以及不可避免的跨分片 JOIN/聚合/事务比例；
- 节点丢失、网络分区、协调端故障、再平衡、备份恢复和版本升级演练；
- RPO/RTO、数据新鲜度、部分可用、降级和人工处置规则；
- Pigsty L1 环境中的包版本、拓扑、监控、告警、备份与恢复证据；
- 一份明确的回退方案及其最后可回退点。

## 被否决的捷径

### 把 HASH 分区 remainder 当整数取模

早期原型把远端数据按 `tenant_id % 2` 生成，却用 PostgreSQL HASH 分区接管
协调端路由。PostgreSQL 对分区键计算哈希值后再选 remainder，所以租户与
物理分片并不一致；裁剪后会静默漏数。冻结实现改用显式 LIST 分区，并把
路由算法一致性列为发布合同。

### 看到同分片就宣称 JOIN 下推

租户 3 的账户与销售都在 shard B，但通过两个分区外表父表连接时，实际计划
仍把 7,500 条事实和 50 条账户传回协调端做 Hash Join。只有目标产品、目标
版本、目标 SQL 的 `EXPLAIN` 与回归测试才能证明下推。

### 把 loopback 结果当横向扩展基准

三个数据库共享一个进程集、存储和故障域；不存在真实网络、节点竞争、
复制、重平衡或节点 HA。这里的行数只描述数据流形状，不描述性能。

## 后果

好处是架构升级有明确门槛，正确性先于速度，并能避免过早承担分布式复杂度。
代价是团队必须维护基线、冻结输入、容量模型与演练证据，物化汇总还引入
新鲜度和刷新治理。若最终采用分布式，还要把分片键和部分失败语义提升为
业务合同，而不只是数据库配置。

## 复审触发器

出现以下任一情况时重开 ADR：容量预测越过单节点安全余量；读副本仍无法
隔离分析负载；跨分片访问比例变化；租户分布显著倾斜；RPO/RTO 变化；
Pigsty、PostgreSQL、Citus 或候选 OLAP 主版本变化；真实 L1 演练与本提案
假设不符。
