skip to content
Running Otter

精读 DDIA(一):数据系统架构中的权衡

/ 31 min read

DDIA 封面

写在前面

《Designing Data-Intensive Applications》(DDIA)是我这几年回头翻得最多的一本书,正好借它的新版开个「精读 DDIA」系列,一章一篇,边读边记。目标不是复述原文,是把书里的概念和我这几年在数据工程里踩过的坑对起来看——书能讲的是原则,具体场景还是得自己下场。

中文版可以参考 冯若航 Vonng 维护的译本,这篇的封面也是从那里借来的。

这是第一章。它不讲具体技术,而是把后面所有章节会反复用到的概念先交给你。

第 1 章 · 数据系统架构中的权衡

Kleppmann 开篇引了 Thomas Sowell 那句 “There are no solutions; there are only trade-offs.” 我想把它单独拎出来,不是因为它漂亮,而是因为整本书后面都会不断回到它身上——B-Tree 还是 LSM?单主还是多主?快照隔离还是可串行化?答案从来不是”哪个更好”,而是”你愿意为哪件事付代价”。所以这一章不讲具体技术,它在做一件更底层的事:把后面要用到的概念先交给你。

简单说一下数据密集型和计算密集型的区别。前者的难点是存得下、改得动、查得快、不丢数据、不在半夜把 DBA 吵醒;后者的难点是把一个矩阵乘法并行到一万个核上。两种难法完全不同,不要混。

数据系统里的三种人

一家普通的零售公司里,跟数据打交道的大概有三种人:

  1. 后端工程师——写订单、登录、页面刷新。他们用的数据库是”被用坏的”那种,一秒几万次读写,挂一下就上热搜。
  2. 业务分析师(BI)——写 SQL 给管理层出报表:“上个月哪家店卖得最好?""香蕉比去年多卖了多少?“只看、不改。
  3. 数据科学家——找规律、建模型:“买尿布的人是不是也买啤酒?""这笔交易像不像欺诈?“原始数据基本不动,但会派生出大量新的表、特征、嵌入、模型权重。

后两种人做的事统称 analytics(分析)。从 80 年代末开始整个行业就因为”分析师和工程师在抢同一个数据库”不断吵架——分析师的大查询把线上库拖垮过太多次。

于是行业裂出了第一道缝:

  • OLTP(操作型):线上数据库,数据在这里被创造、被修改。
  • OLAP(分析型):只读副本,专门给分析师和数据科学家使用。

Kleppmann 给的那张对比表里我最在意的是一行——“数据代表什么”。OLTP 存的是当前世界长什么样(这个用户此刻的余额),OLAP 存的是世界过去发生了什么(这个用户过去一年每一笔交易)。两种时间观完全不同,后面讲事件溯源、流处理的时候会反复回到这里。

他顺手提了一嘴中间派——real-time analytics(Pinot、Druid、ClickHouse),用 OLAP 的打法嵌在用户产品里,比如 LinkedIn 那个”谁查看了你的资料”。我对 ClickHouse 的观感很好,“分布式 vs 单机”那节还会再提它一次。

数据仓库的来历:一段 40 年的老账

80 年代末大公司开始把分析从线上库搬走,单独搞一个库,叫 data warehouse。原因几条:

  • 一家公司里几十上百个 OLTP 库,数据被锁在各自的筒仓里,想跨系统查一个问题就是所谓的 data silo(数据孤岛)。
  • OLTP 的表结构是为”改一行”优化的,拿去给”聚合一亿行”用非常难受。
  • 分析查询太重,跑起来会把线上拖死。
  • 有些 OLTP 库在隔离网段里,分析师根本连不上。

于是工程师发明了一条管道:ETL——抽(Extract)、变(Transform)、装(Load)。顺序倒过来就是 ELT(先装进去再变形)。ELT 是现在更流行的路数,因为数仓引擎(BigQuery、Snowflake、ClickHouse)自己够强,T 这一步放在仓库里做比放在中间层做更灵活。

这一整件事确立了一个到今天还在用的架构范式:数据有出生地(OLTP),有下游消费地(OLAP),中间要有一条管道把它搬过去。

后来又分出两个变种:

  • HTAP(混合事务/分析):想一个系统全干了。能做到,但多数 HTAP 系统内部还是两套引擎拼起来,并没有真的消灭这道裂缝。
  • Data lake:90 年代的数据仓库是严格关系型、SQL 查的。后来数据科学家反抗——“我要做特征工程、跑 NLP、处理图片,SQL 写不出来。“于是换一种打法:别强制 schema 了,原始文件怎么来就怎么放——Parquet、Avro、JSON、图片、传感器数据一股脑丢进对象存储,爱怎么用怎么用。Kleppmann 引了一个很形象的说法叫寿司原则——raw data is better,原始数据最好,每个消费者自己按需加工,别让上游替大家”预熟”。

再后来管道越来越长,ETL 泛化成 data pipeline,有时候还会反向:分析系统训出的模型再推回线上做个性化推荐,这个叫 reverse ETL。

被 Kleppmann 跳过去的一段:维度建模

前段时间我在一个数据群里看到有人问:“什么是维度建模?为什么要维度建模?“我当时想到的第一件事是——这个问题没法单独答,它的答案全在上面那段历史里。

维度建模(Kimball 那一派)的核心是把分析数据拆成两类表:

  • 事实表(fact table):记录”发生了什么”——每一行是一个事件,比如一笔订单、一次点击、一次库存变化。表很长,字段很窄。
  • 维度表(dimension table):记录”发生在什么上下文里”——商品、客户、门店、时间、促销活动。表不长,字段很宽。

一条查询典型地长这样:从事实表里按条件筛出一批事件,再 join 上若干维度表拿到人类能看懂的标签。这就是 star schema(星型模型)——一张事实表在中间,一圈维度表围着它。如果维度表自己又被进一步范式化拆开,就叫 snowflake schema(雪花模型)

为什么非要长这样?把上面那段历史捋一遍就知道了:

  • OLTP 的表是为”改一行”优化的,字段高度范式化,看懂一条订单可能要 join 七八张表。
  • 分析场景要的是”聚合几亿行”,每多一层 join 就多一层开销。
  • 所以把所有”描述性”的字段推进维度表、把所有”事件性”的字段留在事实表——读事实表时扫一长条,维度表常驻内存做 hash join——聚合才跑得动。

所以维度建模不是谁拍脑袋想出来的”最佳实践”,它是**“OLAP 要对上亿行做聚合” 这个约束在表结构上的投影**。那个群里问问题的人如果知道这段历史,自己就能把答案推出来:因为目的不一样,所以表长得不一样。

这也是我想在这本书的整份笔记里一直坚持的视角——不要把一个概念当作孤立的名词去记,要把它还原回它所处的那个历史约束。名词会过时,约束不会。

记录系统 vs 派生数据

这节是我在第一章里真正停下来划线的地方。前面几节基本是”对,知道,继续”,这个二分法以前没被我清晰地想过。

Kleppmann 的问法很简单:别问”这个数据库是干嘛的”,问”这份数据是原生的还是加工出来的”。第一次写入的地方叫 system of record,是法定原件;从它算出来、索引出来、缓存出来的都叫 derived data,丢了能重建。

听起来像废话。但我把这句话对着现在天天处理的数据管道看——业务库 CDC 进数仓 ODS,再一层层加工到 DWD、DWS、ADS,我负责靠后几层。按 Kleppmann 的定义,除了最上游的业务库,整条链全是派生数据。

现实里不是这样用的。对我这层的团队来说,上游给我的那份表事实上就是”真相”——看到一个指标对不上,第一反应不可能是”回业务库校验”,那要跨两三个团队、每家排期两周,最后可能追到半年前某个字段口径悄悄改了。默认做法是把上游交付的数据直接当记录系统用,出了问题再一层层往上剥,谁在哪层卡住谁去协调。

所以 Kleppmann 那句”派生数据丢了可以从源头重建”我读的时候皱了一下眉。单系统里这话是对的;在跨组织的数据管道里它是句漂亮话——重建的成本不是计算成本,是跨团队协调的成本,而后者经常比数据本身还贵。

我现在做的项目就是从这条缝里长出来的。不指望”出了问题能回溯到源头”,而是在每层交接的地方预先定契约:几点之前必须到、质量指标不达标不许往下游流转、口径变更要提前报备。本质上是承认”派生链上每一层都会被下游当成真相源来用”,然后给这种默认信任关系一个可验证的指标。

云 vs 自建

这一节是我最有感慨的地方,但不是因为 Kleppmann 讲的那个 build or buy 的经济账——他讨论的是”公司要不要上 AWS”,我最近一年想的是另一个维度。

书里的对立面是公有云和自建机房。现实里我在公司面对的第三种东西叫平台团队——大数据集群由内部平台组维护,使用方申请资源、提任务、出问题开工单。它的气质其实更接近云:我对集群没有控制权,想装新组件要排期,故障了只能等,缺功能只能礼貌地问。但它又完全没有云的好处——不自动升级、不按需弹性,版本停在多年前的 Hadoop,只是因为早些年一位大佬把 Spark 引进来、把提交门槛压低,这个集群才勉强扛住今天的数据量。

Kleppmann 在这节列的”云的六条代价”——没控制权、出故障只能等、性能问题不好 debug、供应商可能不维护、被迫迁走、数据边界——几乎一条不少地出现在了平台组身上。这节我读完想在空白处写一句:一个不升级的内部平台,就是一个更糟的云——有云的全部缺点,没云的任何好处。这大概是审计、安全、运维能力这些考量堆出来的结果,我理解它为什么长成这样,但”内部平台 = 低配云”这个等式一旦在脑子里成立,再看架构图就有点不一样了。

反过来那一面也有意思。我自己在个人 VPS 上搞了一套 docker 一键起的 Hadoop 集群,带 Kerberos 认证,完全按”自建”的路数走。这事放在几年前是不可能的——光 Kerberos 那一块就能把一个人劝退。现在能搞成,不是因为我突然变强了,是因为网络上的资料、社区踩过的坑,加上一个随时能喂知识的 AI,把以前需要一队人才覆盖得住的运维面,压到了一个人能 hold 住的程度。

这让我对书里那句”自建划算要看你有没有一队懂 MySQL/Postgres 的运维老手”起了疑心。这话背后的前提是”运维知识是稀缺的、集中在少数资深人手里”。2026 年这个前提变弱了——没消失,变弱了。build or buy 这道题里 build 那一边的系数变了,而 Kleppmann 写这本书的时候这个变化还不明显。

还有一个书里没细分的东西我想记一笔。“云”这个词里其实塞了两种不同的东西:一种是买云主机自己搭服务,租的是机器,控制权基本还在自己手里;另一种是买配好的云产品(BigQuery、Snowflake、托管 Kafka),买的是一个黑盒 API,控制权交出去了。Kleppmann 讲”云的代价”时两种是混在一起谈的,但经济学和风险分布其实很不一样——前一种更像”租了个带空调的机房”,后一种才真正对应他列的那六条。

所以这节我读完的感受不是”该上云还是自建”,而是这两个词本身已经不够用了。今天真正的光谱至少是四档:公有云产品 / 公有云主机 / 公司内部平台 / 个人或小团队自建。每一档的控制权、成本结构、知识门槛都不一样,而且 AI 正在把最后那一档的门槛往下拉。

Kleppmann 那句”别被’上云就是现代化’忽悠”我同意。我想顺手再补一句:也别被”自建太难所以只能用公司平台”忽悠。

云原生:存算分离

云原生这节 Kleppmann 讲了两件事,我只留一件。

跳过去的是”服务是分层搭上去的”——一个云数据库跑在其他云服务之上(比如 Snowflake 的存储全在 S3 里),层层叠叠,每一层只提供一种能力。观察对,但不新鲜,略过。

真正值得记住的是存算分离。传统服务器的磁盘插在机器上,CPU 和存储绑死;云原生的世界里,存储和计算是两个独立的服务,靠网络连起来。计算节点挂了无所谓,重新起一个接上同一块存储;要扩容就多起几个计算节点分担查询。为什么非要这么做?因为云里的本地盘不可靠——实例一换本地盘就没了——所以云原生服务干脆假装本地盘是”临时缓存”,真正的持久存储全部走对象存储或专门的存储服务。

代价是每次 I/O 都变成一次网络调用,而云里的虚拟磁盘故障率比很多人以为的高。这个代价在分析型负载里可以容忍(查询本来就慢,多几十毫秒无所谓),但在 OLTP 里必须靠大量缓存和写优化来掩盖。

记一个词:disaggregation(解耦)。它是 Aurora、Spanner、Snowflake、BigQuery 这一代云数据库的共同基因。

运维的变化:从装机器到算账单

Kleppmann 有一句话很准:容量规划变成了财务规划,性能优化变成了成本优化。过去 DBA 操心磁盘快满了要加盘,现在 SRE 操心这个月账单为什么涨了 30%、哪个查询在烧钱。运维没消失,只是换了形态——“serverless 不等于 no ops”,机器不用管了但要管配额、管成本、管服务间集成。

分布式 vs 单机

Kleppmann 在这一节立场非常鲜明,是整章里我最同意他的一段——他基本就是在说”能单机就别分布式”。我读的时候一直在点头,因为我自己就是单机派。

先说一句暴论:现在很多数据团队对单机的能力边界是完全没有概念的。默认”上规模 = 上分布式”,从来没回头问过”这个负载单机行不行”。我这几年见过太多场景,数据量撑死几十 GB、查询并发个位数,跑在十几台 Hadoop 节点上,理由是”我们一直这么做”。

最让我震撼的一次对比是 ClickHouse 单节点 vs 我们那套几十台节点的 Hive 集群——同一个 SQL、同一份数据拉过去,单机 ClickHouse 从响应时间到资源占用都是吊打。吊打到让人尴尬的程度。那一刻我才真正明白 Kleppmann 引用的 McSherry 那篇 Scalability! But at what COST? 论文在讲什么——那论文里好几个”大规模分布式系统”都被笔记本上的单线程程序干掉了,结论不是”分布式没用”,是”你加的那些节点里有一大半算力被协调和网络开销吃掉了,你付了钱,但没得到你以为自己买到的东西”。

这件事其实和我在上一节抱怨平台组 Hadoop 是同一个故事的两面。那套 Hive 集群不是哪一天被人决定”就用它了”,它是历史债——当年的数据量确实需要分布式,后来硬件涨了、单机引擎涨了、数据量可能也没怎么涨,但集群还在那儿,运维它的平台组在那儿,围绕它的任务、脚本、权限体系、账号、审计链路全都在那儿。尾大不掉。技术上早就该退役的东西,组织上退不掉。

读到 Kleppmann 点名 DuckDB、SQLite、KùzuDB 这几年又火起来那段我是真的高兴。这代表一个趋势在被承认:单机不是一个”不够用”的降级选项,它是一个被长期低估的正牌方案。一台现代服务器内存能到 TB 级,一块 NVMe 固态盘顺序读写能到几个 GB/s(比传统 SATA SSD 快一个量级),CPU 几十个物理核——你把这些资源真的喂满了再说单机不够用也不迟。大多数数据团队连 10% 都没喂满。

顺便:当年 Hadoop 设计时的一个隐含假设是”硬盘太慢所以要把计算搬到数据旁边”,这个假设在 NVMe 时代已经动摇了。跨节点 shuffle 一轮数据的代价,可能比本地读一块 NVMe 还慢。

但我得给自己的立场加一个诚实的限定:我没真正处理过 PB 级的数据。我手上的负载撑死 TB 级,而 TB 级确实是单机还打得动的范围。到了 PB 级之后,协调和网络开销的成本结构可能完全不一样,我没发言权。所以更准确地说,我的立场是”在 TB 这一档,很多团队上分布式是选错了”,而不是”分布式永远是错的”。单机派也得有边界感,不然就成了另一种教条。

Kleppmann 这节如果让我起标题,我会叫它”分布式是手段,不是身份”。很多团队把”我们是分布式的”当成一个身份标签在维护,这比任何具体的技术错误都更难纠正——因为纠正它要承认的不是”这个技术选错了”,而是”我们这几年在维护一个不该维护的东西”。

微服务

我不做微服务,所以这节压到一段。Kleppmann 对微服务的定义很尖:它是一个技术方案,解决的是人的问题——让不同团队可以各自推进工作,不需要互相协调。不是为了让程序更快,是为了让组织架构能独立演化。这就解释了为什么小公司用微服务经常翻车:拆出 20 个服务,还是那 3 个人在维护,纯属自找麻烦。关键要求是微服务之间不能共享数据库——一旦共享,schema 就变成了事实上的 API,改一个字段整条链路都要协调,独立演化就崩了。顺带一提 serverless:它把”按用量计费”搬到了代码执行上,代价是冷启动、运行时限制、供应商锁定加深——Kleppmann 冷冷吐槽了一句,“serverless 也还是跑在 server 上,只是可能每次是不同的一台。“

法律、伦理、被遗忘权——以及脱敏

这是第一章最后一节,特别不”技术”,但 Kleppmann 把它放在这里是对的:数据系统的架构不只被业务决定,也被法律和伦理决定。

他主要讲了两件事。

一是被遗忘权。GDPR、CCPA、EU AI Act 给了用户真实的法律权利——用户可以要求你彻底删除他的数据。Kleppmann 立刻指出一个极其难的工程冲突:很多现代数据系统的地基是”不可变的追加日志”——事件溯源、流处理、CDC、数据湖都依赖这个。既然日志不可变,你怎么删中间一条?更麻烦的是派生数据——那条被遗忘的数据可能已经被拿去训练模型了,模型权重里还藏着它的影子,你怎么”遗忘”?他没给答案,留了个伏笔到第 14 章。

二是 Datensparsamkeit——一个德语词,直译是”数据节俭”:能不存就别存。这和过去十年”大数据万岁,什么都先存下来再说”的思潮完全相反。GDPR 第 5 条原则就是这个:数据只能为明确目的收集,不能挪用、不能超期保存。存储不只是账单上的数字,它还是一个潜在的负债——泄露赔偿、监管罚款、用户安全、声誉损失。

Kleppmann 没专门讲但我想挂进来的一件事:脱敏

被遗忘权讨论的是”这份数据能不能真正删掉”,脱敏讨论的是”这份数据谁能看见”。两者是隐私这个大问题的两端,但日常工程里后者出现的频率远高于前者——真的遇到用户行使被遗忘权的次数不多,脱敏是每天都要做的事。

脱敏在我的工作里是很具体的一组操作:

  • 字段级:身份证号、手机号、邮箱、地址这些 PII 字段,在进入分析层之前必须处理——要么哈希、要么加盐哈希、要么掩码(中间四位打 *)、要么完全丢掉。
  • 角色级:同一张表不同角色看到的字段不一样。数据开发能看所有字段,数据分析师只能看脱敏版,业务方只能看聚合后的视图。这往往靠下游引擎的行列级权限加视图层来实现。
  • 派生链的传染问题一份原始表脱敏了,不等于它派生出去的所有下游都脱敏了。做聚合的时候可能不小心在 group by 里带上了原始字段,训模型的时候可能不小心把原始值当特征喂进去。派生链条越长,漏的概率越大——这和被遗忘权遇到的”派生链传染”其实是同一个结构性问题。

我把这两件事并排放在一起是因为它们指向同一个更深的点:隐私问题一旦和派生数据系统相遇,就不再是”加个过滤器就行”的问题,而是一个需要全链路治理的问题。你不能只在入口处做一次处理就交差——你必须一路跟着数据走。

Kleppmann 这本书应该不会专门讲脱敏(它的气质是谈架构原则,不是谈具体治理工具),但被遗忘权和脱敏共享同一条脊梁骨:对派生数据的跟踪能力。这条脊梁骨正是他在第 14 章留下的那个伏笔真正要讨论的东西,到时候再接着说。


一个数据系统工程师不需要成为法律专家,但要有基本的法律和伦理意识,就像要有基本的分布式系统常识一样——这是 Kleppmann 在第一章结尾的话,也是我想用来结束这一篇的话。下一章见。