NoBug World NoBug World

DDIA 第二版精炼:从数据库组件到数据流系统的设计框架

AI 辅助创作声明 本文的内容、结构或代码排版等, 100% 由 AI 辅助生成。

《Designing Data-Intensive Applications》第二版比第一版更像一本“数据系统设计教材”。它不只是解释数据库、复制、分片、事务、共识、批处理和流处理这些技术点,而是不断追问同一个问题:

当一个应用必须在性能、成本、可靠性、可演化性、隐私、法律责任和用户伤害之间做选择时,工程师如何做出可解释的技术判断?

这篇文章整理自 DDIA 第二版中文内容。我不会逐章复述原书,而是把全书重组为一份可以独立学习的资料:先建立判断标准,再看数据如何被表达、存储、复制、分区和事务化,接着进入分布式系统的现实约束,最后回到批流处理、派生数据系统和数据伦理。

0. 材料定位

DDIA 第二版的主题可以概括为一句话:

数据密集型应用的设计,本质是在不完美的硬件、不可靠的网络、不断变化的需求和复杂社会约束下,选择合适的数据模型、存储结构、一致性保证和数据流架构。

它不是某个数据库产品的使用指南,也不是分布式算法论文集。它更像一套工程判断框架:看见一个数据系统设计方案时,你要能问出正确的问题,理解它解决了什么、牺牲了什么、适用于哪里、会在哪里失败。

第二版相对第一版的重要变化是:它把“权衡”放到更前面,显著扩展了云服务、数据仓库、数据湖、事件溯源、CQRS、DataFrame、向量检索、工作流、分拆数据库、端到端数据流、可审计性和数据伦理等主题。也就是说,第二版不再只围绕数据库内部机制展开,而是更关心现代组织如何把多个异构数据系统组合成一个可运行、可演化、可验证的整体。

1. 全局知识地图

全书可以拆成七层。

层次核心问题关键主题
判断标准为什么没有唯一正确架构?OLTP/OLAP、云与自托管、分布式与单机、法律与社会约束
非功能需求怎样定义“好系统”?性能、尾延迟、SLA、可靠性、容错、可伸缩性、可维护性
数据表达业务事实如何进入系统?关系、文档、图、事件溯源、CQRS、DataFrame、数组
存储与演化字节如何被写入、读取、升级?LSM-tree、B-tree、列式存储、全文索引、向量索引、编码兼容性
分布式数据多节点如何保存和修改数据?复制、分片、二级索引、事务、隔离级别、分布式事务
分布式正确性在网络和时钟不可靠时如何协调?部分失效、超时、进程暂停、线性一致性、逻辑时钟、共识
派生数据系统多个系统如何组合和演化?批处理、流处理、CDC、事件日志、物化视图、端到端完整性、审计、伦理

这七层之间有强依赖关系。你不能先谈共识再谈故障模型,因为共识就是为了在不可靠网络和节点故障下做决定。你也不能只谈 Kafka、Flink 或数据仓库,而不谈数据从记录系统派生到搜索索引、缓存、推荐模型和报表时的一致性边界。DDIA 第二版的价值正在于把这些主题连成一条工程链路。

2. 第一原则:数据系统都是权衡

很多架构争论看起来像工具之争:用关系数据库还是文档数据库,用 Kafka 还是消息队列,用流处理还是批处理,用云托管还是自建集群。DDIA 第二版首先把问题拉回到权衡本身。

OLTP 和 OLAP 是第一组基本分野。OLTP 面向用户在线请求,单次操作通常读写少量记录,要求低延迟和高并发。OLAP 面向分析,单次查询可能扫描大量数据,要求高吞吐、列式布局、压缩、向量化执行和复杂聚合。二者访问模式不同,用户群体不同,存储结构也不同。把所有需求塞进同一个系统,通常会让某一边变差。

记录系统和派生数据也要分清。记录系统保存事实的权威版本,派生数据是为了某种访问模式从事实计算出来的视图,例如搜索索引、缓存、反规范化表、报表、特征表、推荐模型。派生数据可以丢掉重建,记录系统不能随便丢。这个区分会贯穿后面的 CDC、事件日志、批处理、流处理和分拆数据库。

云服务与自托管也不是价值观选择。托管服务可以减少运维负担、提供弹性和更快启动速度,但会带来成本结构、供应商锁定、可观测性、合规、数据主权和故障边界问题。云原生架构让存储和计算分离更常见,也让对象存储、托管数据库、托管消息队列和无服务器计算成为系统设计的常用积木。

分布式与单机同样没有绝对答案。单机方案更简单、更容易理解,很多业务远没有大到必须分布式。分布式系统带来容错、地理低延迟和更高容量,但也引入网络不确定性、时钟不可靠、部分失效和协调成本。一个成熟工程师不应把分布式当成默认高级方案,而应先问:单机是否已经足够?真正需要分布式的原因是容量、容错、地理分布,还是只是提前复杂化?

第二版还强调数据系统受法律和社会约束影响。隐私法规、数据主体权利、审计要求、删除权、用途限制和数据泄露风险,都会反过来影响架构。例如,一个系统如果到处复制个人数据、缺乏数据来源追踪和删除传播机制,它就不仅是技术债,也是合规风险。

3. 非功能需求:把“好”定义清楚

架构讨论最容易失败的地方,是大家都说系统要“快、稳、可扩展”,但没人定义这些词。

性能不能只看平均值。用户体验通常被高分位延迟支配,尤其是一个请求依赖多个后端调用时,只要其中一个慢,整体就慢。平均响应时间会隐藏长尾问题;p95、p99、p999 更能暴露真实体验。服务等级目标也应该建立在这些指标上,而不是模糊承诺。

可伸缩性必须先定义负载。说“这个系统可扩展”没有意义,正确问法是:当哪种负载增长时,系统如何保持性能?负载可以是请求数、写入吞吐、读写比例、活跃用户数、数据量、热点 key、粉丝关系扇出、查询复杂度或批处理数据规模。不同负载维度对应不同瓶颈。

社交网络首页时间线是典型例子。拉模式是在用户打开首页时查询所有关注对象的最新帖子;写入轻,读取重。推模式是在帖子发布时把它写入粉丝的时间线;读取快,写入可能爆炸。普通用户适合推,名人用户可能适合拉或混合策略。这说明可伸缩性不是某个组件天然属性,而是读写路径之间的工作量分配。

可靠性要区分 fault 和 failure。fault 是组件出错,failure 是系统整体无法向用户提供正确服务。可靠系统不是没有 fault,而是能容忍 fault,避免它扩散成 failure。硬件故障、软件 bug、人为误操作和外部依赖故障都要考虑。软件故障尤其危险,因为它常常是相关故障:同一个 bug 可能让很多节点同时出错。

可维护性包含三件事:可运维性、简单性、可演化性。可运维性要求系统可观察、可恢复、可升级、可容量规划。简单性不是功能少,而是抽象恰当,偶然复杂度低。可演化性要求系统能适应新需求、新规模、新团队结构和新合规要求。

4. 数据模型:选择你希望系统自然表达什么

数据模型决定开发者如何思考业务,也决定查询和演化的成本。

关系模型的优势是通用、成熟、擅长多对多关系和声明式查询。SQL 的核心价值不是语法,而是让开发者描述“要什么”,让优化器决定“怎么做”。这使系统可以重写查询、选择索引、调整 join 顺序和并行执行。

文档模型适合自包含的树状数据。例如一份用户简历包含教育经历、工作经历、联系方式等,一次读取整个文档很自然。文档模型的优势是局部性好、应用对象和存储结构接近、模式灵活。但当多对一、多对多关系变多时,文档模型会变得别扭。地区、学校、组织、标签、推荐人一旦从字符串变成实体,应用就要处理引用、反规范化和 join。

图模型走向另一端:它适合任意实体都可能互相连接、查询重点是沿关系遍历的场景。属性图、Cypher、三元组、SPARQL、Datalog 解决的是复杂关系路径表达问题。你可以用关系数据库模拟图,但递归查询和路径模式往往不如图模型自然。

事件溯源把事实表示为不可变事件日志,而不是只保存当前状态。它适合复杂业务领域:保留“发生过什么”,再从事件派生出当前状态、审计视图、读模型和报表。CQRS 则把写模型和读模型分开:写侧保留业务命令和事件,读侧为查询构建物化视图。这种方法提高可审计性和可演化性,但也增加事件设计、重放、幂等和最终一致性的复杂度。

DataFrame 和多维数组把关系数据扩展到机器学习、统计分析和科学计算。它们说明“数据模型”不是数据库领域的专属概念:当列很多、矩阵运算多、分析代码和模型训练成为核心工作负载时,表、数组和向量之间的边界会变得重要。

数据模型选择可以用一句话判断:选那个让核心业务关系最自然、让常见访问模式最直接、让未来演化代价最低的模型。

5. 存储与检索:索引是在读写之间移动成本

数据库最基本的问题是:写入数据,之后再找回来。索引的本质是从主数据派生出的额外结构,它加速读取,但增加写入、存储和维护成本。

OLTP 存储常见两大路线。

日志结构存储只追加写入,不直接修改旧文件。写入先进入内存结构,随后刷成有序的 SSTable;后台把多个段合并,丢弃过期值和 tombstone。LSM-tree 把随机写变成顺序写,因此写吞吐高,适合写密集场景。代价是读取可能查多个层级,后台压缩可能消耗 I/O,压缩跟不上时会造成延迟和空间问题。

B-tree 把磁盘组织成固定大小页,通过就地更新页来维护有序索引。它读路径直接、范围查询稳定,是关系型 OLTP 数据库的主力。代价是随机写、页分裂和 WAL 维护。粗略地说,LSM-tree 更偏写吞吐,B-tree 更偏读稳定,但真实结果取决于工作负载和实现细节。

OLAP 存储完全不同。分析查询通常扫描大量行,但只读取少数列,因此列式存储比行式更合适。列式布局能减少 I/O,并让同一列的数据更容易压缩。向量化执行和即时编译则减少 CPU 开销。数据仓库、数据湖和云对象存储把存储计算分离进一步推向主流。

第二版还扩展了全文检索和向量嵌入。全文索引用倒排索引解决关键词搜索;向量数据库则把文本、图片或其他对象表示为高维向量,通过相似度查找语义接近的对象。它们都是“为了某种查询模式构建的派生结构”。理解这一点很重要:向量索引不是神秘新数据库类型,而是索引家族里针对相似度搜索的一员。

6. 编码与演化:数据格式决定系统能否平滑升级

系统会不断变化。新增字段、删除字段、改服务接口、换客户端版本、滚动升级,都会让新旧代码同时存在。因此编码格式必须支持向后兼容和向前兼容。

向后兼容是新代码能读旧数据;向前兼容是旧代码能读新数据,至少能忽略未知字段。滚动升级要求二者同时成立,否则一次发布就可能让部分节点互相读不懂。

语言自带序列化通常不适合长期存储和跨服务通信,因为它绑定语言和运行时,兼容性弱,还可能有安全风险。JSON、XML、CSV 人可读、生态广,但类型模糊,数字精度、二进制数据和 schema 约束需要额外处理。Protocol Buffers、Avro 等 schema 驱动格式用更明确的规则换取紧凑编码、代码生成和兼容性管理。

编码不只影响文件,也影响数据流方式。数据可以经由数据库流动,经由 REST/RPC 服务流动,经由工作流引擎流动,也可以经由消息和事件流动。

RPC 的危险在于把远程调用伪装成本地函数调用。网络请求可能超时、丢失、重复、变慢;调用者不知道请求没到、响应丢了,还是对方已经处理但响应丢了。重试可能造成副作用重复。因此远程调用应当显式处理超时、重试、幂等、版本和失败语义。

事件驱动架构和消息代理提供了另一种耦合方式。生产者不直接等待消费者,消息可以缓冲、重放、扇出。只要消息格式支持演化,生产者和消费者就能独立升级。但消息系统不是自动正确:顺序、重复、丢失、幂等和消费者状态仍然要设计清楚。

7. 复制与分片:把数据放到多台机器后,所有边界都会显形

复制是把同一份数据放到多个节点上,目的包括高可用、低延迟、读扩展和离线操作。难点不是复制静态数据,而是复制持续变化的数据。

单主复制最容易理解:写入到 leader,再复制到 follower。它能提供较强的一致性语义,但 leader 是写入瓶颈,故障切换复杂。异步复制延迟低,但 leader 挂掉时可能丢已确认但未复制的写入。同步复制更安全,但 follower 出问题可能拖慢甚至阻塞写入。

复制延迟带来用户可见异常:用户写完读不到自己的数据,先看到新数据后又看到旧数据,或者先看到回复后看到问题。DDIA 用读己之写、单调读、一致前缀读来描述这些一致性需求。它们不是理论洁癖,而是产品体验和业务正确性问题。

多主复制允许多个地点接受写入,适合跨地域、离线客户端和协同编辑,但必须处理冲突。无主复制让客户端向多个副本写、从多个副本读,常用仲裁参数 n、w、r 描述。它提高故障容忍度,但在并发、时钟、读修复、hinted handoff、sloppy quorum 下有很多细节。

冲突处理的核心是因果关系。两个操作是否并发,不取决于物理时间是否重叠,而取决于一方是否知道另一方。版本向量、CRDT 和操作变换都试图让系统在弱协调下收敛。最后写入胜利虽然简单,但会静默丢失并发写,不适合不能丢数据的场景。

分片解决单机容量和吞吐限制。范围分片保留键顺序,适合范围查询,但容易热点;哈希分片更均匀,但破坏范围局部性。重平衡要处理节点增减、分片迁移和负载倾斜。请求路由要知道哪个分片在哪个节点。

二级索引让分片更复杂。本地二级索引写入便宜,但查询可能要打到所有分片。全局二级索引查询集中,但写入可能更新多个索引分片。分片的基本目标是让分片尽量独立;任何跨分片写入、跨分片约束、跨分片事务都会把复杂度带回来。

8. 事务:把复杂错误压缩成可重试的中止

事务是一个抽象层。它让应用在一定范围内假装并发错误、崩溃和部分写入不存在,把很多复杂错误压缩成“事务中止,然后重试”。

ACID 中最容易误解的是一致性。原子性是要么全做要么全不做;隔离性是并发事务互相少干扰;持久性是提交后尽量不丢。一致性更多是应用层不变量:数据库可以提供约束和事务机制,但如果应用逻辑写错,数据库不能凭空知道业务正确性。

隔离级别是事务学习的核心。

隔离级别防止什么仍可能发生什么
读已提交脏读、脏写不可重复读、读取偏差、丢失更新、写偏差
快照隔离读取偏差、很多幻读场景写偏差,部分系统仍需显式处理丢失更新
可串行化等价于某种串行执行成本更高,可能中止更多事务

丢失更新来自读-改-写竞争。写偏差更隐蔽:两个事务都读到某个条件成立,各自写入不同对象,最后共同破坏不变量。医生值班例子就是典型写偏差:两名医生都看到至少两人在岗,于是各自请假,结果无人值班。快照隔离不一定能防止它,只有可串行化才能从根上处理。

可串行化有三类实现思路:真正串行执行、两阶段锁定、可串行化快照隔离。真正串行执行适合事务很短、可分片、单核吞吐足够的场景。两阶段锁定保守但可能性能差。SSI 乐观执行,在提交时检测不可串行化的冲突,必要时中止事务。

分布式事务用两阶段提交保证多个参与者原子提交,但跨异构系统的 XA 事务在性能、故障恢复和协调器依赖上问题很多。第二版的后续章节会给出另一条路线:用事件日志、确定性重试和幂等写入,在很多场景下替代跨系统分布式事务。

9. 分布式系统的麻烦:网络、时钟和知识都不可靠

单机程序常常运行在确定性幻觉里:调用函数,要么返回,要么抛错。但分布式系统没有这种清晰边界。

网络请求可能丢失、延迟、重复;响应也可能丢失、延迟。没有响应时,你不知道请求是否到达、对方是否处理、响应是否丢失。超时只是怀疑机制,不是真相机制。

时钟也不可靠。日历时钟可能跳变,单调时钟适合测量持续时间但不能表达全局时间。NTP 能减少偏差,但不能让所有节点共享完美时间。依赖同步时钟做锁、租约、顺序判断时,必须理解误差边界。

进程可能暂停。GC、虚拟机挂起、操作系统调度、缺页、容器迁移都可能让一个进程长时间停止,然后恢复时仍以为自己持有锁或租约。为避免旧 leader 恢复后继续写入,系统需要 fencing token 之类的防护机制。

这些问题共同构成部分失效:系统的一部分坏了,另一部分还在运行,而且没人能立即准确知道哪些部分坏了。分布式算法必须在这种不完美知识中工作。多数派原则的意义正在于此:单个节点不能安全决定全局事实,必须通过仲裁让系统在故障下仍能前进。

10. 一致性与共识:强保证昂贵,但有时不可替代

线性一致性是一种强一致性模型:系统表现得像只有一个副本,所有操作按某个全局顺序原子生效。它易于理解,适合唯一性约束、锁、领导者选举、文件创建、比较并设置等场景。

但线性一致性很贵,尤其在跨地域复制时。任何要求最新读写都经多数派确认的系统,都要付出网络往返和可用性代价。很多系统看起来“强一致”,但并不一定线性一致;判断时必须看读写路径和故障切换规则。

第二版把 ID 生成器作为理解顺序的例子。单节点自增计数器能给出线性一致顺序,但不是容错的。Snowflake 类方案、Lamport 时钟、混合逻辑时钟能生成大体有序或因果一致的 ID,但不能自动给出线性一致。逻辑时钟解决的是因果排序,不是全局实时顺序。

共识是让多个节点不可反悔地同意某个决定。很多问题都可归约为共识:锁、租约、唯一性约束、共享日志、原子提交、compare-and-set、线性一致计数器。Raft、Paxos 这类算法本质上是带自动故障切换的单主复制:通过多数派确保不会出现脑裂,不会丢已提交写入。

协调服务如 ZooKeeper、etcd 建立在共识上,适合存放小规模关键协调状态,例如 leader 选举、配置、锁和租约。它们不是普通数据库替代品。共识应当用于必须全局决定的地方,而不是给所有业务操作默认加协调。

11. 批处理与流处理:派生数据系统的两种执行方式

批处理处理有界、不可变输入,输出派生数据。它的函数式风格很强:输入确定,代码确定,输出就应确定;失败后可以重跑,调试和恢复相对容易。Unix 管道、MapReduce、Spark、DataFrame API、SQL 引擎都在不同层次上体现这种思想。

批处理的关键操作是排序、分组、连接和 shuffle。分布式文件系统和对象存储提供持久输入输出,调度器负责资源和任务失败,查询优化器把声明式逻辑变成执行计划。常见输出包括 ETL、分析报表、训练数据、机器学习模型和导入到服务系统的派生视图。

流处理处理无界输入。消息代理和事件日志可以看作文件系统的流式等价物。传统消息队列适合任务分发:消息处理完就确认删除,顺序通常不重要。基于日志的消息代理保留消息,消费者记录偏移量,支持重放,更适合维护派生状态。

流的来源可以是用户行为、传感器、金融行情,也可以是数据库变更。把数据库写入视为流,是第二版后半部分的核心转折。通过 CDC 或事件溯源,系统可以把记录系统的变化持续应用到搜索索引、缓存、物化视图、数据仓库和推荐系统。

流处理必须处理时间。事件时间是事实发生时间,处理时间是系统看到它的时间。迟到数据、乱序事件、窗口关闭、流流连接、流表连接、表表连接,都要求系统明确回答:什么时候输出?迟到后是否修正?状态保留多久?

容错方面,批处理可以丢弃失败任务输出后重跑;流处理不能等无限流结束。微批次、检查点、事务性输出、幂等写入和状态快照都是为了实现“效果上恰好一次”。真正要记住的是:exactly-once 不是用户代码真的只执行一次,而是外部可见结果等价于只生效一次。

12. 流式系统的哲学:分拆数据库,用数据流组合系统

第二版第 13 章是全书思想密度最高的部分。它把前面所有技术合成一种架构哲学:没有单个工具适合所有访问模式,所以现代应用必然组合多个数据系统;组合的关键,是让数据从记录系统以可靠、可重放、可审计的方式流向派生系统。

传统集成方式容易出现双写问题。应用同时写数据库和搜索索引,如果两个客户端并发写入,两个系统可能按不同顺序处理,最终永久不一致。解决思路是让一个系统决定写入顺序,然后其他系统按同样顺序派生。这个顺序可以来自复制日志、CDC 或事件溯源日志。

这与分布式事务目标相似,但机制不同。分布式事务用锁和原子提交同步保证一致;基于日志的派生系统用顺序、确定性重试和幂等消费保证最终派生正确。前者时序保证更强,代价更高,故障容易传播;后者异步、松耦合、可重放,更适合异构系统集成。

分拆数据库不是抛弃数据库,而是把数据库内部已有功能外部化。索引、物化视图、复制日志、全文检索,本来就是数据库内部从基础数据派生出的结构。当组织用 Kafka、Flink、Spark、搜索引擎、数据仓库和缓存组合系统时,其实是在组织层面维护一组“外部索引和物化视图”。

读路径和写路径是理解派生数据的关键。没有索引时,写入便宜,读取昂贵;有索引时,写入要更新索引,读取变快;缓存常见查询结果则进一步把工作提前到写路径。架构设计常常就是决定哪些工作在写入时做,哪些工作在读取时做。

端到端数据流把这个想法推到用户设备。传统 Web 客户端无状态,只在请求时读服务器状态。离线优先应用和实时 UI 则在本地维护状态,把服务器变更作为事件流推送到客户端。客户端屏幕也可以被看作远程记录系统的派生视图。这样思考会让同步、离线、冲突解决和订阅更新变成同一个问题。

正确性方面,第二版强调端到端原则。不要只相信某个组件声称自己可靠,而要能从输入事件、处理步骤、输出状态一路验证完整性。事件日志、幂等操作 ID、可重放处理、异步约束检查、审计和校验,可以在不把所有操作都放进全局同步事务的情况下,提供强完整性。

这给出一个务实结论:不是所有约束都值得同步协调。对不可补偿、外部副作用大、违反后代价极高的操作,需要强协调。对可补偿、可检测、可修复的操作,可以异步检查并在发现问题后修正。系统设计不是把道歉数量降到零,而是在不一致风险和不可用风险之间找到业务可接受的平衡。

13. 数据伦理:数据系统的输出会进入现实世界

第二版最后一章提醒读者:数据系统不是中立管道。预测分析、个性化、风控、推荐、广告和监控都会影响人。

预测系统可能放大偏见。如果训练数据包含历史歧视,模型可能学习并自动化这种歧视。系统做出的决定越影响贷款、招聘、保险、教育、司法、医疗等现实机会,责任问题就越重要。用户必须有申诉、解释和纠错机制,否则“算法决定”会变成无法挑战的权力。

反馈回路会让系统影响它观察的世界。推荐系统改变用户行为,风控系统改变攻击者策略,预测性警务可能让某些地区被更多监控,从而产生更多记录,再反过来证明模型判断。数据不是自然事实的完整镜像,它是被收集机制、业务目标和社会结构塑造的。

隐私不是“用户同意了就没事”。用户通常不知道数据会如何组合、转售、推断和长期保存。数据泄露也不是小概率边角问题;系统保存越多敏感数据,攻击收益越高,泄露后的伤害越难撤回。

工程师的责任不是替社会决定所有价值问题,但也不能假装只是在写存储引擎和 API。数据最小化、用途限制、可删除性、可审计性、权限边界、加密、匿名化、差分隐私、人工复核和申诉机制,都是系统设计的一部分。

14. 核心概念表

概念精炼解释常见误区
记录系统保存事实权威版本的系统把缓存、索引、报表当作同等级事实源
派生数据从记录系统计算出来的视图认为派生数据必须同步更新才正确
尾延迟高分位响应时间支配体验只看平均响应时间
LSM-tree追加写、后台合并的日志结构索引只记住写快,忽略压缩和读放大
B-tree就地更新的页式有序索引认为它总比 LSM 慢或过时
向前兼容旧代码能读新数据只做向后兼容就滚动升级
读己之写用户能看到自己刚写的数据把它等同于全局强一致
写偏差并发事务各自写不同对象却破坏不变量以为快照隔离总是安全
线性一致性多副本表现得像单副本原子对象把它和可串行化事务混为一谈
共识多节点对一个决定达成不可反悔的一致把共识服务当普通高吞吐数据库
CDC捕获数据库变更并作为流输出忽略初始快照、顺序和 schema 演化
exactly-once外部可见效果等价于一次以为代码不会重试或执行多次
分拆数据库用日志和数据流把数据库功能外部化以为要用组件拼掉所有数据库
审计检查数据来源、处理和结果完整性只保留日志但不能重放或验证

15. 学习路径建议

如果你是后端工程师,建议按这条路线学习:

  1. 先掌握第 1、2 章:学会把性能、可靠性、可伸缩性和可维护性说清楚。
  2. 再学第 3、4、5 章:理解数据模型、索引、存储引擎和编码演化。
  3. 然后学第 6、7、8 章:把复制、分片和事务连起来看,特别注意一致性异常。
  4. 接着学第 9、10 章:理解为什么分布式协调困难,以及共识到底解决什么。
  5. 最后学第 11、12、13、14 章:把单个数据库视角扩展成组织级数据流、派生数据和社会责任。

如果你只想快速建立系统设计判断力,优先抓住五个问题:

  1. 哪个系统是记录系统?哪些只是派生视图?
  2. 读路径和写路径分别承担了多少工作?
  3. 数据在多个系统之间是否有唯一顺序来源?
  4. 哪些操作必须同步协调,哪些可以异步检查和补偿?
  5. 系统出错后,能否从输入事件重放、验证和修复?

16. 学习检查题

  1. 为什么 OLTP 和 OLAP 的存储布局通常不同?
  2. 为什么“系统可扩展”这个说法必须补充负载维度?
  3. 文档模型在什么场景下比关系模型自然?什么时候会变得痛苦?
  4. LSM-tree 和 B-tree 分别把读写成本放在哪里?
  5. 滚动升级为什么要求向前兼容和向后兼容同时成立?
  6. 异步复制在 leader 故障时可能丢失哪类写入?
  7. 本地二级索引和全局二级索引的读写代价有什么差异?
  8. 快照隔离为什么不能彻底防止写偏差?
  9. 超时为什么不能证明远程节点已经失败?
  10. 线性一致性和可串行化分别约束什么?
  11. 为什么基于日志的消息代理更适合维护派生状态?
  12. exactly-once 为什么更准确地说是 effectively-once?
  13. 双写为什么会让数据库和搜索索引永久不一致?
  14. 为什么事件日志有利于审计和重放?
  15. 哪些业务约束必须强协调?哪些可以异步检查后补偿?

17. 最后总结

DDIA 第二版的核心不是“分布式系统很难”,也不是“用日志解决一切”。它真正教的是一种系统设计姿势:

先明确目标和约束,再选择数据模型;先理解访问模式,再选择索引和存储;先定义一致性需求,再选择复制、分片、事务或共识;先区分记录系统和派生数据,再设计跨系统数据流;最后,不要忘记系统输出会影响真实的人。

如果把全书压缩成一个工程原则,那就是:

数据系统设计不是追求最强保证或最新工具,而是在可解释的权衡中,让数据以正确、可演化、可验证、对人负责的方式流动。