产品碳足迹核算落地的一个硬问题:使用次数怎么进模型

起因是一个「留三年」的要求

指标数据要保留三年,因为他们三年复核一次。
一开始我以为这是个存储问题:留三年,就多买点盘,加个归档策略,定期备份。做完之后我甚至觉得这块挺干净的——按期间分区,冷数据下沉,查询走汇总层,没什么花头。
后来发现,「留三年」不是存储要求,是可复现要求。这两个差得很远。

第一版:只存结果

第一版的设计很自然,现在回头看是典型的想当然:
指标快照表:期间 | 指标编码 | 值 | 单位
排放量在写入时就算好了,因子乘完,一条一条落进去。查询快、报表快、接口快,所有人都满意。
当时的想法是:数已经算完了,存结果就够了,谁还要看中间过程。

第一次复核,问题来了

第一次撞上是复核前整理材料。要从三年前的结果往回倒,倒出「这个数怎么来的」。
两个问题,都不是代码错,都是设计错。

第一个:因子更新了。

电力排放因子每年更新一次。三年前算的时候用的是当年的因子,现在系统里查到的因子表只有当前版本——旧版本被覆盖了。
于是同一个期间,重新跑一遍,算出来的数和当年存下的数不一样。差异不大,百分之几,但没人能解释这个百分之几是从哪来的。对方问了一句「是数据变了还是算法变了」,我答不上来。

第二个:计量点被拆过。

一条产线改造,原来一个计量点拆成三个,编码沿用了主表那个。组织架构也调了,原来的车间并进了新的部门。
结果就是:三年前的数挂在旧的计量点上,那个计量点现在属于另一个部门。按当期组织架构统计,同一批数据被算到了不同的地方。同比基数错了,而且错得看不出来——数字看着都很正常。

第二版:把「当时的状态」一起存下来

改法其实很朴素:凡是在计算里用到的、未来会变的东西,都要在结果里留一份副本。

凡是在计算里用到的、未来会变的东西,都要在结果里留一份副本。

改了三张表。改了三张表。
一是因子。排放事实行上直接冗余因子版本和因子值:
排放事实表:读数ID | 因子ID | 因子版本 | 因子值 | 排放量 | 计算时间
因子表自己仍然是版本化的(effective_from / effective_to),但事实行不再依赖它。这条违反了数仓的常规规范——维度表的值不该冗余进事实表。但在核账场景里,事实行必须能独立回答「当时用的是哪个因子、值是多少」,这一点比规范重要。
二是计量点。改成缓慢变化维,加生效区间:
计量点表:编码 | 介质 | 单位 | 归属部门 | 生效起 | 生效止
查历史时按「读数期间落在生效区间内」关联,而不是按当前状态关联。
三是快照。申报口径快照单独一张表,冻结不可变,带一个结果集的哈希:
快照表:申报期 | 指标编码 | 值 | 口径版本 | 锁定时间 | 结果集哈希
哈希的作用不是防篡改,是自证:冻结之后如果有人动了数据,能看出来。
image4

反思:把结果当数据存,是个很普遍的错

这件事之后我留意了一下,发现「只存结果」的现象到处都有:报表存快照但存的是渲染后的数、指标存值但不存口径、日志存了但不存产生它的参数。
共同点是一样的——系统的状态一直在变,而我们要证明的是「过去某一刻的状态」。只存输出,等于把「当时态」丢掉了。
三条经验,写在这里给后来的人省点时间:

一、先问「这个数要能被谁复算」。如果答案是「三年后的另一个人」,那所有会变的输入都得留副本。如果答案是「不需要」,那就别留,别过度设计。
二、口径不是一个词,是一组参数。说「按实测值优先」,到底什么是实测、缺测怎么算、边界含不含运输,这些都要落成字段,而不是留在文档的自然语言里。文档会改,字段改不动。二、口径不是一个词,是一组参数。说「按实测值优先」,到底什么是实测、缺测怎么算、边界含不含运输,这些都要落成字段,而不是留在文档的自然语言里。文档会改,字段改不动。
三、变更本身要留痕。计量点拆分、部门合并、口径升版,这些动作发生时不记录,事后就再也还原不出来。我们现在的做法是每次变更写一条记录,谁、什么时候、改了哪个字段、改前改后。这部分最枯燥,也最省事。三、变更本身要留痕。计量点拆分、部门合并、口径升版,这些动作发生时不记录,事后就再也还原不出来。我们现在的做法是每次变更写一条记录,谁、什么时候、改了哪个字段、改前改后。这部分最枯燥,也最省事。

最后

这套东西做完之后,技术上没有任何新东西。没有分布式,没有流式计算,连索引都没多加几个。
它解决的问题是:三年后有人问「这个数怎么来的」,能在五分钟内答出来,并且答得能对上。
我现在的看法是,这一类系统里,「能算回来」比「算得快」重要。性能问题可以加机器,可复现问题只能靠当初设计的时候多想一层。
本文为个人工作复盘,不含任何商业推广,未涉及任何企业的实际数据与内部信息。

posted @ 2026-11-11 18:39  Ecobuzz  阅读(0)  评论(0)    收藏  举报