17.6 实战:从单机证据到选型 ADR
本节把前五节压成一个可重复的 1.5-proposal:
freeze generator and monthly golden
-> bootstrap two retained shard database shells
-> build two exact remote schemas
-> build a local single-node baseline
-> build LIST-partitioned foreign parents
-> compare four byte-identical result paths
-> collect parallel/index/spill/summary plans
-> collect pruning/pushdown/transfer plans
-> preserve a non-pushed JOIN counterexample
-> prove application privilege denial
-> prove partial shard failure semantics
-> checksum catalogs and business state
-> reject unsafe reset attempts
-> pre-verify three databases
-> exact per-database reset
-> rebuild and repeat the entire review正式证据来自 PostgreSQL 18.4 / postgres_fdw 1.2 的受控本地开发实例。
Pigsty 4.4 的 topology 和职责已经映射,但没有执行 L1:
pigsty_l1=not-run破坏边界
task.sh all会删除并重建精确标记的shop_ch17、shop_ch17_ext、 两个 foreign server、六个 user mapping,以及pg36_shard_a/pg36_shard_b中的shop_ch17_shard。两个数据库壳会 保留。只可在本书受控开发 fixture 中执行,禁止在生产运行。
17.6.1 证明一个边界,拒绝一个伪瓶颈
前置连接
沿用第 4 章管理员 service:
[pg36-admin]
host=/path/to/socket-or-host
port=5432
dbname=pg36_shop
user=postgreschmod 600 /path/to/pg_service.conf
export PGSERVICEFILE=/path/to/pg_service.conf
export PGSERVICE=pg36-admin密码应放在受控 secret/service 机制中,不出现在命令行、脚本、evidence 或 Git。
环境保护
协调端
context.sql
要求:
database = pg36_shop
writable primary
PostgreSQL major = 18
session_user = postgres superuser
can SET ROLE pg36_owner
pg36_app = constrained LOGIN non-superuser
ch04-v1 physical model exists
postgres_fdw 1.2 available or exact managed state
pg36_shard_a/b database shell identity exact
fdw host/port exactly equal current instance远端
remote-context.sql
还核对:
expected database name
database owner/comment
shard remainder
shard marker
UTC/ISO session
timeouts任何已有同名 schema、server、mapping、extension 或 database 身份不符都停止, 不会因名字相同就接管。
资产清单
static/labs/ch17/
├── fixture-manifest.json
├── frozen-monthly.csv
├── fixture.sql
├── fixture-remote.sql
├── bootstrap.sql
├── remote-context.sql
├── remote-setup.sql
├── context.sql
├── setup.sql
├── verify.sql
├── remote-verify.sql
├── final-state.sql
├── reset.sql
├── remote-reset.sql
├── review.py
├── task.sh
├── analytics-distributed-adr.md
├── baseline-v1.5-proposal.json
└── pigsty-declaration.example.yml另有四份月报导出、十份计划、五份 catalog、安全边界与单分片失败探针。
冻结输入身份
frozen-monthly.csv
rows=32
sha256=64b045809e10364fd84a587121d919e8562a15335c4c6c015e91a0ead3a44323
fixture.sql
sha256=3110d0369b0c62fffeee643200f5320d1f6bd26ad5f9950b2ab2b58991080e10
fixture-remote.sql
sha256=da02dbca8d2cfeb0294c369b15cad3619a9433d984f98866b5043eb4508e3e91生成器不读取当前时间,不使用随机数。冻结时间只是 fixture metadata,不参与 运行时决定。
单步建立
./static/labs/ch17/task.sh setupsetup 的顺序:
connect maintenance database postgres
-> create/reuse exact pg36_shard_a/b shells
connect pg36_shard_a
-> exact remote schema rebuild
-> load even tenant IDs
-> analyze
connect pg36_shard_b
-> exact remote schema rebuild
-> load odd tenant IDs
-> analyze
connect pg36_shop
-> exact FDW/data schema rebuild in one transaction
-> load full local baseline
-> create indexes/materialized summary
-> create LIST foreign partitions/views/grants
-> analyze
-> commit
-> VACUUM ANALYZE local sales fact协调端 DDL 在单事务内,避免半成品;数据库壳创建和三个数据库的 schema 建立不能放在一个全局事务里。
为什么 setup 后显式 vacuum
覆盖索引:
CREATE INDEX sales_fact_tenant_day_idx
ON shop_ch17.sales_fact (
tenant_id,
occurred_on,
account_id
)
INCLUDE (amount, units, channel);理论上包含查询所需列,但 Index Only Scan 是否免 heap 访问还取决于 visibility map。新装载表的 page 未必 all-visible。第一轮自动验收曾真实得到:
Bitmap Heap Scan on sales_fact
-> Bitmap Index Scan on sales_fact_tenant_day_idx而审查器要求:
Index Only Scan
Heap Fetches: 0正确修复不是放宽断言,而是在 COMMIT 后显式:
VACUUM (ANALYZE) shop_ch17.sales_fact;随后两个完整周期都稳定得到:
Index Only Scan using sales_fact_tenant_day_idx
actual rows=7500
Heap Fetches: 0这个经历说明:计划回归不仅依赖 DDL 和数据,也依赖 vacuum/visibility 状态。实验必须显式制造自己的前置条件。
本地事实
distributed_amount=2256000.00
distributed_sales=240000
distributed_units=1200000
first_day=2026-01-01
last_day=2026-04-30
local_amount=2256000.00
local_sales=240000
local_units=1200000
shard_rows=dist_0:120000,dist_1:120000
summary_rows=2880
summary_sales=240000远端:
| 数据库 | 租户 | 账户 | 销售 | units | amount | checksum |
|---|---|---|---|---|---|---|
pg36_shard_a | 2,4,6,8 | 200 | 120,000 | 599,988 | 1,188,000.00 | 274002…24e3 |
pg36_shard_b | 1,3,5,7 | 200 | 120,000 | 600,012 | 1,068,000.00 | 0bb770…2629 |
总数相等不够,物理分片 checksum 也必须匹配。
并行边界
psql -X -w \
--dbname="service=$PGSERVICE" \
--set=fdw_host=/path/to/socket \
--set=fdw_port=5432 \
--file=static/labs/ch17/local-parallel-plan.sql通常不需要手工运行;task 会动态读取 socket/port 并注入。
冻结关键路径:
Finalize HashAggregate
-> Gather
Workers Planned: 2
Workers Launched: 2
-> Partial HashAggregate
-> Parallel Seq Scan on sales_fact
actual rows=80000 loops=3证明:
parallel path exists
two workers actually launched
aggregation decomposes into partial/final
240k rows become 32 groups不证明:
production speedup
safe system-wide worker count
behavior under concurrent reportswork_mem 边界
低内存:
Sort actual rows=240000
Sort Method: external merge
Disk: about 5920kB
temp read/write present高内存:
Sort actual rows=240000
Sort Method: quicksort
Memory: about 13645kB
no temp read两个文件:
这证明 spill 可被定位到一个节点;不授权全局调大 work_mem。
汇总边界
Parallel Seq Scan on sales_fact
actual rows=80000 loops=3Seq Scan on daily_tenant_summary
actual rows=2880 loops=1两者输出同一 32 行月报。ADR 因而可以拒绝:
“全局月报每次扫描 24 万行,所以必须分片”更准确的结论:
这条重复聚合可先用 2,880 行日粒度处理;
生产还要评审刷新、新鲜度、迟到和恢复成本。BRIN 只记录候选
catalog 验证:
sales_fact_day_brin_idx
access_method=brin
operator_class=pg_catalog.date_minmax_ops
size > 0
size < covering B-tree in this fixture没有强制一条 BRIN 查询并宣称更快。小表无法代表物理相关性和 block range 收益。
17.6.2 比较单机加速与一个分布式候选
四条相同结果路径
自动导出:
monthly-local.csv
monthly-summary.csv
monthly-distributed.csv
monthly-two-stage.csv分别来自:
monthly-local-export.sqlmonthly-summary-export.sqlmonthly-distributed-export.sqlmonthly-two-stage-export.sql
任务逐个:
cmp static/labs/ch17/frozen-monthly.csv \
evidence/.../monthly-local.csv四个文件都要 byte-identical,不做“行数相等即可”的弱比较。
单租户裁剪
SELECT count(*), sum(amount)
FROM shop_ch17.sales_fact_distributed
WHERE tenant_id = 3
AND occurred_on >= DATE '2026-04-01';关键计划:
Foreign Scan on shop_ch17.sales_fact_dist_1
actual rows=7500
Remote SQL:
SELECT amount
FROM shop_ch17_shard.sales_fact
WHERE occurred_on >= '2026-04-01'
AND tenant_id = 3审查器明确要求:
sales_fact_dist_1 present
sales_fact_dist_0 absent
tenant/date in Remote SQL这同时证明 partition pruning、column projection 和 filter pushdown。
朴素全局聚合
distributed-naive-plan.sql 直接查询
分区父表:
HashAggregate
-> Append actual rows=240000
-> Foreign Scan dist_1 actual rows=120000
Remote SQL: SELECT tenant_id, occurred_on, units, amount ...
-> Foreign Scan dist_0 actual rows=120000
Remote SQL: SELECT tenant_id, occurred_on, units, amount ...远端没有 GROUP BY,协调端获得 24 万条事实再聚合。
这不表示 postgres_fdw 永远不能下推 aggregate;它说明这条父表查询在本次
目标版本和 SQL 形状下没有得到期望的跨分片预聚合。
两阶段聚合
distributed-two-stage-plan.sql
显式分别查询两个 foreign partition:
SELECT tenant_id, occurred_on,
count(*), sum(units), sum(amount)
FROM shop_ch17.sales_fact_dist_0
GROUP BY tenant_id, occurred_on
UNION ALL
SELECT tenant_id, occurred_on,
count(*), sum(units), sum(amount)
FROM shop_ch17.sales_fact_dist_1
GROUP BY tenant_id, occurred_on;外层再按月合并。计划:
GroupAggregate actual rows=32
-> Sort actual rows=960
-> Append actual rows=960
-> Foreign Scan Aggregate dist_0 actual rows=480
Remote SQL ... GROUP BY 1, 2
-> Foreign Scan Aggregate dist_1 actual rows=480
Remote SQL ... GROUP BY 1, 2数据流:
[ \frac{960}{240000} = 0.004 ]
即返回行数为朴素路径的 0.4%。这个比例只描述冻结 fixture 的行数,不能直接 转成“性能提升 250 倍”。
同分片 JOIN 反例
SELECT
account.segment,
count(*) AS sale_count,
sum(sale.amount) AS amount_total
FROM shop_ch17.sales_fact_distributed AS sale
JOIN shop_ch17.account_dim_distributed AS account
ON account.tenant_id = sale.tenant_id
AND account.account_id = sale.account_id
WHERE sale.tenant_id = 3
AND sale.occurred_on >= DATE '2026-04-01'
GROUP BY account.segment;物理上两表的 tenant 3 都在 shard B。实测:
Hash Join on coordinator actual rows=7500
-> Foreign Scan sales_fact_dist_1 actual rows=7500
-> Hash
-> Foreign Scan account_dim_dist_1 actual rows=50两条 Remote SQL 都没有 JOIN。这个反例写进 ADR:
对目标抽象层而言,共置只是设计前提,不是下推证据。必须检查目标产品、 版本、SQL 与
EXPLAIN VERBOSE。
若生产候选是 Citus,应在真实 Citus L1 上创建目标 distributed/reference tables,重新验证 colocated join;不能用 FDW 反例替代,也不能假定一定成功。
应用身份
成功读:
sale_count=7500
amount_total=69375.00写入负例:
exit=3
SQLSTATE 42501catalog:
app_schema_usage=true
app_local_sales_select=true
app_local_sales_write=false
app_distributed_sales_select=true
app_distributed_sales_write=false
app_server_a_usage=true
app_server_b_usage=true六个 mapping 都是具名;password_required=false 被 review 当成必须显式存在
的 lab-only 警告,而不是悄悄依赖环境。
单分片失败
执行器先查询 tenant 2:
healthy_shard_tenant_2=30000再执行全局 count,固定:
exit=3
SQLSTATE 08001任务同时捕获 stdout/stderr;如果只看 stderr,就无法证明健康分片路径曾成功。
错误断开使事务回滚后,重新导出:
server-catalog-after-failure.csv并与故障前 server-catalog.csv 逐字节比较,确保 shard B port 没有残留为 1。
一次完整 evaluate
如果不需要 reset/rebuild 双周期:
evidence_dir="$PWD/evidence/ch17/evaluate-$(date -u +%Y%m%dT%H%M%SZ)"
PG36_EVIDENCE_DIR="$evidence_dir" \
./static/labs/ch17/task.sh evaluateevaluate 会重建一次、采集所有证据并运行 review。
仅验证现有数据库:
PG36_EVIDENCE_DIR="$PWD/evidence/ch17/verify" \
./static/labs/ch17/task.sh verify它分别运行 coordinator、shard A、shard B 的完整数据库内断言。
review 的职责
review.py 不连数据库,只审查 evidence:
manifest version/target/checksum
source generator and frozen CSV hashes
four byte-identical monthly exports
local/remote cardinality and checksums
server/mapping/relation/index/security/size catalogs
parallel/index/spill/summary plans
tenant pruning and Remote SQL
naive/two-stage row shape
non-pushed JOIN counterexample
application read/write
shard failure and restored server catalog
final state
coordinator/remote verify outputs
baseline ADR contract这使 evidence 可以离线审阅,也避免“数据库后来变了,旧报告仍假装当前”。
17.6.3 输出 ADR、PoC 证据、生产代价和撤退路线
ADR 不以产品名开头
analytics-distributed-adr.md
先写背景和决策顺序:
1. fix correctness, SQL, statistics, paths
2. prove parallel/index/BRIN/spill/summary on one node
3. assess offline replica for tolerable-staleness reads
4. enter distribution only after measured resource boundary
5. evaluate Citus when PostgreSQL-compatible sharding fits
6. compare specialized OLAP only when PostgreSQL paths miss SLOpostgres_fdw 的定位是 mechanics/counterexample lab,不是生产性能结论。
ADR 的冻结证据
| 问题 | 证据 | 决策影响 |
|---|---|---|
| 可并行? | 2 workers launched | 单机还有并行路径 |
| 选择性查询? | index-only,heap fetch 0 | 先修访问路径 |
| 内存? | 64kB 外排 / 32MB 内排 | 按并发设局部预算 |
| 重复聚合? | 240k vs 2,880 输入 | 先评估汇总 |
| 单租户路由? | 只访问 shard B | tenant_id 可局部 |
| 朴素全局? | 240k foreign rows | 协调端/网络风险 |
| 两阶段? | 960 aggregate rows | 计算靠近数据 |
| 同分片 JOIN? | coordinator Hash Join | 必须实测下推 |
| shard 失败? | scoped read 成功/global 08001 | 定义 partial semantics |
决策与限制同版本
baseline-v1.5-proposal.json
固定:
target versions
default path
distribution key
routing warning
remote aggregation design
production Citus gate
fixture contracts
expected checksums
lab authentication warning
evidence inventory
rollback contract
limitationscanonical JSON SHA-256:
3dcb7308cf6983122ee860ad3dc2a4b44651549e3d5631770839bb9a0be450c6改变 ADR contract 会改变 release candidate identity。
进入生产 PoC 前的代价
ADR 要预算:
schema/query changes for distribution key
backfill and dual-write/read
coordinator/worker nodes
HA and service routing
network/TLS/auth
backup repository and restore
monitoring/alerting
rebalance capacity
rolling/major upgrade
on-call training
license/cloud cost
exit rehearsal不能只比较机器数量。
Pigsty 交付分支
pigsty-declaration.example.yml
保留两个候选:
Path A:
pg-analytics primary + replica with pg_offline_query
pg_conf: olap.yml
Path B:
pg-citus0/1/2 groups
pg_mode: citus
pg_shard / pg_group它们不是同一 inventory 的叠加方案,也不是完整生产配置。ADR 应先决定测哪条 假设,再补齐目标地址、database、users、extensions、HBA、secret、backup、 service 和 HA。
撤退路线
PoC 撤退:
stop new lab sessions
verify coordinator + A + B exact state
drop coordinator views/matview/tables
drop mappings/servers/extension
drop remote tables/schemas
retain empty database shells
verify zero remaining managed objects生产迁移撤退则应提前设计:
canonical source of truth remains PostgreSQL
dual-read compares checksums
route change has version/epoch
old and new writers cannot both own same key
backfill has watermark and resume point
last reversible point is named
service/DNS rollback is tested
new system data can be exported backreset 的三道 guard
协调 reset 需要:
PG36_RESET_TOKEN=RESET_CH17_ANALYTICS_FDW_LAB
PG36_RESET_TARGET=pg36_shop/shop_ch17+shop_ch17_ext+fdw错误 action token:
SQLSTATE P3660
reset refused: invalid ch17 action token错误 target:
SQLSTATE P3661
reset refused: invalid ch17 target token存在 application_name LIKE 'pg36-ch17-%' 的其他 worker:
SQLSTATE P3663
reset refused: ch17 workers are activetask 会主动启动一个 sleep worker,观察到 PID 后证明 P3663,再 cancel。
精确 reset
协调端顺序:
DROP VIEW ...
DROP MATERIALIZED VIEW ...
DROP TABLE exact parents/local tables ...
DROP SCHEMA shop_ch17;
DROP USER MAPPING ... -- six exact mappings
DROP SERVER ... -- two exact servers
DROP EXTENSION postgres_fdw;
DROP SCHEMA shop_ch17_ext;不使用 CASCADE。输出:
status=coordinator-reset-ok
remaining_data_schema=0
remaining_extension_schema=0
remaining_ch17_servers=0
retained_shard_databases=pg36_shard_a,pg36_shard_b每个 remote:
full remote verify
BEGIN
drop sales_fact
drop account_dim
drop fixture_meta
drop schema
COMMIT输出:
status=remote-reset-ok
remaining_schema=0
database_shell=retained跨库非原子边界
任务先对三个数据库全部 preflight verify,再按:
coordinator -> shard A -> shard B退出。每一步各自事务化,但整体不是一个事务。若 A reset 后 B 失败,系统处于 部分退出状态;恢复方式是按 exact identity 继续补偿或完整重建。
这项 limitation 同时写入 lab contract、baseline 和 ADR,不能用脚本“看起来 是一条命令”掩盖。
手工 reset
只有明确需要退出 fixture 时:
export PG36_RESET_TOKEN=RESET_CH17_ANALYTICS_FDW_LAB
export PG36_RESET_TARGET=pg36_shop/shop_ch17+shop_ch17_ext+fdw
PG36_EVIDENCE_DIR="$PWD/evidence/ch17/reset" \
./static/labs/ch17/task.sh reset它会同时处理两个远端 schema。不要在生产、共享开发数据库或身份未知的目标 上执行。
17.6.4 验收采用 checklist:evidence
完整双周期
evidence_dir="$PWD/evidence/ch17/$(date -u +%Y%m%dT%H%M%SZ)"
PG36_EVIDENCE_DIR="$evidence_dir" \
./static/labs/ch17/task.sh all成功输出:
status=ok
fixture=frozen-byte-identical-four-paths
single_node=parallel+index+summary+spill
distributed=tenant-pruning+fdw+two-stage
counterexamples=hash-is-not-modulo+join-not-pushed
failure=healthy-shard-read+global-08001
guards=P3660+P3661+P3663
postgres_fdw=1.2
pigsty_l1=not-run
release_candidate_checksum=3dcb7308cf6983122ee860ad3dc2a4b44651549e3d5631770839bb9a0be450c6目录:
evidence/ch17/<run>/
├── cycle-1/
├── reset-wrong-token.*
├── reset-wrong-target.*
├── reset-active-worker.*
├── reset-exact/
└── cycle-2/checklist:evidence
| 验收项 | evidence | 通过条件 |
|---|---|---|
manifest | manifest.txt | PG18、FDW 1.2、三库、checksums |
source | manifest + fixture JSON | generator/golden SHA 精确 |
golden | 四份 monthly CSV | 与 frozen byte-identical |
local-facts | fixture-facts.csv | 240k、1.2m、2.256m |
remote-facts | remote-*-state.csv | 各 120k + shard checksum |
parallel | local-parallel-plan.txt | planned/launched=2、partial/final |
covering-index | selective-index-plan.txt | 7,500、Heap Fetches 0 |
spill | low/high plans | external merge vs quicksort |
summary | raw/summary plans | 240k vs 2,880 input shape |
pruning | tenant-pruned-plan.txt | only dist_1 + remote filters |
naive | naive plan | 240k foreign rows |
two-stage | two-stage plan | 480+480 remote aggregate rows |
join-counterexample | collocated plan | coordinator Hash Join |
auth | mapping/security catalogs | six named mappings, read-only app |
app-failure | app-write.* | exit 3, SQLSTATE 42501 |
shard-failure | shard-failure.* | healthy=30k, global 08001 |
catalog-rollback | two server catalogs | byte-identical |
database-verify | three verify outputs | coordinator + A + B status ok |
final-state | final-state.csv | release/rows/checksums exact |
review | review.txt | status=review-ok |
reset-guards | root failure files | P3660/P3661/P3663 |
exact-reset | reset outputs | schemas/servers zero, DB shells retained |
rebuild | cycle-2 | same complete review |
最终状态
final-state.sql 固定:
business_checksum=42fb8ab5444469eba1f104a8e1e529dd
distributed_sales=240000
fixture=ch17-analytics-v1
local_sales=240000
monthly_checksum=644d45544ebbc2a80c42270c38ac6885
naive_transfer_rows=240000
postgres_fdw=1.2
release=1.5-proposal
shard_rows=dist_0:120000,dist_1:120000
summary_rows=2880
tenant3_april=7500:69375.00
two_stage_transfer_rows=960naive_transfer_rows 和 two_stage_transfer_rows 是与计划合同共同审查的固定
事实;如果 SQL 或版本改变,不能只保留硬编码值,必须重新采集 plan 并更新
proposal。
review 输出
status=review-ok
fixture=frozen-byte-identical-four-paths
single_node=parallel+index+summary+spill
distributed=tenant-pruning+fdw+two-stage
counterexamples=hash-is-not-modulo+join-not-pushed
failure=healthy-shard-read+global-08001
business_checksum=42fb8ab5444469eba1f104a8e1e529dd
monthly_checksum=644d45544ebbc2a80c42270c38ac6885
release_candidate_checksum=3dcb7308cf6983122ee860ad3dc2a4b44651549e3d5631770839bb9a0be450c6失败排查顺序
如果 task 失败:
- 保留 evidence,不立即重跑覆盖;
- 找到最后产生的 stderr;
- 判断是环境 guard、业务 checksum、计划 shape、权限还是故障边界;
- 连接三个数据库分别运行 verify;
- 检查 server catalog 是否已回滚;
- 修复原因,不降低断言掩盖差异;
- 新建 evidence 目录完整重跑两个周期;
- 比较两个 manifest。
第一轮 index-only 失败就是这个流程的例子:证据显示 Bitmap Heap Scan, 根因是 visibility map precondition,没有把 reviewer 改成接受任意 index 路径。
生产发布仍缺什么
本地 status=ok 之后仍需:
[ ] representative production-scale data and skew
[ ] independent nodes and failure domains
[ ] target network/TLS/identity
[ ] Pigsty L1 inventory and runtime convergence
[ ] actual Citus or selected candidate, not FDW surrogate
[ ] OLTP+OLAP mixed concurrency
[ ] P50/P95/P99 and open-loop throughput
[ ] CPU/memory/I/O/temp/WAL/network cost
[ ] coordinator/worker HA
[ ] backup-to-empty restore checksum
[ ] rebalance interrupt/resume
[ ] schema change mixed-version test
[ ] rolling/major upgrade
[ ] RPO/RTO and partial-result semantics
[ ] migration dual-read and last rollback point
[ ] exit rehearsal因此 1.5-proposal 是设计与本地机制候选,不是生产批准。
本章最终决策
在冻结工作负载上,当前可支持的结论是:
- 单机仍有并行、覆盖索引、spill 治理和汇总空间;
- 不能因 24 万行原表扫描直接宣布需要分布式;
tenant_id对单租户访问有良好局部性;- 分布式全局聚合必须关注计算位置与协调端输入;
- 同分片不自动证明 JOIN 下推;
- 路由算法不一致会导致静默错误,HASH remainder 不能当整数取模;
- 部分失败和跨数据库退出必须成为业务/运维合同;
- 下一层应先比较 Pigsty offline replica;若代表性容量仍越界,再在真实 Pigsty Citus L1 上验证分布键、HA、恢复和再平衡;
- 专用 OLAP 只有在 PostgreSQL 路径无法满足已定义 SLO,且团队接受第二套 数据管道与值班体系时进入终选。
这就是一份合格选型 ADR 的语气:它不承诺某产品必胜,而是清楚说明当前证据 允许做什么、禁止外推什么,以及什么新证据会触发下一次决策。
上一节:部署最小分布式 PoC · 返回本章目录 · 下一章:万法归宗:PostgreSQL 数据平台与替代边界 · 查看全书目录 · 查看索引中心