我和 PostgreSQL 的缘分,是从一个前辈的故事开始的。
那是我刚入职不久,坐在工位上听旁边的同事随口聊起一件"不久前发生的事"。聊着聊着我意识到,这件事对他们来说可能只是个已经翻篇的插曲,但对刚进门的我来说,相当于一堂造价不菲的入职培训课。
时间回到 2007 年前后。
公司承接了一个北欧客户的项目——瑞典那边的一家银行,做的是当时还没有什么名字的东西:纯线上的信用消费贷款。放在今天,这种产品满大街都是,但 2007、2008 年的中国,这件事还早了差不多十年。
系统是跑起来了,跑得也还算顺。问题出在轧账上。
轧账是金融系统的日常动作——每天跑完业务,对一遍账,看看进出的钱加起来对不对。正常情况下这件事无聊得像例行公事,没人会特别注意它。直到某一天,账对不上了。
差了一点点。不多,但就是对不上。
这种问题是金融系统里最让人头皮发麻的一类。单笔交易看起来都没问题,日志干干净净,数据库里的记录也没有异常。但账就是差了那么一丁点,像是有什么东西在某个角落里悄悄漏水,找不到口子。
客户那边的态度很直接:来人,现场解决。
于是开发负责人买了机票,飞去了斯德哥尔摩。
我每次想到这个画面都觉得有点意思——一个工程师,拎着电脑,飞了将近十个小时,降落在一个陌生的北欧城市,走进客户的办公室,打开终端,开始查日志。
不是因为系统崩了,不是因为数据丢了,就是因为账差了一点点。
但那"一点点"在银行系统里的分量,和"系统崩了"没什么区别。
现场的排查持续了好几天。
这里有个细节,今天的工程师可能很难体会——整个排查过程里,开发团队对生产环境几乎是零可见度。
没有 Datadog,没有 Grafana,没有分布式追踪,没有任何今天习以为常的可观测性工具。生产环境对开发完全隔离,日志、数据库、服务状态,全部在客户的 DBA 和运维手里。开发想知道线上发生了什么,只能靠对方把信息转述过来。
隔离的原因也不只是技术条件限制。更深的一层是:我们是离岸开发团队。代码写完交付出去,客户那边有自己的运维体系,生产访问权限从来不在开发手里。信任问题,这件事心照不宣,但大家都清楚。
所以那几天排查的画面是这样的:开发负责人坐在斯德哥尔摩的客户办公室里,本地跑着同一套代码,靠 DBA 现场转述数据库里的数据,一点一点往回推。
在黑盒里找漏水的地方。
这样折腾了好几天,终于找到了——
不是事务问题。不是并发问题。不是网络抖动。不是任何听起来高深的分布式系统故障。
是进位规则。
金融计算里,钱不是普通的数字。普通人习惯的"四舍五入"在银行业务里并不总是适用,不同的业务场景有不同的进位要求——有时候要向上取,有时候要截断,有时候要用银行家舍入法(四舍六入五成双)。
这些规则不是技术问题,是业务规则,是行业惯例,是写在合同里的东西。
而系统里,管钱的地方散落在很多个角落,每个地方的工程师各自实现了一套,有人用的是 Java 默认的 double,有人用了 BigDecimal,但舍入模式(RoundingMode)的选择不一致。单笔算的时候差异小到看不见,但日积月累、成千上万笔叠加下来,账就开始漂移。
每一分钱的误差都是合理的,所有误差加在一起,就是对不上的账。
结局不算难看。
现场阶段,DBA 拿到了差异数据,直接在数据库里修正了记录——这是最快的止血方式,也是那个年代金融系统出了问题最常见的处置手段。
回国之后,架构师牵头做了一次全面排查,把系统里所有涉及金额计算的地方找出来,抽出了一个独立的金融计算 lib,把进位规则固化在里面,此后所有的钱都必须经过这个 lib 来算。
这个 lib 大概是那个项目留下来最有价值的东西之一。
我听完这个故事,沉默了一会儿。
震撼来自好几个方向。
一是那个根因——原来"钱"在系统里不是一个数字,它是一套规则。这套规则如果没有人在最开始写清楚,就会藏在代码的各个角落里,等着在某天的轧账时悄悄浮出来。
二是那个排查过程——在一个对开发完全不透明的生产环境里,靠转述、靠猜、靠本地复现,把问题找出来。这件事放在今天,有完善的日志系统、链路追踪、实时监控,可能半天就定位了。但那个年代没有这些,而且就算有,离岸团队也未必有权限用。
不知道生产在发生什么,是那个年代离岸开发的常态。你写的代码飞出去之后,就像放出去的风筝,线在别人手里。
至于 PostgreSQL 在这个故事里扮演了什么角色——它是那套系统的数据库,是那位 DBA 深夜对着改数据的地方,是那个项目的地基。
这是我第一次,以这种方式,记住了这个名字。
