BodySense 数据库第 20 课:Database Capstone——从 Schema 到 Failure Engineering
数据库深化课程最终综合验收:选择 BodySense 一条真实数据链,从 schema、query、index、transaction、concurrency、recovery、observability 和 privacy 做端到端审计。
[!info] related notes
- 课程 MOC:BodySense 数据库工程深化课程 MOC
- 前置:第 19 课
- 总览:数据库事务、并发与恢复总览
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] 综合判断
- 一个 endpoint p95 从 80ms 升到 2s,数据库 CPU 只有 25%,首先能否判断“需要更强 CPU”?
- 新索引把读从 200ms 降到 20ms,是否就一定应该合并?
- 一条业务 transaction 在 DB commit 后再调用 Redis,Redis 失败,数据库应不应该自动 rollback?
- PostgreSQL replica 正常、backup job 也 success,是否足以说明灾备合格?
- 两个并发请求都通过应用层
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,至少包含:
- ER / ownership diagram;
- 3 个关键 invariant;
- 3 条真实 SQL + EXPLAIN;
- 当前索引及一个优化/删除候选;
- 一个 transaction timeline;
- 一个并发失败复现;
- 一次 lock/slow query 排障;
- 一个 migration compatibility 设计;
- backup/restore evidence;
- privacy erasure path。
最终 20 题
[!question] 不看答案独立回答
- Schema 与 ORM model 的边界是什么?
- UNIQUE 为什么是并发控制工具?
- Normalization 解决什么,为什么又会有 read projection?
- LEFT JOIN 条件放 ON/WHERE 有何差异?
- database/sql 的
sql.DB为什么不是单 connection?- Migration 为什么不等于改 struct?
- Page/Buffer 心智模型如何帮助解释查询成本?
- 联合索引顺序为什么由 query pattern 决定?
- estimated rows 与 actual rows 差异意味着什么?
- N+1 为什么在高并发下尤其危险?
- Transaction boundary 应由谁拥有?
- Read Committed 下同一事务两次 SELECT 为什么可能不同?
- MVCC 为什么让普通读写少阻塞?
- 为什么长事务会伤害 VACUUM?
- Deadlock 与 lock wait 的区别是什么?
- Optimistic lock 与 pessimistic lock 如何选择?
- WAL 为什么先于数据页?
- VACUUM、ANALYZE、Autovacuum 各自解决什么?
- Backup、Replication、PITR 的区别?
- 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。