Skip to content

第一性原理 ​

标签
方法论
字数
15485 字
阅读时间
58 分钟

第一性原理(first principles)是少数既能当哲学概念、又能当工程动作的方法。它有两层用法:一层是判定「哪条命题够格当起点」,一层是从那个起点把结论重新推一遍。本篇先把「什么算第一原理」的判据立住,再把拆解、重建、交叉验证三步写成可执行的动作,然后用三个案例把整套流程走完整——跨洋往返时间的物理下限、CAP 的推导形态、B+ 树的层数估算。三个案例分别落在物理定律、模型定义和工程估算三种约束类型上,对应第 5 节四类约束里的三类,第四类(信息论或复杂度下限)在 B+ 树那个案例里作为下游出现。

读者顺着一篇读完,应当能对任何一个「所有人都这么做」的结论问出两个问题:这条结论依赖哪条约束,那条约束本身的前提是什么。

1. 定义与出处:从 ἀρχή 到「不可再推导的起点」 ​

古希腊语 ἀρχή(arche,本原)是这个概念的词根。亚里士多德在《物理学》第一卷开篇给出它在认识论上的位置:在每一种成体系的探究里,只要存在第一原理、原因或元素,知识就来自对它们的把握;因为我们认为自己认识一件事,当且仅当我们把握了它的最初原因与最初原理(来源:Aristotle, Physics, I.1, 184a10–21)。

这句话给了两件事:第一原理是「认识一件事」的必要条件;「把握最初原理」与「认识」之间是充要关系。换成今天的说法,一件事若只记住了结论而不知道结论由什么支撑,就不算认识它。这条标准比「知道答案」严格得多,也正是第 4 节判据的源头。

《形而上学》把「最初依据」说得更直接:第一原理是 "the first basis from which a thing is known"(一个事物由之被认识的最初依据)(来源:Aristotle, Metaphysics, 1013a14–15)。这里的限定词是「最初(first)」——它在认识顺序上排在最前,别的东西由它被认识,它不由别的东西被认识。

把两处放在一起读,可以看出它们说的是两件事。《物理学》那处讲的是「认识一件事需要什么」:它把「把握最初原理」列为认识的条件,落点在主体的知识状态上。《形而上学》那处讲的是「一个命题处在什么位置」:它把「最初依据」定义成一条命题在整个推导网络里的坐标,落点在命题之间的关系上。前者回答「我算不算懂这件事」,后者回答「这条命题是不是起点」;本篇用的判据来自后者,下面那条「不能由系统内其它命题推出」的一般定义也是后者的改写版。

两处的差异带来一个直接的工程推论。一条推理链的起点是否正确,不取决于推它的人是否「真的理解了」,而取决于它在推导网络里的位置对不对——这是一个可以被别人复核的性质。第 5 节的三个动作全部服务于这一点:拆解确定位置,重建连出网络,交叉验证检查这个网络与既有结论是否冲突。

日常使用的一般定义更短:一个基本的命题或假设,不能由该系统中的任何其他命题或假设推导出来。 这句话可以拆成三个部分,每一部分都在限定范围。

  • 「基本的」:它不再被别的命题支撑。若一条命题能从其它命题推出来,它在这套体系里的身份是定理,推导它的那几条才是更靠前的起点。位置一旦确定,这条命题的强度也就定了——定理可以被换掉一条上游而保持文字不变,第一原理不能。
  • 「命题或假设」:它不必先被证明为真。第一原理的作用是充当推导的出发点,而不承担「已证事实」这个身份。假设被写进公理集之后,整套体系是在「若它成立,则……」的意义上被建立起来的。用一条未证命题当起点,得到的是条件结论,不是无条件真理。
  • 「该系统中的」:这是限定词里最容易被读漏的一个。第一原理永远是相对某个体系而言的。同一条命题在一个体系里是公理,在另一个体系里可能是需要证明的定理。

第三个限定词的后果值得展开。欧几里得《几何原本》把几百条几何命题组织成一条链:链的起点是若干定义、五条公设与五条公理,其余命题全部从这些起点推出。在这套体系里,公设充当第一原理,「三角形内角和等于两直角」这类命题是它的下游。换到另一套几何体系里,同一条命题的身份可以翻转。判断一条命题是不是第一原理,必须连着问一句「在哪个体系里」——脱离体系问这个问题,问不出确定答案。

严格性上还有一个常被忽略的落点:第一原理的性质长在表述体系上,不长在客观世界上。「不可再推导」是符号系统里的一个位置属性,它描述的是这条命题与其它命题之间的推导关系,不是这条命题与物理世界之间的关系。这条区分在后面反复用到:第 8 节的 CAP 把起点放在模型定义上,第 7 节的 RTT 把起点放在物理定律上,两者都是第一原理形态,但「不可再推导」的含义在两个体系里并不相同。

笛卡尔在《哲学原理》(1644)序言里给这套方法配了一个操作化的入口:用方法怀疑(methodic doubt)把所有可以被怀疑的命题先怀疑掉,剩下的不可怀疑的命题留作公理,从它们出发重建知识。「我思故我在」就是这套筛选留下的那个起点。这个入口的价值在于它把「哪个是起点」变成一个可以被执行的动作——逐条检查「这条能不能被怀疑」,得到的就是起点清单。

到这里可以把本篇的使用口径固定下来:

第一性原理思考 = 找到当前问题所在体系里最靠前、且不可再往下推出的那条约束,再从它出发把结论重新推一遍。

这个口径里没有「绝对真理」的位置。它要求的产物是一条能被别人独立走一遍的推理链,而不是一个声称无需证明的断言。第 4 节会把这条边界写成一个可操作的判据,第 5 节把它变成三个动作,第 11 节说明它在什么场合不划算——三节合起来构成本篇的主干。

2. 同一个概念在各领域里的名字 ​

「不能由系统内其它命题推出」这件事,在不同学科里有不同的名字。名字不同带来的是印象不同,实际指向的结构一致:一条被选作起点的命题,加上一套从它出发的推导规则。

数学和形式逻辑里的对应词是公理(axiom)与公设(postulate)。欧几里得《几何原本》把起点分成两组:公设是几何专用的(例如平行公设这类几何断言),公理是各学科通用的(例如等量加等量仍相等这类逻辑断言)。几百条几何命题从这两组起点加定义推出。数学里对第一原理的实际判断标准落在独立性上:一条公理如果可以从其余公理推出,它就不该出现在公理表里。

哲学里与第一原理同义的两个说法是先验(a priori)与自明之理(self-evident truth)。先验强调获取方式——不依赖经验即可知;自明之理强调接受方式——不需要论证就应当被接受。这条与第 4 节的判据有一处张力:「自明」在哲学史上常被当作接受理由,而在工程语境里「我认为它自明」不构成可检验的证据。判定标准落在「能不能从体系内的其它命题推出它」上。

物理学里的对应词是 ab initio(拉丁语「从头」),中文通常译作「从头算」:从既有的基本科学定律出发建立计算,不引入经验模型与参数拟合。典型例子是电子结构计算——在某种近似方法下求解薛定谔方程得到电子结构,而不是拿一批实验数据去反拟合一套参数。分水岭在「参数从哪来」:由基本定律和体系设定决定属于 ab initio,由实验数据拟合出来属于经验模型。第 3 节说明这条分水岭的边界。

三个名字指向同一件事:公理论推导结构,先验论知识来源,ab initio 论计算里有没有拟合参数。混用时它们各自的限定条件常常被丢掉。

3. 严格性是有度的 ​

ab initio 这个名字容易给人一个过强的印象:既然「不引入经验参数」,那它应当是从基本定律算到答案、中间不做任何取舍。实际做起来没有这一档。

原因在计算本身。用基本定律解一个多电子体系的薛定谔方程,精确解不存在解析形式,任何可执行的计算都必须选一套近似方法(密度泛函一类的方法就属于这一层)。近似方法把问题压到可计算的规模,代价是引入新的误差来源。所以 ab initio 排除的东西要写准:它排除的是用实验数据反拟合参数,而保留使用近似。一套 ab initio 计算里,近似方法、基组、截断半径这些选择全都在,它们的合法性来自「不依赖被测体系的实验数据来定标」这条纪律,不来自「没有任何假设」。

这条限定可以推广到全部「回到第一原理」的说法——每种这样的做法都要回答「回到哪一层」:

  • 绝对意义上不可再推导的命题,在工程问题里几乎不存在;
  • 实际存在的是对当前问题足够底层、且可被独立检验的约束;
  • 这个「足够底层」是相对量——同一个问题换一个目标,够用的层会变。

相对性举个具体的:估算跨洋往返时间时,「光在光纤里的传播速度」够底层(第 7 节);设计一台光传输设备时,同一个问题要往下拆到色散与非线性效应那一层。两次的起点不同,因为问题所在的体系不同。

这条边界决定了第 4 节的写法:判定落到「在这套表述体系里,它有没有从别处被推出来过」上,不落在「它是否绝对不可推导」上。

4. 怎么判定一条命题是不是第一原理 ​

判定的主判据只有一条:它能不能由系统内的其它命题推出。 能推出来,它就是定理,不是起点。用这条判据筛一遍,就能把「看起来像第一原理」的候选分成两类:有上游的和没上游的。

光有主判据不够,因为它是一条否定式判据——要证明「推不出来」需要穷尽所有可能的推导路径,而路径空间随体系规模爆炸。实际操作里配一条正向的检验方法。

反例检验:构造一个不违反它的情形。 做法是试着构造一个模型,让系统内其它命题都成立,同时让这条候选命题不成立。两种结果各有含义:

  • 如果构造得出来,这条候选命题就不是第一原理。它独立于其余命题,但独立不等于处于起点位置——它是一个额外的、可被替换的假设。第 7 节的折射率取值就是这一类:换一种光纤材料它变了,但它不是这套推导的起点,起点是「介质中光速等于真空光速除以折射率」这条定律。
  • 如果怎么构造都失败,它才够格当起点。

这套做法在数学里就是独立性证明的标准手法:要证明公理 P 不从其余公理推出,就构造一个模型让其余公理成立而 P 不成立。几何里非欧几何的出现在结构上正是这件事——把平行公设换成它的否定、其余公理不动,得到的体系自洽。这条先例说明反例检验有产出:它能筛掉不合格的候选,也能生成新的体系。

三类「假的第一原理」。 工程语境里被误当成第一原理的命题,绝大多数落在三类里。三类的共同特征是:它们在某一个体系里确实不可推出,但被用到了错误的体系上。

  • 从别处借来的定理。 一条在更大体系里是定理的命题,被搬到子问题里当成起点。子问题里看不到推导它的那几条前提,于是它读起来像起点。判据是往上找一层:在更大的体系里,它有没有上游。有上游,它在这套子体系里就不是第一原理。
  • 当前实现的观测值。 「这套服务的 QPS 上限是 5000」是一条测量结果,会随实现、硬件、负载变化。它不是约束,是观测。判据是问:换掉实现,这个数还在吗?第 10.1 节会给这条一个完整的反例。
  • 行业惯例。 惯例通常是从某个成功系统的做法里总结出来的,它继承了那个系统的全部前提。判据是问:这些前提在当前场景里成立吗?第 6 节会说明惯例与类比推理的关系。

三类的区分办法是看这条命题凭什么成立:凭别处的推导(第一类)、凭一次测量(第二类)、凭一批成功案例(第三类)。三种「凭」都不足以支撑「起点」这个位置,因为它们都指向体系之外,而第一原理的位置由体系内部的关系决定。

诚实的边界:不可再推导很难被证实。 要证实它,需要穷尽所有可能的推导路径,而这件事在实践中拿不到。实际能拿到的是较弱的一条:在现有表述体系里,它没有被推导出来过。 这条弱化带来两个后果,都要接受:

  • 产物形态变了。 第一性原理思考的产出是一条可检验的推理链,不是一份「绝对起点」的清单。链条的价值在于别人能独立走一遍、并指出哪一步不成立;一份「绝对起点」清单没有这个性质,因为它的每一项都不可被检验。
  • 结论地位变了。 从候选起点推出的结论,强度上限是「相对于那条起点成立」。起点被换掉,结论跟着换。第 7 节那条 98 ms 的结论带着「假定直线距离 10,000 km、走石英光纤」这两个前提,前提换掉数就变——这不是缺陷,是这套方法的常态。

把这两条后果接受下来,判据就完整了:

一条命题够不够格当第一原理,看它在当前体系里有没有上游;用反例检验去测「有没有上游」;得到的结论带着「相对某条起点」的限定语。

把判据变成一份可以照着问的清单,检查一条候选起点:

  1. 它的上游在哪? 往更大的体系里找一层,看它有没有被别的命题支撑。找不到上游,进入下一步。
  2. 有没有一个模型让它不成立、而其余命题都成立? 构造得出来,它的身份是独立假设而不是起点;构造不出来,进入下一步。
  3. 它换了实现还在吗? 答「不在」的是观测值(本节三类假起点里的第二类),答「在」的进入下一步。
  4. 它带哪些前提? 把前提逐条写下来。前提写不出来的候选,多半是被概括掉了前提的行业惯例(本节三类里的第三类)。
  5. 它的结论强度是多少? 结论的强度等于起点的强度加上链条的完整度。链条里有断步,结论的强度低于起点。

五个问句的产出物不只是「是/否」,还有一份前提清单。这份清单是后面两个动作的输入:第 5.2 节重建时每一步都要能对回起点,第 5.3 节验证时先看前提在当前场景里成不成立。

「结论带着起点」这条限定在工程上的价值,在决策被复查时才显现。一条带着前提清单的结论,别人可以直接检查前提是否仍然成立——不成立就改前提重算,成立就沿用;一条不带前提的结论(「业界一般这么做」),别人只能整体接受或整体推翻。第 12 节排查线索表里的「换掉一条假设结论剧变」那一行,靠的正是这份清单。

第 5 节把这条判据变成三个动作。

5. 操作步骤:拆解、重建、交叉验证 ​

从约束出发的推理分三步:先拆到不可再分,再从约束往上重建结论,最后拿结论与既有的定律和结论交叉验证。三步的顺序不能换。先重建再验证等于事后找理由,验证标准的松紧会跟着想要的结论走;先验证再拆解则不知道在验证什么,因为验证的对象还没产生。

text
   三步的顺序与各自的失效点
   ┌───────────────────────────────────────────────────────┐
   │ ① 拆解     向下问到不能再问的约束                       │
   │            落在:物理上限 / 规范规定 / 成本 / 复杂度下限 │
   │            ▼ 约束(起点)                              │
   │ ② 重建     每一步都能从约束推出                         │
   │            ▼ 结论(带推理链)                          │
   │ ③ 交叉验证 不得违反任何基本定律                         │
   └───────────────────────────────────────────────────────┘
      ① 失效:停在观测值上 ────────▶ §10.1
      ② 失效:出现「通常」「一般」──▶ 回 ①,§10.2
      ③ 失效:把冲突抹平,不查它 ──▶ §5.3、§12

5.1 拆到不可再分:约束落在哪几类 ​

「不可再分」在工程问题里落到四类约束上。四类之间没有优先级,遇到问题时逐个检查,看这个问题的天花板被哪一类压住。

一、物理上限。 光速、热耗散、内存带宽这类由物理定律给定的量。它们的特征是不随实现水平变化。判据是问「有没有一种物理上可能的实现能突破它」——答不出「有」,它就还在约束的位置上。第 7 节的跨洋往返时间是这一类里最干净的例子:约束是光在光纤里的传播速度,不是交换机的性能。

二、协议或规范的硬性规定。 这类约束来自模型定义或规范条文。要小心区分两种规范:一种是「当前实现的选择」,一种是「模型本身的定义」。前者的例子是某个系统的默认配置,改配置就换掉了;后者在异步网络模型里表现为「消息延迟无上界」这条定义——它不是实现水平,是模型的前提,换掉它换出的是另一个模型,结论也跟着换。第 8 节的 CAP 就落在这一类上:那里的公理是 FLP 与异步模型的定义,不是经验。

三、成本结构。 这类约束来自「做这件事要付出什么」。它的特征是跟着拓扑和距离走:同一次操作,跨机架、跨城、跨洋的代价差着数量级。判据是先量出一次操作的物理开销,再看业务能接受多少次这样的操作。第 7 节给出的「一次跨洋共识往返至少 100 ms」就是这一类约束的量化形式,它直接决定了跨地域强一致该怎么部署。

四、信息论或复杂度下限。 约束来自「表示一个可区分状态至少要多少位」或「在某个计算模型下至少要多少步」。判据是:任何编码都必须给每个可区分状态留出一个不同的表示,位数贴着信息量时,再往下压只能靠概率假设(允许出错或近似)。第 9 节的键长估算属于这一类的下游——键占几个字节决定每页能放多少键。

还有一条停止判据:再往下问会离开这个问题所在的体系。 问「光速为什么是有限的」对估算 RTT 没有帮助,那个问题属于物理学,不属于这次估算。拆解的终点是当前体系里最靠前的那条约束,第 11 节会说明这条判据在什么场合会失效。

5.2 从约束往上重建 ​

重建的纪律一句话:每一步都要能从约束推出。 推不出来,说明中间少了一层,回到第 5.1 节把那一层拆出来。

为了让这条纪律可执行,把每一步写成固定格式:给定 X,由 Y 得到 Z。X 是上一步的结论,Y 是这一步用到的定律或定义,Z 是本步的输出。这样写有三个好处:

  • 「Y 去哪了」这件事无处隐藏。Y 写不出来的那一步就是断点。
  • 每一步都能被独立验证:检查 Y 在当前体系里成立即可,验证者不必重走整条链。
  • 链条可以被别人接手。接手的人从任意一步往下走,都能复现。

重建过程中有几个词是断点的信号:「通常」「一般」「参考某某系统的做法」。这些词出现的位置,就是偷偷引入未声明的约束或类比的位置。它们不是不能用,用了就要显式补一句「这一步借用了 X 系统在 Y 场景下的做法,前提是 Z」。第 10.2 节的错拆例子就是这样暴露出来的。

5.3 交叉验证:冲突点就是最该查的地方 ​

重建完成后的检查有两条。

第一条:结论不得违反任何基本定律。 这条是硬约束。算出的数如果违反守恒、违反信息论下限、违反物理上限,说明重建过程里至少有一处 Y 用错了。

第二条:与既有结论冲突时,冲突点就是最该查的地方。 这里要分清两类冲突:

  • 量级冲突:结论与既有观测差一个数量级。这类冲突多半说明约束不同——两个结论分别从不同的起点推出,先确认两边的起点各是什么。第 7 节的推导给出约 98 ms,实测落在 100–150 ms,量级一致,差额有明确的去处(绕行与排队),两边并不矛盾。
  • 方向冲突:结论与既有观测符号相反,或者与一条公认结论直接矛盾。这类冲突多半说明有一方的推导链断了,而且断点通常就在冲突附近。

两类冲突都指向同一件事:冲突是有价值的信息,不是要抹平的噪声。 想把冲突抹平,通常的做法是给结论加一条模糊的限定语(「在同一条件下」这类),加完之后链条既不能被验证也不能被否定,第 12 节会把这条写成排查线索。

6. 与类比推理的对照 ​

同一条决策可以走两条路。把两条路的流程、成本、失效方式摆在一起,才能判断该走哪一条。

类比推理的流程是三步:先找一个参照物(同类问题的既有做法),再在参照物上改参数(规模、流量、预算),最后把结论写成「参照物的结果 ± 微调」。成本低、见效快,日常工程决策绝大多数走这条。

它的代价写在第三步里:结论继承参照物的全部前提。 参照物的做法之所以成立,是因为它所在场景有一组前提(负载形状、硬件代际、成本约束、团队规模);只照搬做法而不照搬前提,结论在前提不成立的那一天整体作废,从「大概对」直接变成「整条推理没有依据」。

第一性原理的流程是另一条:先把问题拆到约束,再从约束往上重建结论(第 5 节)。成本高在第一步——要先知道这个体系里的约束是什么,而这件事通常需要专门知识,还要花时间把候选约束逐条验证。收益是不必继承参照物的前提,因此可能得到与所有人做法都不同的解。

两条路的差别可以收成一句话:类比推理把「约束」打包进了参照物,所以快;第一性原理把包拆开,所以慢。 打包的代价是前提不可见,拆包的收益是前提可见——可见的前提可以被逐条检查,第 4 节的三类假起点正是靠这个检查筛出来的。

text
   类比推理 与 第一性原理:两条路的成本、上限与失效方式
   ┌──────────────────────┬──────────────────────────┬──────────────────────────┐
   │                      │ 类比推理                   │ 第一性原理                 │
   ├──────────────────────┼──────────────────────────┼──────────────────────────┤
   │ 起点                  │ 参照物(同类既有做法)      │ 约束(体系内最靠前的命题)  │
   │ 中间步骤              │ 在参照物上改参数            │ 每一步都能从约束推出        │
   │ 结论形态              │ 参照物 ± 微调              │ 约束 + 完整推理链           │
   │ 成本                  │ 低(找参照物即可)          │ 高(先要知道约束是什么)     │
   │ 结论上限              │ 参照物的水平               │ 物理 / 规范的上限           │
   │ 前提是否可见          │ 否,打包在参照物里          │ 是,逐条写在推理链上        │
   │ 失效方式              │ 前提不成立 → 整条推理作废    │ 换掉起点 → 结论按新起点重算  │
   └──────────────────────┴──────────────────────────┴──────────────────────────┘
                                                        ▲
                                                        └─ 判据:参照物是否已经失效

对照表里最要紧的两行是「结论上限」和「失效方式」。

结论上限定住了两条路的收益差别。 类比推理给出的是「别人做到多少我们也能做到多少」,上限是参照物的水平;第一性原理给出的是约束允许的极限,可能比所有现行做法都高。反过来也成立:当参照物已经贴着约束,两条路的结论会重合,此时用第一性原理只是多花成本(第 10.4 节)。判断参照物贴没贴着约束,用第 5.1 节那四类检查逐条比一遍。

失效方式定住了两条路的适用场合。 类比推理失效是突发的:前提不成立的那一天,结论整体作废。第一性原理没有这类突变,结论带着起点,换起点就按流程重算。这影响长期维护系统的决策可追溯性——第一性原理留下的是一条链,三年后的人能看出当初为什么这么定;类比留下的是一句「当时参考了 X」,前提变了也没人知道该改哪。

选择判据由此而来:

场合走哪条路理由
日常工程决策、有充足先例类比参照物的前提当前成立,成本最低
结果是「所有人都做不到」第一性原理参照物的上限可能就是错的,要去看约束
成本结构与参照物不同第一性原理参照物继承的前提之一(成本)已经不成立
约束本身是新的第一性原理找不到前提齐全的参照物,只能从约束重建

四行里有三行落在「类比已经失效」上。这不意味着第一性原理更优越;它有一块专属的适用区——参照物不再能代表当前问题的时候,第 11 节把这块区域的边界写完。

7. 案例一:跨洋往返时间的物理下限 ​

第一个案例取物理上限这一类约束,因为它的推理链最短、每一步都能自己算,适合用来演示第 5 节那套流程。目标是一个具体的数:一条跨太平洋的网络链路,往返时间(RTT,round-trip time)的物理下限是多少。

7.1 从距离到往返时间:一条能自己算的推导链 ​

推导按第 5.2 节的格式写,每一步标明「给定 X,由 Y 得到 Z」。读者可以拿计算器跟着走一遍。

第一步,距离。 给定目标是一条跨太平洋链路,假定两地间的直线距离约 10000 km,即 107 m。这是假设,实际光缆路径长于直线距离,这一条在第 7.2 节单独说明。

第二步,传播速度。 给定信号在光纤中传播,由「介质中光速等于真空光速除以折射率」得到 v=c/n,其中 n 是光纤的群折射率(group index)。石英光纤在 1550 nm 附近的群折射率约 1.47(通信单模光纤 ITU-T G.652 的典型值,等价的工程口径是每公里约 4.9 μs 的单程时延)。取 c=3×108 m/s,得到:

v=cn=3×1081.47≈2.04×108 m/s

这一步的两个输入都不是工程水平。真空中光速是物理常数;群折射率描述的是材料的色散性质,同一族光纤在 1310 nm 与 1550 nm 之间也只从约 1.4676 变到 1.4682。这里用的是群折射率而不是相折射率,原因是承载信息的是光脉冲的包络,包络的传播速度由群折射率决定——用错这个量,算出的速度会偏高。

第三步,单程。 给定匀速传播,由 t=s/v 得到:

t单程=1072.04×108≈0.049 s≈49 ms

第四步,往返。 给定往返路径等长,由 tRTT=2×t单程 得到:

tRTT≈2×49 ms=98 ms

到这一步,结论的形态已经清楚了:98 ms 是用「距离 ÷(真空光速 ÷ 群折射率)× 2」算出来的,链条上没有一个可以随工程水平调低的参数。 想让它变小,只有两条路:缩短距离,或者提高传播速度。前者受地理限制;后者的上限是真空中光速,把折射率降到 1(在真空里传),往返也还有约 67 ms。

再做一次敏感性检查,看结论对假设有多敏感。把群折射率从 1.47 换成 1.45(更靠拢真空),速度升到约 2.07×108 m/s,单程约 48.3 ms,往返约 96.7 ms——变化不到 2 ms。把距离从 10000 km 换成 11000 km,单程约 54 ms,往返约 108 ms——变化 10 ms。两条输入里,距离是主导项,这也解释了为什么第 7.2 节把绕行放在差额的第一位。

text
   跨洋 RTT 下限的推导链:每个方框里的量来自哪里
   ┌─────────────────────────┐
   │ ① 距离                  │   假设:直线 10,000 km(= 10^7 m)
   │    10^7 m               │   实际光缆更长 → 只会让结果变大
   └───────────┬─────────────┘
               ▼
   ┌─────────────────────────┐
   │ ② 传播速度  v = c / n   │   材料给定:n ≈ 1.47(石英,1550 nm)
   │    ≈ 2.04 × 10^8 m/s    │   定律:介质中光速 = c / 折射率
   └───────────┬─────────────┘
               ▼
   ┌─────────────────────────┐
   │ ③ 单程  t = 距离 / v    │   定律:匀速运动 t = s / v
   │    ≈ 49 ms              │
   └───────────┬─────────────┘
               ▼
   ┌─────────────────────────┐
   │ ④ 往返  2 × 49 ≈ 98 ms  │   假设:往返路径等长
   └───────────┬─────────────┘
               ▼
   ┌─────────────────────────┐
   │ 实测区间:100 ~ 150 ms  │   ▶ 差额 = 绕行 + 光电转换 + 排队
   │ 结论:98 ms 是下限      │   ▶ 「做到 30 ms」不可能,不是难
   └─────────────────────────┘

7.2 实测区间与多出来的时间 ​

7.2.1 差额的去处与性质 ​

跨洋链路的实测 RTT 普遍落在 100–150 ms 区间(该区间随城市对与光缆绕行长度变化,可用每公里约 4.9 μs 的口径自行折算),比 98 ms 的下限高出一截。差额有明确的去处:

  • 路由绕行。 实际光缆不会沿着直线铺,路径长度大于 10000 km,这一项直接把第一步的假设放大。第 7.1 节的敏感性检查给过它的量级:距离每多 1,000 km,往返多约 10 ms。
  • 沿途的光电转换与排队。 信号经过再生器、交换机、路由器时有处理和排队延迟。这类开销随实现和负载变化,属于可以优化的部分。

关键在差额的性质:这些项是加在下限之上的,它们不替代下限。 98 ms 描述的是一条线,任何实现都在线的上方;优化能压缩的是线与实测之间的那段距离,压不到线以下。这条性质把「优化空间」与「不可达区间」分开了:100 ms 到 98 ms 之间还有 2 ms 可争,98 ms 以下是物理上不适用的区域。

这就是这类结论与类比推理的分界处。类比推理会给出一句「别的团队做到 120 ms,我们也能做到 120 ms 左右」——上限是别人的实现水平。第一性原理给出的是一句「做不到 30 ms」,理由是 30 ms 对应单程 15 ms,按 2.04×108 m/s 反算需要约 3000 km 的直线距离,跨太平洋的物理距离不允许。前者是「暂时做不到」,可以被工程进步推翻;后者是「不可能」,只有换拓扑或换介质才能改。

7.2.2 这条下限在工程里的用法 ​

这条下限在工程里有一个直接用法:它决定了跨地域强一致的物理成本。 一次跨洋共识往返至少要等约 100 ms,意味着一次需要跨洋同步的强一致写,光通信本身就要花掉 100 ms 量级。由此得到一个部署结论:要求强一致的多个副本放在同一地域内(同城多可用区),跨洋的部分交给异步复制。 反过来做——把所有副本集中在一处、让远端用户跨洋访问——每次写都要付一次 100 ms 量级的下限,这个成本由协议加在物理上,不随实现改善。

这也是跨境访问海外站点时「带宽多大都要先等一百多毫秒」的原因:带宽决定后续数据能多快灌满管道,改不了这一段的通信延迟。两者分属不同资源,第 6 节对照表里的「结论上限」一行说的就是这件事。

8. 案例二:CAP 的第一原理形态 ​

第二个案例取规范/模型定义这一类约束。分布式/01-CAP 定理.md 已经写过 CAP 的完整推导,包括 Gilbert 与 Lynch 的反证构造、部分同步模型下的定理 2、以及推论的证明细节,本篇不重复;分布式/分布式事务/01-分布式事务.md 里「用共识视角看 CAP」一节是同一路线的另一种写法。这里只把它的第一原理形态抽出来。

CAP 的推导链是这种形状:

  1. 把 consistency 的定义收窄到线性一致性那一档,于是「读写对象的一致性」在语义上等同于「多个节点对一次操作达成一致」;
  2. 异步系统里放一个可能失效的进程;
  3. 引用 FLP 不可能性结果:异步系统中只要有一个进程可能失效,共识问题就没有同时保证终止与一致的确定性解;
  4. 由 2、3 推出:在异步模型下,不可能同时具备一致性与可用性。

链条的起点是第 1、2 步,而这两步用的都是定义:一致性取哪一档是定义,异步模型里「消息延迟无上界」也是定义。这是它与第 7 节的差别所在——第 7 节的起点是一条物理定律和一个材料常数,这里的起点是模型前提。

这个差别决定了结论的适用范围。 从抽象定义推出的结论对所有满足该定义的实现成立,不依赖任何具体系统的观测:「任何异步分布式读写系统都做不到」这句话,不能被下一个系统推翻。这正是第一原理形态的标志。相对地,一条从「某个系统实测能做到 X」推出的结论,适用范围止于那类系统。第 4 节列出的第一类假起点在这里有一个精确的对照:从「某个系统的行为」推出的结论,对另一个系统的适用性依赖于前提是否一致;CAP 的结论不经过任何系统的行为,所以不存在这个依赖。代价是它也更弱——它说的是「不可能同时」,不回答「该保哪一档、怎么恢复」,这部分工程内容在库内那两篇里。

9. 案例三:B+ 树的层数与容量 ​

第三个案例取工程估算这一类:结论落在一个量级上,而不在「可能不可能」上。问题是一棵 InnoDB 的 B+ 树,三层结构能放多少行数据。

这一节的所有输入都是假设,逐条写明。假设改动,结果按同样的算式重算即可。

假设一:页大小。 InnoDB 默认页大小 16 KB(innodb_page_size 的默认值,来源:MySQL 8.0 Reference Manual, 17.8.1 InnoDB Startup Configuration)。

假设二:可用空间。 扣掉 FIL 头尾与页头这些固定字段约 200 字节,每页可用约 16,200 字节。库内 03-索引与 B+ 树 把页头与页目录一并估成约 1 KB、取 15 KB 可用 —— 差别在有没有把页目录与填充率算进去,键数相差约 8%,两种取法的算式相同。

假设三:主键长度。 主键用 BIGINT,占 8 字节;每条记录在非叶节点里还要一个 6 字节的页指针。合计每个「键值对」约 14 字节。

由此得到非叶节点每页能放的键数:

1150≈1620014

约 1,150 个键。这里取整到 1,150,是为了让后面的乘法好算。

三层树的结构。 根节点一层,中间节点一层,叶子节点一层。非叶节点只存键和指向下一层的页号,叶子节点存实际的行数据。于是:

  • 根节点一页有约 1,150 个键,指向约 1,150 个中间节点页;
  • 每个中间节点页同样有约 1,150 个键,各自指向约 1,150 个叶子页;
  • 叶子页总数 ≈ 1,1502≈1.32×106。

行数。 假设每行占 1 KB,一个 16 KB 的叶子页放约 16 行,则:

1.32×106×16≈2.1×107

约两千万行。

text
   三层 B+ 树的容量是怎么乘出来的(InnoDB,假设见正文)
   ┌───────────────────────────────────────────────────────────┐
   │ 根(1 页)            约 1,150 个键  │                      │
   │          ┌──────────────────────────┴───────────────────┐   │
   │          ▼                                              ▼   │
   │ 中间层(约 1,150 页)  每页约 1,150 个键  │   ...          │
   │          ┌──────────────┴──────────────┐                 │   │
   │          ▼                             ▼                 ▼   │
   │ 叶子层(约 1,150^2 ≈ 1.32 × 10^6 页)                        │
   │     每页 16 KB,按每行 1 KB 计 ≈ 16 行                       │
   │                       │                                     │
   │                       ▼                                     │
   │ 总行数 ≈ 1.32 × 10^6 × 16 ≈ 2.1 × 10^7  ≈ 两千万量级         │
   └───────────────────────────────────────────────────────────┘
       ▲ 根到叶子 3 次页访问 = 3 次 I/O(页不在缓冲池时)

这个估算的形态与第 7 节一致:树的层数不是设计出来的,是从页大小与键长算出来的。 页大小是给定值,键长是给定值,层数出现在根到叶子的路径长度上。想改层数,能调的只有键长(页大小通常不动)。

这也给出「用自增 BIGINT 做主键、而不是 UUID」的一个第一原理理由。UUID 的文本形式是 36 字节,加大 6 字节页指针后每个键值对约 42 字节,每页键数降到约 1620042≈385,只有 1,150 的约三分之一。键数掉到三分之一,同样行数需要的层数就上去了(或每层能覆盖的行数掉下来),而每多一层,根到叶子的路径就多一次页访问。取舍因此变成一个可以算的问题,不再依赖「UUID 性能差」这条经验说法。

库内 03-索引与 B+ 树 讲 B+ 树的结构与页大小的机制细节,本节的估算建立在那篇的机制之上;两篇的可用空间假设不同(见假设二),按各自的算式重算即可。 本条属于第 12 节说的「结论依赖假设」那一类:假设写在正文里,别人可以换掉假设重算。

10. 常见误用 ​

第 5 节的三步各有失效方式,第 4 节列出过三类假起点。落到工程里,误用集中在四种形态。

text
   四种误用的分叉:拆到哪一步出错,结论偏到哪里
   ┌──────────────────────────────────────────────────────────────┐
   │                      拆解(找约束)                           │
   └───────────┬──────────────────────────────────────────────────┘
       ┌───────┼────────────┬─────────────────┐
       ▼       ▼            ▼                 ▼
   ┌───────┐ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐
   │10.1   │ │10.2      │ │10.3          │ │10.4                │
   │停在观 │ │拆到一半  │ │漏掉约束的    │ │结论 = 常识         │
   │测值上 │ │就重建    │ │前提          │ │                    │
   └───┬───┘ └────┬─────┘ └──────┬───────┘ └─────────┬──────────┘
       ▼          ▼              ▼                   ▼
   回 §4.2    回 §5.1       补写「前提」        停机,改走类比

10.1 把经验规律当公理 ​

最常出现的误用是把一条观测值放在约束的位置上。「这套服务的 QPS 上限是 5000」是这类命题的典型。

它是当前实现的观测,不是约束:换掉实现、换机器规格、改连接池配置、改下游依赖,这个数就变。把它当起点往下推,推出来的结论继承了这个会变的数,一旦观测值变化,整条链要重算。更麻烦的是这类结论会伪装成第一性原理的产物——它同样经历了「拆解 → 重建 → 交叉验证」,只是起点取错了位置,外人从结论看不出差别。

判据是一个问句:换掉实现,这个数还在吗? 答「在」的才可能是约束(第 7 节的 49 ms 单程:换任何实现都还在);答「不在」的是观测。观测有用,但它属于「现状度量」,不属于「约束」。正确的位置是往下再问一层:这个数被什么压住?可能是单连接的处理开销、可能是锁竞争、可能是下游的超时。找到那一层,才是这次拆解的起点。

10.2 拆错层 ​

第二种误用是拆到一半就开始重建。链条在下面,起点取在了上面一层,于是整条推理在解一个错的问题。

具体例子:接口变慢,要找出优化方向。有人把「网络延迟」当成不可再分的约束,从它开始重建,得到「换更快的网卡、把机房挪近」这类结论。问题是网络延迟不是这次问题的约束——一次请求要串行调用三个下游服务,每个服务各自有处理时间,延迟的大头在这里,不在单跳网络。

错在拆解的停止点上:「网络延迟」还能再拆一层(拆成调用链上各段的耗时之和),而这一层才是当前问题的体系边界。 停在它上面,重建出来的整条链都在回答一个错的问题——优化网络对总耗时的影响是毫秒级,调用链串行化带来的却是数十毫秒。这个错拆得到的结论看起来合理,只有把实测的分段耗时与推导结果对照,冲突才暴露出来。

判据是第 5.2 节那条信号:重建过程中出现「通常网络是瓶颈」「一般这类接口慢在网络」这类句子,说明起点取在了概括层,需要回到第 5.1 节往下再拆一层。

10.3 忽略「约束本身也有前提」 ​

第三种误用是把约束当成无条件的。第 5.1 节给出的四类约束,每一类都带前提。

第 7 节那条光速下限的前提是「走光纤」。前提换掉,约束就换了:改走卫星链路,几何距离变长、介质变成近真空,n 接近 1,整套数都要重算;改拓扑(两地之间不再需要横跨太平洋),距离这一项直接变。约束没消失,但它换了形态。第 7.2 节给的敏感性检查是处理这类误用的通用动作:把每条假设单独动一下,看结论对哪条最敏感,那条假设的前提就是最该检查的。

判据是每用一条约束就问一句:这条约束在什么前提下成立? 前提一旦不成立,约束的位置要重新找,不能沿用旧数值。这条误用的后果比前两种隐蔽——推导链本身是完整的,只是它的起点在当前场景里不成立,从结论看不出任何异常。

10.4 给显而易见的事背书 ​

第四种误用是拆到底得到的结论和常识一样。「接口慢,拆到约束层,结论是『请求数量要减少』」——这类拆解花了成本,没有产出。

判据在开工前就能问出来:如果结论和常识一致,我准备做什么? 答不上来就不要拆。反过来,当既有的做法已经贴着约束(第 6 节那条对照:参照物的水平等于约束允许的极限),从约束重建只是把已知的事再算一遍。

这条与第 6 节的选择判据配合用:第一性原理的适用区是「类比已经失效」的场合。类比没失效时走类比,成本低,结论一样。

11. 适用边界:什么时候不值得用 ​

第 6 节把适用区划在「类比已经失效」上,这一节把不值得用的场合列全。

可逆的小决策。 决策可以低成本撤回时,先做再改优于先算。第一性原理的成本花在「先要知道约束是什么」上,而这个成本在决策可撤回时不必要——试错的代价更低,拿到的信息也更贴近真实系统。

有充足先例的常规选择。 参照物的前提当前成立,且场景与参照物同构,走类比。这一类占了日常工程决策的绝大多数。

时间窗口紧的场景。 约束的确认需要专门知识,专门知识需要时间。窗口不允许时,用类比给出一个可用的答案,回头再补推导。这条适用于线上故障处置——先按参照做法恢复服务,事后用第 12 节的线索表复盘。

三类合并起来是同一个原因:第一性原理贵在「要先知道该领域的约束」;不知道约束时它会退化成空谈。 空谈的形态很具体——拆解停在概括层(第 10.2 节),或者引入一堆前提不明的「业界一般这么做」(第 4 节第三类假起点)。此时产出的是包装过的类比,成本却按第一性原理计。

适用信号与第 6 节的表格一致:参照物给出的结论是「所有人都做不到」;当前场景的成本结构与参照物不同;约束本身是新的,找不到前提齐全的参照物。三个信号都指向同一件事——参照物不再能代表当前问题,此时第一性原理的高成本才有对应的产出。

12. 排查线索 ​

第 5 节的三步走完之后,结果可能是达标的链条、也可能在某一步露馅。这一节把「现象 → 判据 → 先看什么」做成一张表,再把几条判读原则写完整。

现象判据先看什么
结论与直觉冲突差额是一个数量级还是符号相反量级冲突先查「两边的起点各是什么」;方向冲突先查「哪条链有断点」,断点通常在冲突附近
重建到某一步写不出 Y「给定 X,由 Y 得 Z」里的 Y 缺席说明中间少了一层,回第 5.1 节把那一层拆出来
找不到任何冲突点结论与既有观测、既有定律全都一致通常说明拆得还不够深——起点取得太高,链条太短,还不容易撞上别的结论
结论与常识一样拆解前后认知没有变化停止这次拆解,改走类比(第 10.4 节)
重建中出现「通常」「一般」链条里混进了未声明的类比每一个这样的词都补一句前提,或回第 5.1 节重拆(第 10.2 节)
换掉一条假设结论剧变结论对某条假设高度敏感那条假设的前提就是最该检查的(第 7.1 节的敏感性检查、第 10.3 节)
换个场景结论就不成立结论的成立范围小于预期属于适用范围问题,把场景写进前提,别改链条(第 10.3 节)
前提清单写不出来说不出起点凭什么成立候选多半是行业惯例或观测值,回第 4 节重定起点

三条判读原则把这张表串起来。

第一条:结论与直觉冲突时,要查的是「我假设的那条约束是不是真的」,不是结论。 拆解的起点决定链条方向,起点错了,往下每一步都对,结论照样错。第 7 节的实测区间(100–150 ms)比下限(98 ms)高,这个差额是约束之上加的成本,不是约束错了;反过来,如果实测落到了 50 ms 以下,该怀疑的是距离假设或传播速度假设,而不是「物理定律不适用」——后者不可能被一次测量推翻,前者可以。

第二条:重建的每一步都要能被独立验证。 「给定 X,由 Y 得 Z」的格式就是为这条服务的——验证者不需要重走整条链,只要检查某一步的 Y 在当前体系里是否成立。一个链条的价值与它可被分段验证的程度成正比:能被分段验证的链条,错误会被定位到某一步;不能被分段验证的链条,错误只能被笼统地感知为「这个结论不太对」。

第三条:找不到冲突点,往往说明拆得还不够深。 起点越高,推出的结论越接近常识,越不容易与别的结论冲突。第 10.2 节的例子就是这样:把起点取在「网络延迟」上,推出的结论是「网络要好」,与所有人的认知一致,于是没有任何校验价值。把起点往下推到「调用链上各段耗时之和」,结论才有可能与观测对上或对不上,链条才开始承担判断功能。

三条原则之外,还有一条关于验证顺序的判断。链条写完先做第一条(对照基本定律)还是先做第二条(逐段验证),取决于链条的长度:链条短时逐段验证更快(每步的 Y 都在眼前);链条长时先做定律对照更快,因为一条违反守恒的链不值得逐段查。第 7 节那条四步链属于前者,第 8 节那种引用不可能性结果的链属于后者——先确认「异步模型 + 一个故障进程」这个前提是否真的被用上了,再去核对中间步骤。

量级冲突的具体走法值得单独写一遍,因为它在两类冲突里最容易查错。做法是把两个结论各自的前置条件列出来做差,而不是把两个数放在一起比大小:

  1. 记下我这边的起点与每一步的 Y(第 5.2 节的格式让这一步是现成的);
  2. 找到既有结论的起点。既有结论往往不带起点,此时先按第 4 节那五个问句把它补出来,补不出来的部分标成「未知」;
  3. 两边的起点逐条对照,找出分量最大的一条差异,那是优先检查的对象;
  4. 差异集中在前提上时(例如一边认为走光纤、一边认为走卫星),两个结论都对,问题在适用范围;差异落在中间步骤上时,其中一条链有断点。

第 7 节的例子走完这四步:我这边的起点是「直线 10,000 km + 石英光纤」,接近实测的另一组数往往带着更长的绕行距离,第 3 步找出的最大差异是距离项,与第 7.1 节敏感性检查的结论一致——距离是主导项。两步互相对上,链条可以信。

排查的顺序收成一句:先定起点,再看链条,最后比结论。 顺序反了会陷入「两个数不一样,到底谁对」的循环,因为数不一样的原因可能根本不在这两个数上。把起点放在第一位还有一个副作用:起点清单一旦写下来,后面两层检查都会变快——链条的每一步都能对回起点,结论的差额也能归到某一条前提上。

三条原则指向同一条收尾判断:这次拆解有没有产出一条「可以被别人指出错误位置」的推理链。 有,它就是有效的第一性原理思考;没有,不管结论看起来多正确,这次拆解都只是把常识重述了一遍。

相关 ​

  • 01-CAP 定理 —— 本篇第 8 节讲的「以模型定义作公理」的完整推导在那篇:Gilbert 与 Lynch 的反证构造、部分同步模型下的定理 2、以及推论的证明
  • 01-分布式事务 —— 其中「用共识视角看 CAP」一节是同一路线的另一种写法,把一致性等同到 consensus 上再推
  • 03-索引与 B+ 树 —— 本篇第 9 节的估算建立在它的页大小与节点结构之上;两处若有出入以那篇为准
  • 10-Linux 性能观测 —— 观测值的取法:本篇第 4 节区分「观测」与「约束」,那篇给出观测入口与计数器口径
  • 01-推理性能指标与瓶颈定位 —— 推理场景里同样有「哪些是约束、哪些是当前实现的观测」这条分界,可与第 4 节对照

参考 ​

  • Aristotle. Physics, I.1, 184a10–21.
  • Aristotle. Metaphysics, 1013a14–15.
  • Euclid. Elements.
  • René Descartes. Principia Philosophiae. 1644.
  • ITU-T. Recommendation G.652: Characteristics of a single-mode optical fibre and cable.

贡献者 ​

文件历史 ​