← [ BACK_TO_NOTES ]

BodySense 数据库第 20 课:Database Capstone——从 Schema 到 Failure Engineering

数据库深化课程最终综合验收:选择 BodySense 一条真实数据链,从 schema、query、index、transaction、concurrency、recovery、observability 和 privacy 做端到端审计。

#course #growing #backend #database #postgresql #bodysense

[!info] related notes

BodySense 数据库第 20 课:Database Capstone——从 Schema 到 Failure Engineering

[!abstract] Capstone 最后一课不再引入主要新概念,而是要求把前 19 课组合成一个生产数据库工程判断。

从零建立概念:Capstone 不是再背一遍知识点,而是证明能把它们组合起来

前 19 课分别学了:

Ownership
Schema / Constraint
SQL
Data Access
Migration
Page / Buffer
Index
Planner
ORM Query Shape
Connection Pool
Transaction
Isolation
MVCC
Lock
Concurrency Pattern
WAL
VACUUM
Backup
Security / Lifecycle

生产问题不会说:

这是第 13 课 MVCC 题,请回答。

真实问题更像:

用户保存 BodyState 时偶尔 409
线上突然 p95 变成 3s
Job 偶尔执行两次
migration 部署时接口卡死
删用户后向量检索仍出现旧记录
主库事故后恢复需要多久

你要自己识别:

这是哪几层共同作用?

Vertical Slice 是什么

不是只审查:

users table

而是选一个真实用户动作,从入口一直跟到持久化和恢复。

例如:

User changes BodyState
↓
HTTP / Contract
↓
Service
↓
TransactionManager
↓
BodyStateRepository
↓
SELECT FOR UPDATE
↓
Fact/Observation changes
↓
Revision row
↓
current_revision update
↓
COMMIT / WAL
↓
later query/cache/agent uses result

这才叫 vertical slice。

Capstone 的第一步:写 Invariant,不要先点评代码风格

例如 BodyState:

Invariant A:
current_revision monotonically increases

Invariant B:
one logical transition produces at most one revision

Invariant C:
active singleton facts cannot silently fork

Invariant D:
revision history and current pointer agree

只有先写 invariant,才能判断:

constraint 对不对
transaction 是否够大
lock 是否必要
retry 是否安全

否则 review 很容易退化成:

函数太长
命名不够优雅
建议加缓存

而忽略数据库正确性。

第二步:画 Ownership Map

对这个 slice 的每份状态标:

Owner
Lifetime
Durability
Rebuildable?
Derived from?
Deletion path

例如:

BodyStateFact
→ PostgreSQL truth

HealthWorkspace cache
→ Redis derived copy

UI form state
→ browser temporary state

这样缓存/异步/恢复边界立刻清楚。

第三步:从 Schema 找“数据库真正保护了什么”

不要只读 Go struct。

直接看 migration:

PK?
FK?
UNIQUE?
CHECK?
NOT NULL?
partial unique?
indexes?
ON DELETE behavior?

然后列出:

protected by DB
vs
only protected by application

如果一个关键 invariant 只存在:

if ... { return error }

要问并发或旁路写入是否可能绕过。

第四步:Trace 一个真实 Read Path

例如:

Handler
→ Service
→ Repository
→ GORM
→ SQL
→ Plan
→ Pages

记录:

queries/request
rows returned
rows filtered
index used?
estimated vs actual
buffers
pool wait

这一步是性能 baseline。

第五步:Trace 一个真实 Write Path

画 timeline:

BEGIN
↓
read current state
↓
lock?
↓
validate expected revision
↓
write rows
↓
write revision
↓
move pointer
↓
COMMIT

每一步问:

如果这里失败?
如果另一个 transaction 同时到来?
如果发生 timeout?

第六步:Concurrency Attack

不要只跑 happy-path test。

同时发两个冲突请求:

A based on revision 7
B based on revision 7

验证:

一个成功?
一个 conflict?
有没有丢 update?
有没有两个 revision 都 claim same next number?

再故意:

reverse lock order
slow transaction
retry

观察真实 failure behavior。

第七步:Performance Attack

构造真实数据量。

不要:

table has 30 rows
→ query 1ms
→ declare index perfect

而是:

representative rows
representative distribution
hot/cold cache
concurrent requests

记录:

p50 / p95 / p99
EXPLAIN ANALYZE BUFFERS
DB CPU/I/O
pool stats
lock waits

然后修改 index/query 再比较。

第八步:Migration Attack

给这个 slice 改一次 schema。

例如:

add required source_version

设计:

expand
backfill
verify
switch code
contract

然后问:

old app still works?
new app before migration works?
migration lock time?
rollback loses data?

第九步:Crash / Recovery Attack

至少回答:

transaction crash before commit
crash after commit response
primary database unavailable
restore from backup
PITR target

哪些由:

transaction atomicity
WAL
replication
backup
PITR

分别负责。

第十步:Privacy / Erasure Attack

假设用户请求删除。

追踪:

main rows
child rows
revision/history
Redis
object storage
vector data
logs
backup

如果有一层没有 ownership/delete policy,就说明 data lifecycle 不完整。

示例:BodyState Update 的完整推理

可以这样串:

Domain:
BodyState current context must advance coherently

Schema:
body_states owns current_revision
facts/observations/revisions hold durable history

Transaction:
one patch shares one DB transaction

Concurrency:
body_states row FOR UPDATE
+ expectedRevision

Durability:
commit protected by PostgreSQL WAL

Read:
repository rehydrates current facts / projection

Performance:
indexes should match user_id + lifecycle/review access pattern

Failure:
transaction error → rollback
stale caller → conflict

Recovery:
database backup/PITR protects durable history

Privacy:
user scoped facts/revisions must join erasure inventory

这就是从“知道很多数据库名词”升级成“能审计一个真实系统”。

Capstone Evidence Pack

最终不要只写一篇主观文章。

应保存可复现证据:

SQL files
EXPLAIN plans
test commands
concurrency timelines
pg_stat / pg_locks snapshots
migration diff
benchmark table
restore log
failure injection result

这样 L4/L5 的结论才不是“我感觉懂了”。

1. 选择一条真实 Vertical Slice

推荐候选:

BodyState current-context update
Treatment revision acceptance
Runtime Event append + recovery
Job claim / retry / completion
Privacy erasure
Knowledge publication lifecycle

2. 八层审计

对同一个 slice 回答:

1. Ownership   → durable truth 是什么?
2. Schema      → PK/FK/UNIQUE/CHECK 守什么 invariant?
3. Query       → 主要 read/write SQL shape?
4. Index       → 访问路径和写放大?
5. Transaction → 原子边界在哪里?
6. Concurrency → lock/version/constraint/idempotency?
7. Recovery    → crash/backup/restore 后如何证明正确?
8. Operations  → 用什么 metric/log/plan/pg view 定位故障?

Prediction

[!question] 综合判断

  1. 一个 endpoint p95 从 80ms 升到 2s,数据库 CPU 只有 25%,首先能否判断“需要更强 CPU”?
  2. 新索引把读从 200ms 降到 20ms,是否就一定应该合并?
  3. 一条业务 transaction 在 DB commit 后再调用 Redis,Redis 失败,数据库应不应该自动 rollback?
  4. PostgreSQL replica 正常、backup job 也 success,是否足以说明灾备合格?
  5. 两个并发请求都通过应用层 if not exists 检查,最终却只能有一个 row,最可靠的最后防线是什么?

[!success]- Answer / 解析 1. 不能。 可能是 connection pool wait、lock wait、I/O、bad plan、外部事务过长等。先收集 wait/query/pool/plan 证据。

2. 不一定。 还要看写入成本、index size、维护/vacuum、其他查询、真实 QPS 与是否已有重叠索引。

3. 不会。 PostgreSQL transaction 已 commit;跨系统一致性需要 outbox、compensation、idempotency 或把 Redis 定位为可失败派生层。

4. 不足。 需要隔离 restore drill、数据/约束验证和明确 RPO/RTO;replica 也不能替代历史备份。

5. 数据库 UNIQUE/EXCLUDE 等 constraint。 应用检查用于 UX,数据库负责并发下最终 invariant。

Final Lab

输出一篇 BodySense Database Architecture Review,至少包含:

  1. ER / ownership diagram;
  2. 3 个关键 invariant;
  3. 3 条真实 SQL + EXPLAIN;
  4. 当前索引及一个优化/删除候选;
  5. 一个 transaction timeline;
  6. 一个并发失败复现;
  7. 一次 lock/slow query 排障;
  8. 一个 migration compatibility 设计;
  9. backup/restore evidence;
  10. privacy erasure path。

最终 20 题

[!question] 不看答案独立回答

  1. Schema 与 ORM model 的边界是什么?
  2. UNIQUE 为什么是并发控制工具?
  3. Normalization 解决什么,为什么又会有 read projection?
  4. LEFT JOIN 条件放 ON/WHERE 有何差异?
  5. database/sql 的 sql.DB 为什么不是单 connection?
  6. Migration 为什么不等于改 struct?
  7. Page/Buffer 心智模型如何帮助解释查询成本?
  8. 联合索引顺序为什么由 query pattern 决定?
  9. estimated rows 与 actual rows 差异意味着什么?
  10. N+1 为什么在高并发下尤其危险?
  11. Transaction boundary 应由谁拥有?
  12. Read Committed 下同一事务两次 SELECT 为什么可能不同?
  13. MVCC 为什么让普通读写少阻塞?
  14. 为什么长事务会伤害 VACUUM?
  15. Deadlock 与 lock wait 的区别是什么?
  16. Optimistic lock 与 pessimistic lock 如何选择?
  17. WAL 为什么先于数据页?
  18. VACUUM、ANALYZE、Autovacuum 各自解决什么?
  19. Backup、Replication、PITR 的区别?
  20. Redis、PostgreSQL、Kafka 在 durable truth / fast state / event integration 上如何分工?

[!success]- Answer / 解析 1. ORM model 是应用表示,Schema 是数据库真实结构/约束;二者通过 migration 显式演进。
2. 并发事务最终由数据库原子裁决唯一性,避免 SELECT-then-INSERT race。
3. Normalization 减少重复事实和更新异常;read projection 为查询/接口形状有意组合或冗余。
4. ON 控制匹配,WHERE 过滤 join 后结果;LEFT JOIN 的右表 WHERE 条件可能消掉无匹配左行。
5. 它是并发安全连接池句柄,按需借还多个底层 connection。
6. Struct 只描述当前应用 shape;migration 描述持久状态从旧版本到新版本的转换。
7. 查询成本可具体化为访问多少 page、buffer hit/read、随机/顺序 I/O,而不是只看行数。
8. B-Tree leading key、equality/range/order 共同决定可缩小的扫描范围。
9. Planner cardinality estimate 可能失真,会连锁影响 scan/join/order 选择。
10. 它把单请求放大成大量 DB round trip,占满 pool 并造成排队放大。
11. 通常由知道完整业务原子动作的 Application Service 拥有,Repository 参与。
12. 默认 RC 每个 statement 可获取新的 committed snapshot。
13. Reader 按 snapshot 读可见 tuple version,writer 创建新 version,很多场景无需互斥。
14. 老 snapshot 让旧 tuple 仍可能可见,VACUUM 不能安全回收。
15. Lock wait 是等待持锁者释放;deadlock 是等待图形成环,数据库必须 abort 一个参与者。
16. 高冲突短临界区偏 pessimistic;低冲突、计算可重试偏 optimistic,但最终取决于 invariant/成本。
17. 保证 crash 时数据库能用日志解释并恢复尚未落盘的数据修改。
18. VACUUM 回收/冻结/visibility;ANALYZE 更新 planner statistics;Autovacuum 自动调度二者。
19. Backup 是历史独立副本;Replication 是近实时副本;PITR 用 base backup + WAL 恢复到目标时间点。
20. PostgreSQL 持 durable business truth;Redis 持 cache/fast state/coordination;Kafka 更适合跨服务 durable event stream/integration,具体 ownership 仍需显式定义。

L4 / L5 Gate

L4: 能用真实 SQL/plan/双 session/pg view/restore evidence 证明判断。
L5: 能安全修改一个数据库边界,并通过 migration、并发测试、性能基线和恢复验证保持 invariant。

课程完成后,优先把证据回写到 BodySense Master Course 中对应的 FSO Part 13 / TECH SCHOOL canonical exercise。

创建于 2026/9/18 更新于 2026/9/18
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本