深度精读 · 数据系统

DDIA 4 章深度精读

Ch.1 Trade-offs · Ch.2 NFR · Ch.8 Transactions · Ch.12 Stream

2026-05-17 爸爸

DDIA 2nd 深度精读 · Ch.1 / Ch.2 / Ch.8 / Ch.12

4 章合一,~ 1300 字/章。 给 12 人 Solutions team 用的"非分布式工程师也能学到东西"版本。 原文来自 Vonng 中文翻译 repo (Vonng/ddia · zh 分支)。 例子、数字、人名(Aaliyah / Bryce)、系统名(PostgreSQL / TiDB / Snowflake / Kafka / Spanner / FoundationDB / VoltDB / CockroachDB / Debezium / Datomic 等)都直接来自原文,不是我编的。

Ch.1 · 数据系统架构权衡 Ch.2 · 非功能性需求 Ch.8 · Transactions Ch.12 · Stream Processing

为什么是这 4 章

Ch.1 给框架(事务型 vs 分析型 / 记录系统 vs 派生系统),Ch.2 给衡量标准(延迟百分位 / 容错 / 可维护性),Ch.8 给事务 ACID 的精确语义,Ch.12 给流处理 + Event Sourcing 的实现侧。 四章串起来 = 一个 agent 系统从架构到非功能到一致性到可重放的完整推理链。 单独看 Ch.8 容易当数据库教程读,4 章一起读才有 team lead 该有的视角。


Ch.1 · 数据系统架构权衡(Trade-offs in Data Systems Architecture)

核心命题(1 句话)

没有"放之四海皆好"的数据架构 — 每个选择都是权衡;真正的能力是说清"我们在什么条件下选 A,代价是 B"。

为什么这章对 12 人 team lead 重要

客户来谈 agent 项目时,90% 的对话都绕不开"你们的数据放哪里 / 你们的状态怎么存 / 你们怎么跟我们的数据仓库 / OLTP 库连"。 这章的术语(OLTP / OLAP / ETL / 数据湖 / 数据湖仓 / 记录系统 / 派生数据 / 云原生 / 多租户 / 微服务 / Serverless)是客户 IT 头每天用的语言。 不熟这套词,你的 12 人 team 在客户会议里就只能听不能说。

关键概念

1. OLTP vs OLAP — 这不是数据库选型,是访问模式之争

OLTP(联机事务处理)的特征是点查询:通过 key 拿单条记录,基于用户输入插入/更新/删除,查询是预定义的固定集合,数据规模 GB-TB,代表当前状态。 OLAP 的特征是扫大量记录做聚合:分析师自由写 SQL,数据规模 TB-PB,代表"随时间发生的事件历史"。 原文给了具体例子:连锁超市分析师问"一月份每家店总收入"、"促销期比平时多卖多少香蕉"、"X 品牌婴儿食品最常跟哪个牌子尿布一起买" — 这种聚合扫描如果跑在 OLTP 库上,会拖垮线上交易。 这就是为什么 80-90 年代企业开始把 OLAP 拆到单独的数据仓库。 对 agent 系统而言:agent 的实时决策(基于当前 state)是 OLTP 模式,agent 的复盘 / 训练数据 / 行为分析(基于历史 event 流)是 OLAP 模式。 两者必须分离,否则 agent 一跑分析就把生产 agent 的响应时间拖到秒级。

2. 数据仓库 → 数据湖 → 数据湖仓 — 一条演化线

数据仓库存的是经过 ETL 清洗、结构化、关系模式优化的数据,通常 SQL + BI 工具(Tableau / Looker / Power BI)。 数据科学家不满意,因为他们需要的是特征向量 / 文本 / 图像 / 传感器读数这种"原始任何东西",所以出现了数据湖 — 把对象存储(S3 / Azure Blob / Cloudflare R2)当文件仓库,什么格式都能放,先 dump 进来再说("寿司原则:原始数据更好")。 但数据湖太乱,SQL 跑不动,所以又出现了数据湖仓(Apache Hive / Spark SQL / Presto / Trino):在湖的文件上跑仓的查询引擎 + 元数据层。 对 agent 系统:agent 的 conversation log / tool call trace / model output 都属于"先 dump 进湖,再按需做 view"的工作负载 — 数据湖仓是天然的存储选择。 Snowflake / BigQuery / Azure Synapse 是云原生版本(原文表 1-2)。

3. 记录系统 vs 派生数据系统 — 这是全书 most underrated 的概念

记录系统(System of Record):数据的权威/canonical 版本,每个事实只表示一次,通常规范化存储。 派生数据系统:从记录系统转换/处理出来的结果,丢了可以重建。 例子:缓存命中走缓存、未命中回退到底层 DB(DB 是记录系统、缓存是派生);反规范化值、索引、物化视图、训练好的模型,都是派生数据。 原文一句话很狠:"派生数据从技术上说是冗余的,但它往往是读查询高性能的关键"。 对 agent 系统:agent 的 memory 是派生数据(可以从 conversation log 重算),vector index 是派生数据(可以从原始文档重建),agent 决策日志才是记录系统(丢了真的丢)。 团队里的工程师如果不能在白板上指着架构图说"这个是记录系统,那三个是派生",项目就会出现"两边数据对不上但都觉得自己对"的事故。

4. 云原生架构 = 存储与计算分离 + 多租户

传统系统里 CPU/RAM/磁盘住在同一台机器,云原生系统把它们解耦:S3 只管存,要分析就在 S3 之外的 compute 节点跑(数据通过网络流过来)。 Snowflake 是这个范式的代表:基于 S3 做数据仓库。 原文强调一个反直觉的点:云的虚拟磁盘 EBS / 持久盘不是真磁盘,是另一组机器在网络上模拟出来的块设备 — 每次 I/O 其实是网络调用,导致传统基于磁盘假设的软件在云上跑会对网络故障极敏感。 真正云原生的系统会绕过块设备抽象,直接对接对象存储 + 定制存储 API。 另外多租户(同一硬件服务多个客户)能省钱,但需要严格的资源隔离 — 一个 noisy 客户能拖死所有人。 对 agent:多客户 agent 实例如果共享 LLM 推理资源,必须有 quota / rate limit,否则一个客户的 burst 流量会让所有客户体验劣化。

5. 分布式不是越多越好 — 单机能搞定就单机

原文一句直白话:"如果你可以在单台机器上做某件事情,与搭建分布式系统相比通常要简单得多,成本也更低"。 现代单机数据库(DuckDB / SQLite / KùzuDB)能扛的工作负载远超大多数人想象。 但有几种情况你必须分布式:固有分布式(用户在不同设备)、容错(单数据中心可能宕)、可伸缩(单机扛不住)、延迟(跨地域用户)、数据驻留法律(GDPR 要求数据留在欧盟境内)、弹性(峰谷差大)、专用硬件(GPU vs 大磁盘)。 给 12 人 team 的启示:不要默认 multi-agent 是答案。 多数 customer agent 用 1-2 个 agent + 一个共享 memory + 几个 tool 就够,上来就 5+ agent 的设计大概率是过度工程。

Agent 设计直接映射

跨章互文

Ch.1 的"记录系统 vs 派生数据"是 Ch.12 整章的世界观地基:event log = 记录系统,所有 view / cache / index / 当前 state 都是派生。 Ch.1 的 OLTP/OLAP 拆分对应 Ch.2 的"用户响应时间 vs 后台分析吞吐量"两套不同 SLO。 Ch.1 提到"分布式事务在微服务架构里基本不用"直接指向 Ch.8 整章 — 你看到 Ch.8 讲 2PC / XA 那么多坑,就理解为什么 Ch.1 一开始就劝退分布式事务。 跟其他书的互文:ADP Ch.7 Multi-Agent 决定"几个 agent",这章决定"agent 背后的数据怎么放";AM Ch.9 DDD 子域识别正好就是 Ch.1 微服务"一个 service 一个 DB"的边界划分依据。

反 pattern

  • 架构图明确标"记录系统 / 派生数据"两层
  • OLTP 路径和 OLAP 路径用不同存储
  • 客户 SaaS 数据先 ETL 到自己的湖再 agent 查
  • 欧盟客户标 EU-only service
  • 默认单 agent,有理由再多 agent

  • 用一个数据库同时跑 agent 实时决策 + 分析师 ad-hoc SQL(典型互相拖死)
  • agent 直连客户生产 OLTP 库做大查询(每次都把客户 DB CPU 打到 80%)
  • 把缓存 / vector index 当记录系统(丢了不能重建,事故时无救)
  • 默认所有 agent 都要 multi-agent 协作(复杂度翻倍,价值不一定翻倍)
  • 用云虚拟磁盘抽象当本地盘用(每次 I/O 走网络,p99 会被拖)

1 张图建议

横向三栏图:左栏「客户 source」(客户 OLTP DB / SaaS API / 文件) → 中栏「我们的湖」(对象存储 + CDC 接入 + Outbox events) → 右栏「派生 view」(agent memory / vector index / dashboard / 决策快照)。 左到中标"ETL / CDC / Outbox",中到右标"流处理 / 物化视图"。 在中栏明确写 "System of Record = 这一栏"。 一张图把全章讲完。

Lightning 5 min 版本(给 team 内部分享用)

  1. 30 秒:agent 项目里,"数据放哪"问得最多。 这章给你回答的框架。
  2. 1 分钟:OLTP(点查、当前状态、用户实时面对)vs OLAP(扫聚合、历史事件、分析师后台)。 不要混。
  3. 1 分钟:记录系统(权威、丢了真的丢)vs 派生数据(冗余、丢了能重建)。 在白板上画。 每个组件归类。
  4. 1 分钟:云原生 = 存储计算分离(S3 + 上面跑的 compute)。 EBS 不是真磁盘是网络服务。 客户问"你们怎么 scale" — 答这个。
  5. 1 分钟:分布式不是越多越好。 单 agent + 多 tool 通常胜出。 multi-agent 是有必要再上,不是 default。
  6. 30 秒:行动:每个 active 项目下周补一张"记录系统 / 派生数据"架构图。 不超过 30 行 markdown。

Ch.2 · 非功能性需求(Nonfunctional Requirements)

核心命题(1 句话)

"可靠 / 快 / 能扛 / 好维护"不是抽象口号 — 必须用百分位、SLO、负载描述、容错边界这些具体数字定义,否则永远讨论不清楚。

为什么这章对 12 人 team lead 重要

客户合同里的 SLA、内部 sprint 里的 "agent 响应慢" 抱怨、新人 onboarding 时"我们的系统能扛多大量"这种问题 — 都需要用 Ch.2 的语言回答。 这章是给团队建立公共讨论语言的章节。 没有这套词,每次内部讨论性能都会变成印象式吵架(A 说"很快"B 说"挺慢" — 没基准没法收敛)。

关键概念

1. Twitter / X 首页时间线案例 — 物化视图的代价

原文用了一个具体案例:X 平均每秒 5,700 条新帖,峰值 150,000;每用户平均 200 关注 + 200 粉丝。 首页时间线如果每次都 join 查(SELECT posts JOIN follows ...),1000 万在线用户每 5 秒轮询 = 每秒 200 万次查询 = 数据库每秒 4 亿次"按发送者查最近帖" — 不可行。 解法:物化时间线 — 用户发帖时把这条帖推到所有粉丝的预算好的时间线里(扇出),读取时直接拿缓存。 扇出因子 200 → 写入放大 1M/s,但读取从 4 亿/s 降到几乎 0。 极端情况:粉丝量上亿的名人(原文点名 Barack Obama)单独处理,读取时跟普通物化时间线合并。 这个 case 是后面 Ch.12 物化视图 + Event Sourcing 的实战 motivation。 对 agent:agent 的"用户上下文 / 客户 profile"必须预先物化成 view,不要每次对话都 join。

2. 百分位 ≫ 平均值 — 平均数会撒谎

平均响应时间对"用户感知"几乎无用,因为分布是长尾的(原文图 2-5 用了 lognormal 分布)。 中位数 p50 告诉你"典型用户等多久";p95 / p99 / p999(尾部延迟)才告诉你"最糟糕的体验"。 原文给了 Amazon 的具体数字:Amazon 内部服务以 p99.9 设定响应要求 — 哪怕只影响千分之一的请求 — 因为最慢的请求往往来自数据最多的客户,而这些客户通常是最有价值客户。 但继续优化到 p99.99(万分之一)成本过高、收益递减,因为高百分位越来越受不可控随机因素影响。 原文还引了 Yahoo 的研究:控制搜索结果质量后,快/慢响应差 1.25 秒以上时,快速搜索的点击量高 20-30%。 对 agent:agent 响应时间报"平均 2 秒"几乎没意义,要报 p50 / p95 / p99,并对 p99 的最慢请求做 case study(通常是 input token 最多 / context 最复杂的高价值客户)。

3. 尾部延迟放大 — 微服务架构的诅咒

一个终端请求触发多个后端调用(并行),终端响应时间 = 最慢那个的响应时间。 原文叫尾部延迟放大(图 2-6)。 后端调用数越多,撞上慢调用的概率越高,所以"看似只有 1% 慢"会变成"30% 的终端请求慢"。 对 agent 系统这点特别毒:一个 agent 任务调 5 个 tool,每个 tool 单独 p95 是 300ms,但只要有一个偶尔 5 秒,整个 agent 响应就是 5 秒。 解法:工具调用尽量并发 + 设硬超时 + 超时后降级(fallback 答案而不是死等)。

4. 容错边界必须明确写出来 — "高可用"不是数字

原文区分故障(fault)(局部组件挂)和失效(failure)(整个系统挂)。 容错系统永远是"针对某些故障 + 某个数量以内"的。 例子:容忍 2 块硬盘同时坏、3 节点坏 1 个。 但没有系统能容忍"地球被黑洞吞掉"(原文的玩笑)。 关键数字:机械硬盘年故障率 2-5%,10000 盘集群平均每天坏 1 个;SSD 年故障率 0.5-1%;ECC 内存仍有 >1% 机器每年遇到不可纠正错误;每 1000 台机器有 1 台 CPU 偶尔算错;SSD 在 32,768 小时(< 4 年)固件 bug 全军覆没(2012 年 6 月 30 日闰秒导致大批 Java 应用同时挂的 Linux 内核 bug 也是原文例子)。 软件故障比硬件故障更可怕,因为高度相关(所有节点同一套软件 = 同一批 bug)。 给 team 的启示:agent 的容错协议要明确写 "我们容忍单 LLM provider 宕机(切到 fallback model);不容忍 vector DB 整个不可用(读不到 memory 就停服)" — 这种边界写清楚比"高可用"三个字有意义 100 倍。

5. 可维护性三个维度 — 可运维 / 简单 / 可演化

软件成本的大头不在初次开发,在后续维护(bug fix / 故障排查 / 适配新平台 / 偿还技术债 / 加新功能)。 原文三个抓手: - 可运维(operability):监控完备、可以下机器维护不停服、有完善文档、行为可预测无 surprise - 简单性(simplicity):降低偶然复杂性(accidental complexity,工具/实现导致的),保留本质复杂性(essential complexity,业务问题固有的) — 区分这两者是关键 - 可演化(evolvability):减少不可逆操作,允许架构随业务变化重塑 — 数据库迁移这种不可逆的事要极度谨慎

原文一句话很狠:"好的运维常能绕过糟糕(或不完整)软件的局限;但再好的软件,碰上糟糕运维也难以可靠运行"。 对 12 人 team:你的 agent codebase 维护难度比客户感觉到的 80% 都重要 — 客户只看 demo,你的工程师每天看 codebase。

Agent 设计直接映射

跨章互文

Ch.2 的 SLO/SLA 概念对应 Ch.8 的"事务隔离级别该选哪个" — 一致性是 SLO 的一部分。 Ch.2 的"物化时间线 + 扇出"案例是 Ch.12 的物化视图 + Event Sourcing 的预热,Ch.12 给的是实现侧理论。 Ch.2 的"硬件故障 + 软件故障"是 Ch.8 持久性讨论的现实背景 — 你看 Ch.8 持久性那段提"SSD 在 fsync 后仍可能违反保证","PostgreSQL 20 年 fsync 用错",直接呼应 Ch.2。 跨书互文:TT(Team Topologies)第 5 章四种基础团队拓扑 + 第 7 章交互模式,决定"运维流程谁负责";AX(Agent Experience)的稳健性章节跟 Ch.2 容错思想同源。

反 pattern

  • kickoff 跟客户对齐 p50 / p99 / 月度可用性三个数字
  • 每月 chaos drill(杀关键依赖)
  • 事故后无责备复盘 — 改流程不改人
  • tool call 并发 + 硬超时 + 降级
  • 不可逆操作 2 人 review + dry-run

  • 报 agent 性能只报平均值(中位数都没有)
  • 把"高可用"当 SLA 写进合同(没有具体数字)
  • 事故后开会找替罪羊
  • tool 串行调用 + 无超时(一个慢全卡)
  • 从不演练故障 — 等真事故才知道脆弱在哪
  • 追求 p99.99(成本暴涨,收益递减)

1 张图建议

四象限图:横轴=响应时间(p50→p99→p999),纵轴=请求量(log scale)。 画三条线:绿线(健康日)、黄线(轻度高峰)、红线(过载,响应时间指数级上升)。 标注:绿黄之间是 SLO 边界,红线区域要触发负载卸除(load shedding)/ 熔断器(circuit breaker)/ 指数退避(exponential backoff,这些都是原文术语)。 给 team 看一次,以后讨论容量规划就有共同图景。

Lightning 5 min 版本

  1. 30 秒:不是"agent 跑得快不快",是"我们对什么数字负责"。
  2. 1 分钟:百分位才是用户体验。 Amazon p99.9 是因为最慢的客户是最有钱的。 我们 agent 该报 p50 / p95 / p99 不报 average。
  3. 1 分钟:尾部延迟放大 — 一个 agent 调 5 个 tool,有一个慢 = 整体慢。 并发 + 超时 + 降级。
  4. 1 分钟:故障是常态,失效要预防。 明确写"我们容忍什么,不容忍什么"。 每月 chaos drill。
  5. 1 分钟:事故无责备 — 改流程不改人。 不可逆操作必须 2 人 review。
  6. 30 秒:行动:下周每个 active project 补一张 SLO 表(p50/p99/可用性),公开在 confluence。

Ch.8 · Transactions

核心命题(1 句话)

"几件事必须一起成功或一起失败"不是免费的 — ACID 看似 4 个字母,实际充满细节歧义、隔离级别陷阱、分布式协议的妥协,你必须知道在哪里需要多强、为什么不更强。

为什么这章对 12 人 team lead 重要

任何 reconciliation / claims processing / 长流程 audit 类 agent,跟客户 IT 头讨论时一定绕到"数据一致性怎么保证"。 不熟 Ch.8,你只能说"我们保证一致性"(被秒怼);熟 Ch.8 后你能说"这块用 snapshot isolation 够、这块需要 serializable、跨 service 用 Saga + idempotent 而不是 2PC,因为 2PC 在崩溃场景会卡 lock 数十分钟" — 客户立刻知道你的 team 是真懂还是嘴硬。

关键概念

1. ACID 是营销词,细节才是真相

ACID 由 Theo Härder 和 Andreas Reuter 1983 年提出,本意是为容错术语精确化。 但原文一句狠话:"今天,当一个系统声称自己'符合 ACID'时,实际上你能期待什么保证并不清楚。 不幸的是,ACID 基本上已经成为了一个营销术语"。 4 个字母里: - A 原子性:不是关于并发(并发是 I),而是"如果中途崩了,要么全部生效要么全部回滚"。 原文说"可中止性(abortability)"是更准确的词 - C 一致性:严重过载的词 — 至少有 5 种不同含义(副本一致 / 一致快照 / 一致性哈希 / CAP 中的线性一致 / ACID 中的应用不变式)。 原文明说 ACID 的 C 通常是应用责任,不是数据库责任 - I 隔离性:经典定义是 serializable(可串行化),但 Oracle 实际是 snapshot isolation(更弱) — 这是原文点名的事实 - D 持久性:写入磁盘 / 复制 — 但原文用了一整个 sidebar 把"完美持久性不存在"展开了:SSD 在 fsync 后仍可能违反保证PostgreSQL 20 年 fsync 用错SSD 固件 bug 在 32,768 小时全军覆没30-80% 的 SSD 前 4 年会出现至少一个坏块

2. 隔离级别 — 弱到强 5 个 + 各自漏洞

原文给的层级(从弱到强): - Read Uncommitted:允许脏读(读到未提交数据)— 几乎所有 DB 支持但生产不该用 - Read Committed:防脏读 + 防脏写 — PostgreSQL / Oracle / SQL Server 默认。 但仍允许 read skew(Aaliyah 银行账户从一个账户转 100 到另一个,中途查余额看到 500+400=900,以为丢了 100) - Snapshot Isolation / Repeatable Read:每个事务看到事务开始时的一致快照,通过 MVCC(多版本并发控制)实现 — PostgreSQL 通过 txid 字段 + inserted_by / deleted_by 标记,更新内部转为"删旧版+插新版"。 关键规则:读者永不阻塞写者,写者永不阻塞读者。 但仍允许 write skew + phantom - Serializable:最强,3 种实现路径:(1) 字面上串行执行(VoltDB 用这个,通过 stored procedure + sharding 做到高吞吐);(2) 2PL 两阶段锁定(几十年来唯一可行,性能差);(3) SSI 可串行化快照隔离(2008 年才出现,PostgreSQL / CockroachDB / FoundationDB 用,乐观并发控制)

命名混乱是原文反复强调的事实:PostgreSQL 的 "Repeatable Read" = SI,MySQL 的 "Repeatable Read" 比 SI 还弱,Oracle 的 "Serializable" = SI(不是真 serializable),IBM Db2 的 "Repeatable Read" = 真 serializable。 原文一句话:"没有人真正知道可重复读意味着什么"。

3. Write Skew + Phantom — Snapshot Isolation 防不住的隐蔽 bug

原文用了医生值班例子(图 8-8):Aaliyah 和 Bryce 是当前 shift 的两位值班医生,两人都不舒服,同时点了下班按钮。 每个事务检查"当前值班医生 ≥ 2 → 安全可以下班 → 把自己设为 off",在 SI 下两个事务都看到"= 2",都通过检查,都提交 → 结果0 个医生值班,违反"至少 1 人值班"的不变式。 这就是 write skew:不是脏写、不是 lost update(两人改的是不同的行),但并发提交后系统约束被破坏。 类似例子原文给了 5 个:会议室预订重叠(SELECT 没查到冲突 → 两个事务都 INSERT)、多人游戏两子同位用户名抢注(唯一约束能救)、透支检查(两笔花费同时插入导致负余额)、值班医生

Phantom(幻读)是 write skew 的根因:当前事务的 SELECT 查出"不存在的行"被另一事务的 INSERT 改变了。 SI 防不住 phantom,只有真 serializable 才行。 替代方案是 materialize conflicts(物化冲突):在 DB 里人为创建"会议室+时间段"的所有可能行,然后 SELECT FOR UPDATE 那一行 — 但原文说"很难且容易出错,应当作最后手段"。 对 agent:agent 做"检查再行动"的逻辑(check inventory → place order / check schedule → book slot / check balance → deduct)都是 write skew 高风险场景。

4. 2PC 两阶段提交 — 跨节点原子的代价

跨节点事务,单纯"给所有节点发 commit 请求"不行 — 可能某些节点 commit 成功某些失败。 2PC 引入 coordinator(协调器): - Phase 1 prepare:协调器问所有 participant "你能保证 commit 吗",participant 必须把数据写盘 + 检查约束 + 锁定相关 row,然后回 yes/no - Phase 2 commit/abort:全 yes 就 commit,任何 no 就 abort

原文用"西方婚礼"比喻:牧师问新郎新娘是否愿意(prepare),双 yes 后宣布"事务已提交"(commit)。 关键不归路:(1) participant 说 yes 后放弃 abort 的权利;(2) coordinator 决定后不可撤销

2PC 的真实代价 — 原文 8-14 图的灾难情景:coordinator 发了 prepare、所有 participant 投 yes,coordinator 把 commit 决定写盘后,给 DB2 发了 commit 但发给 DB1 之前崩了。 DB1 不知道是 commit 还是 abort → in-doubt transaction(存疑事务)。 DB1 不能单方面决定 — 只能等 coordinator 恢复。 这期间 DB1 持有的 row lock 不能释放 → 其他事务全部阻塞。 原文一句话:"如果 coordinator 崩溃要 20 分钟恢复,这些锁就保持 20 分钟"。

XA 事务 是 2PC 的跨异构系统标准(1991 年,C API,JTA / JMS / JDBC 都基于此),问题更多:最低公分母陷阱(无法跨系统检测死锁、不适用 SSI)、孤立 in-doubt transaction(coordinator log 损坏 → 永远 in-doubt,需要管理员手动决定 commit 还是 rollback)、heuristic decision(紧急逃生舱:participant 单方面决定 — 原文说"启发式是'可能破坏原子性'的委婉说法")。

5. 内部分布式事务 vs 异构分布式事务 — 一个能做好,另一个做不好

原文区分两类: - 数据库内部分布式事务(YugabyteDB / TiDB / FoundationDB / Spanner / VoltDB / Aurora DSQL / CockroachDB / Kafka 的 transactional producer):同一软件、同一团队、能用更先进协议(共识算法做 coordinator 复制 + 直接节点通信)。 这类真的能 work - 异构分布式事务 / XA:跨 vendor、跨技术栈,陷入最低公分母 — 原文说"许多云服务由于它们引起的操作问题而选择不实现分布式事务"

替代方案是 idempotent + retry + dedup:消息处理记录每条 message ID 到 DB(用唯一约束),已处理就跳过 — 实现 effective-once 不需要 XA。 这是 Kafka Streams 的做法,也是 Ch.12 容错章节的核心思想。

Agent 设计直接映射

跨章互文

Ch.8 的"持久性不是绝对"直接呼应 Ch.2 硬件故障数据(年故障率 / SSD 固件 bug)。 Ch.8 的"异构分布式事务陷入最低公分母"呼应 Ch.1 微服务设计(每 service 一个 DB,不共享 → 跨 service 事务难)。 Ch.8 末尾的"用 message ID + dedup 实现 effective-once 不用 XA"直接是 Ch.12 流处理容错的思想前身 — Ch.12 进一步说这是 Kafka Streams / Flink 的标配。 Ch.8 的 write skew + materialize conflicts 在 Ch.12 维护物化视图章节再次出现(用流处理避免)。

跨书互文:ADP Ch.7 multi-agent 设计里 agent 之间用 message + dedup 而不是 RPC + 2PC,直接是 Ch.8 末尾结论的应用;AM 第 7 章 EventStorming 画出来的每个 event 在 Ch.12 是 first-class concept;MDR konkret Rule 11(医疗合规)要求完整 audit trail — Ch.8 解释为什么完整 audit 不能靠 mutable DB,要靠 event log。

反 pattern

  • 明确写"哪段路径需要 serializable,哪段 RC 够"
  • 跨 service 用 Saga + idempotent + dedup table
  • "检查再行动"路径用 SELECT FOR UPDATE 或 serializable
  • 客户决策日志多副本 + 跨地域 + 定期校验
  • agent 提示"已完成"必须等 commit 落盘

  • 跟客户说"我们 ACID 兼容"(没人知道这是什么)
  • 跨 service 上 2PC / XA(in-doubt 卡 lock 数十分钟)
  • 默认 serializable(性能崩,大多场景不需要)
  • 用 snapshot isolation 防 write skew(防不住)
  • 把 SSD durability 当绝对承诺(SSD 在 fsync 后仍可能违反)
  • 把 "Repeatable Read" 当 serializable(PostgreSQL 是 SI,MySQL 更弱)

1 张图建议

决策树:agent 操作的对象数量 → 1 个 row / 1 个 service / 多个 service。 - 1 row → Atomic write op(单语句 UPDATE)够,无锁 - 1 service 多 row → DB transaction,Read Committed 默认;有 write skew 风险升 Serializable / SELECT FOR UPDATE - 多 service → Saga + idempotent + message ID dedup(不用 2PC) - 客户系统接入 → 同上 + 事后 reconciliation

一张图把 12 人 team 的事务决策方法论定下来。

Lightning 5 min 版本

  1. 30 秒:ACID 是营销词 — 细节充满歧义,你必须知道你 agent 需要哪个细节
  2. 1 分钟:Read Committed(默认)< Snapshot Isolation < Serializable。 PostgreSQL 默认 RC,Repeatable Read 实际是 SI
  3. 1 分钟:Write Skew — 医生值班 + 两人同时下班 = 0 人值班。 agent 做 check-then-act 都有这个风险。 SI 防不住,要 serializable 或 SELECT FOR UPDATE
  4. 1 分钟:2PC / XA 不要用 — coordinator 崩了 in-doubt transaction 卡 lock 几十分钟。 跨 service 用 Saga + idempotent + dedup
  5. 1 分钟:持久性不是绝对 — SSD 在 fsync 后仍可能违反保证,PostgreSQL 20 年 fsync 用错。 客户决策日志要多副本 + scrub
  6. 30 秒:行动:下周每个 active project 列一张"操作 → 一致性需求"表,5 行就够

Ch.12 · Stream Processing(+ Event Sourcing)

核心命题(1 句话)

存"事件流"而不是"当前状态" — 状态变成 fold(events),你获得完美 audit、time-travel debug、从同一日志派生多视图的能力;这是 agent 时代最重要的架构转换。

为什么这章对 12 人 team lead 重要

合规/金融/医疗客户(REMATIQ 这类 medtech、MDR Rule 11 场景、所有 SOX/GDPR/HIPAA/MDR 客户)的核心要求是 "agent 每个决策可追溯"。 mutable DB 答不了这个题。 Event Sourcing + Stream Processing 是天然答案。 这章不读,你的 team 在合规客户的最后一轮谈判会卡住。 读完后,"我们的 agent 系统是 event-sourced,完整 audit trail 是 first-class" 就是 trump card。

关键概念

1. AMQP/JMS 风格 vs 基于日志的消息代理 — 两种世界观

原文有一个具体估算:典型 20TB 大盘 + 250MB/s 顺序写,满速能写 22 小时再开始删旧 — 实践中保留几天到几周 buffer 很容易。 这意味着新 consumer 加入时可以从头(或任意 offset)重新读 — 这是 AMQP 做不到的根本差异。

对 agent:不要默认上 RabbitMQ。 Kafka(或 Redpanda 这种轻量替代)的 replay 能力让 agent 调试 / 重跑 / 添加新 consumer 几乎无成本。

2. CDC 数据变更捕获 — 给 mutable DB 装上事件流

原文:大多数 DB 的复制日志被当内部细节、不公开。 CDC 是把复制日志公开出来 — Debezium 是开源代表(支持 MySQL / PostgreSQL / Oracle / SQL Server / Db2 / Cassandra),Kafka Connect 提供集成,Maxwell 解析 MySQL binlog,GoldenGate 是 Oracle 版本,pgcapture 是 PostgreSQL 版本。 把 source DB 当 leader、其他系统(搜索索引 / 缓存 / 数据仓库 / vector DB)当 follower。

关键陷阱:Outbox 模式必备。 原文一段话讲得很清楚:CDC 如果直接复制内部表 schema,等于把"内部实现细节"变成了"外部契约" — 删一列就会让下游消费者崩。 解法是 Outbox:专门维护对外发布的 outbox events 表,业务在同一事务里写业务表 + outbox 表,CDC 只读 outbox。 这样内部 schema 演化不影响下游。

对 agent:agent 跟客户 SaaS / DB 集成时,强烈推荐让客户用 Outbox 而不是让你的 CDC 直接读他们的业务表。 你的 agent 在客户改表后还能正常跑;否则一次客户 schema migration 你就上线事故。

3. Event Sourcing vs CDC — 同思想不同抽象层

原文引 Jim Gray 和 Andreas Reuter 1992 年的话:"从原理上讲,数据库并非必需;日志已经包含了全部信息。 之所以要保留数据库(即日志末端的当前状态),只是为了提高读取性能。" 这是 Event Sourcing 的灵魂句子。 现在状态 = 整合(events),数据库只是缓存。

数学比喻(原文图 12-7):state(now) = ∫ stream(t) dt,stream(t) = d state(t) / dt。 状态是事件流的积分,事件流是状态的微分。

4. CQRS + 多视图 — 一个日志,N 个 view

CQRS(Command Query Responsibility Segregation,命令查询职责分离):写走 event log,读走 derived view — 两边独立 scale,read view 可以多份(给 LLM 的 view / 给 UI 的 view / 给 analytics 的 view / 给客户的 view)。 原文一句话:"通过将数据写入的形式与读取形式相分离,并允许几个不同的读取视图,你能获得很大的灵活性"。

实践中 Materialize / RisingWave / ClickHouse / Feldera 在做"增量物化视图维护"(数据库变化时只算 delta 不重算全量),Kafka Streams + ksqlDB 也支持这种用法。 PostgreSQL 的 REFRESH MATERIALIZED VIEW 是非增量的(每次重算全量) — 不适合实时场景。

对 agent:agent 的 memory 不应该是"一个表" — 应该是从事件流派生的多个 view:short-term memory(最近 N 个事件)、episodic memory(按 session 分组的事件)、semantic memory(汇总后的概念图)、vector memory(嵌入索引)。 全部从同一个 event log 派生,丢了能重建,加新 view 不影响旧 view。 这就是 ADP Ch.8 Memory 的工程实现答案。

5. 容错 + Exactly-once 的真相

原文一句话:"Exactly-once 实际上更准确的说法是 effectively-once(等效一次)"。 完全的 exactly-once 在工程上几乎做不到 — 但可以通过下面三个机制做到"看起来 exactly-once":

Time / Window 的复杂性 — 别用 processing time:原文图 12-8 例子:流处理器重启 1 分钟后处理积压,如果按 processing time 统计 QPS,会看到一个假尖峰(其实 QPS 一直稳定)。 必须用 event time(事件实际发生的时间戳),并准备好处理 straggler(迟到事件):忽略 + 监控丢弃率,或发布 correction(修正先前窗口的输出)。

移动设备时钟不可信:用户可能改了设备时间。 解法 — 记 3 个时间戳:(a) 事件发生时间(设备时钟)、(b) 事件发送时间(设备时钟)、(c) 服务器接收时间(服务器时钟)。 用 (c)-(b) 估算设备时钟偏移,校正 (a)。

6. 不变性的局限 — 真正"删除"很难

GDPR 要求"用户注销后删个人数据" — 但 immutable event log 本质上不允许删除。 解法: - Excision(Datomic 的术语)/ Shunning(Fossil VCS 术语):假装事件从未写入。 但备份 / SSD / 文件系统的旧版本可能还在 - Crypto-shredding(加密粉碎):敏感数据加密存,删除时销毁 key — 密文还在但永远读不出来。 限制:必须预先决定哪些数据共享同一 key,粒度粗

原文一句话:"删除更多是在'让数据更难被取回',而非'让数据绝对不可恢复'"。 客户合规对话里这个差异要说清楚。

Agent 设计直接映射

跨章互文

Ch.12 全章是 Ch.1 "记录系统 vs 派生数据" 的实现侧延伸 — event log 是 system of record,所有 view / state / cache 是派生。 Ch.12 的 Twitter 时间线物化视图直接 cite Ch.2 同一例子。 Ch.12 的"exactly-once 用 message ID + dedup,不用 XA" 直接是 Ch.8 末尾结论的具体技术实现。 Ch.12 的 immutability 跟 Ch.8 snapshot isolation 的 MVCC 同源(都是保留多版本而不原地覆盖)。

跨书互文:AM Ch.7 EventStorming 是产品/业务侧画 event,Ch.12 是工程侧实现 event,二者完美闭环 — 一个 team 都该读;ADP Ch.8 Memory 给 agent memory 的抽象 layer,Ch.12 给具体实现答案(event sourcing + 多 view);MDR konkret Rule 11(医疗 SaMD)对完整 audit 的硬要求 → Ch.12 是工程答案;TT Ch.7 团队交互模式里"通过 API 解耦"的更高级版本就是"通过 event 解耦"。

反 pattern

  • agent state change 全 emit event,append-only log 是 source of truth
  • 客户 DB 接入用 Outbox + CDC
  • agent memory = 多 view 派生自同一 event log
  • DLQ + 告警 + 人工 review 流程
  • 每月 replay drill
  • 合规 audit 用 event log 做 first-class 卖点
  • PII 用 crypto-shredding
  • 用 event time 不用 processing time 做窗口

  • agent 用 mutable DB 当 source of truth(完整 audit 答不了)
  • CDC 直接读客户业务表(客户改 schema 你就崩)
  • agent 之间用 HTTP RPC 同步通信(耦合 + 不可 replay)
  • 把 events 当 row diff dump("orders 表第 5 行更新" — 不是 domain language)
  • event schema 不带 version(5 年后旧 events 读不出来)
  • 100M events 没 snapshot(算一次当前状态卡死)
  • processing time 窗口(假尖峰一堆)
  • 承诺 "exactly-once"(说 effectively-once 更诚实)
  • 用 PostgreSQL REFRESH MATERIALIZED VIEW 做实时(非增量,慢)
  • 直接把 PII 写 event log(无法 GDPR 删除)

1 张图建议

竖向流图:最左 Producer(客户 SaaS / agent action / user event) → append 到 Kafka topic(partitioned by key) → 3 个并行 consumer:(1) materialized view 维护(写 Postgres / ClickHouse 给 UI) → (2) vector index 维护(写 Pinecone / Qdrant 给 LLM) → (3) audit / analytics 视图(写 BigQuery / Snowflake 给合规 / 商业分析)。 中间标注 "Event Log = System of Record",三个 view 全是 derived。 最下面加一条 "DLQ" 横线,所有 consumer 的失败消息送这里。 一张图把 agent 系统该长什么样讲清楚。

Lightning 5 min 版本

  1. 30 秒:存事件不存状态。 状态从事件 fold 出来。 这是 agent 时代最重要的转换
  2. 1 分钟:Kafka 不是消息队列,是 append-only 持久日志。 Replay 能力让加新 consumer / 重跑 / 调试几乎免费
  3. 1 分钟:CDC 给客户 mutable DB 装上事件流。 必须用 Outbox 模式,别直接读客户业务表 — 否则客户改 schema 你就崩
  4. 1 分钟:CQRS = 写走 log,读走多个 view。 agent memory 应该是多 view 派生,不是一个表
  5. 1 分钟:Exactly-once 实际是 effectively-once。 用 message ID + dedup + idempotent,不用 XA。 用 event time 不用 processing time
  6. 30 秒:行动:下周选 1 个 active project 改成 event-sourced 架构,作为 reference design

4 章合一 · Team 12 人下半年路线图

Week 1-2
Ch.1 + Ch.2 全员分享
每个 active project 补"记录系统 / 派生数据"架构图 + 3 个 SLO(p50 / p99 / 月度可用性)
Week 3-4
Ch.8 隔离级别 + Saga workshop
team 内部分享:"我们哪里需要 serializable,哪里 RC 够,跨 service 用 Saga 不用 2PC"。 列一张表
Month 2
Ch.12 Event Sourcing reference design
选 1 个 active project 改为 event-sourced + Outbox + 3 个 derived view + DLQ。 作为后续所有项目的模板
Month 3
Chaos drill + Replay drill
每月固定时间:(1) 杀关键依赖看 agent 表现 (2) 从 N 天前 offset 重跑验证可重建。 写在 team OKR
Month 4+
合规 / audit 卖点上线
所有新合规客户提案默认包含 "event-sourced + 完整 audit trail" 章节,medtech / 金融客户卡口必备
Ch.1 · 架构权衡 Ch.2 · SLO + 容错 Ch.8 · 一致性精确语义 Ch.12 · Event Sourcing → Agent 系统 ready for 合规客户

Source

所有具体例子(Aaliyah / Bryce 医生值班 / Twitter 时间线 / SSD 32,768 小时 bug / PostgreSQL 20 年 fsync 错 / Jim Gray 1992 引用 / Star Wars 上映顺序)、系统名(PostgreSQL / Oracle / TiDB / FoundationDB / Spanner / VoltDB / CockroachDB / Kafka / Redpanda / Debezium / Datomic / Materialize)、数字(机械硬盘 2-5% / SSD 0.5-1% / Twitter 5700 QPS / 扇出 200 / 20TB-22h)均直接来自 Vonng 中文 repo 原文,不是我编的。