Crypto OS
职业 Track

T3 · Onchain Data Engineer

把一条链上散落的字节,变成别人敢拿去做决策的数字。

面向数据工程与分析方向

本页里的「T3」指这条 Track。课程章节一律写成「第 T17 章」并带链接,两者不是一回事——章节 T3 讲的是一次交易从代码到链上。

这条 Track 适合谁

你写过脚本拉数据,但不敢说它是全的。 循环调 RPC、落进一张表、跑通了。可要是有人问「从创世到现在有没有漏块」,你答不上来,也没有办法证明。

你的数字和别人的对不上,而你不知道谁错了。 同一个协议的日交易量,你算出来的和平台上的差 8%。你怀疑是口径问题,但你说不出自己的口径到底是什么——因为它散在三段 SQL 和两个脚本里。

你被重组坑过一次,从此在代码里加了一堆补丁。 补丁能挡住你见过的那一种情况,你也知道它挡不住下一种,但不知道正确的结构应该长什么样。

你会查,但不会建。 给你一个现成的数据平台,你能写出很复杂的查询。让你从 RPC 开始搭一条自己的管道,你不知道第一步该分几层。

分界线是这一句:你交付的不是一堆数据,是一条任何人都能重跑、并且重跑结果逐字节一致的管道。

前置

这条 Track 的前置比多数人预期的长,原因很实在:要把字节解码成语义,你就绕不开合约标准。 图谱里第 T13 章依赖第 T9 章,而第 T16、T17 章又一路依赖回去。

  1. 第 T1 章
  2. 第 T3 章
  3. 第 T4 章
  4. 第 T7–T9 章
  5. 第 T13–T15 章
  6. 第 T16 章
  7. 第 T17 章
  8. 第 T25 章
依赖关系取自 graph.ts。这条链看着长,但每一环都在解码或幂等上有用
必须先读图谱里的理由
第 T1 章 · Blockchain 数据究竟是什么整条 Track 的语义起点。State 是事实、Event 只是投影,这个区分决定了你的管道分几层
第 T3 章第 T4 章图谱里第 T25 章直接依赖第 T3 章。RPC 的限速、分页、归档范围,是你这条管道的物理上限
第 T7 章第 T9 章不懂 ABI 与标准,你解出来的就只是一串数字,不是「谁给谁转了多少」。第 T9 章那些非标准实现,全都是你解码时会遇到的脏数据
第 T13 章第 T15 章图谱里通往第 T16 章的必经路径。第 T15 章的幂等与限流,在管道里换个名字又会出现一次
第 T16 章 · 数据库与链上数据以事件为事实、以区块高度为版本。这是这条 Track 的建模基础
第 T17 章 · Indexer重组、断点续传、幂等三件事,在这里第一次被正面讲清楚
第 T25 章 · Blockchain Data Infrastructure图谱里它依赖第 T3 章和第 T20 章。三层结构、分区键、manifest,是这条 Track 的正题

第 T25 章依赖第 T20 章这条边值得单独说一句:数据工程不能脱离业务语义。 你要索引一个兑换事件,就得先知道路由、池子、报价各自是什么,否则你连表该怎么建都定不下来。

你会学什么

  1. RPC
  2. Scanner
  3. Indexer
  4. ETL
  5. Postgres
  6. ClickHouse
  7. DuckDB
  8. Data Pipeline

八个 topic 分成四组,每组解决一个具体的失败模式。

一、采集层:RPC 与 Scanner —— 解决「不知道自己漏了什么」

这一组要做到的是:任何时刻都能回答「我当前覆盖到哪个高度、中间有没有洞」。

要练的东西包括:多节点回退与一致性比对(两家的同一个高度返回不同结果时你信谁)、批量请求的分页与限速、归档数据的可用范围、以及最重要的那一件——进度必须记在数据后面,不能记在数据前面。先写 checkpoint 再写数据,崩溃一次就永久丢一段。

二、加工层:Indexer 与 ETL —— 解决「重跑一次结果就变了」

核心是分层:原始层保留字节不解码,解码层按 ABI 还原结构,业务层做聚合。分层的全部价值在于让代价最高的那一步只做一次——解码错了只需重跑解码,而不是把全链历史重拉一遍。

配套的是三条硬规则:分区键必须来自区块时间而不是服务器时间;一个分区要么完整存在要么完全不存在;重组回滚要按高度删干净,不能留半截。

三、存储层:关系库、列存与嵌入式分析 —— 解决「选错了引擎,后面全是补丁」

三种引擎在这条管道里扮演不同角色,不是三选一:

角色它擅长放什么
关系数据库事务、约束、点查游标与 checkpoint、需要强一致的业务表、对外接口的读模型
列存数据库大规模扫描与聚合解码后的事件明细、需要按时间范围做统计的宽表
嵌入式分析引擎零运维地读本地或对象存储上的列式文件探索、回填校验、一次性的口径比对

选错的代价是具体的:把事件明细放进关系库,几亿行之后每一次聚合都要人等;把 checkpoint 放进列存,你会发现它没有你需要的那种事务。

四、交付层:Data Pipeline 与对外接口 —— 解决「没人敢用你的数字」

数据工程的产出不是表,是可被追问的数字。这一组训练的是口径管理:每一个指标都要能回答「它是从哪几张表、按什么条件算出来的」「它覆盖的高度区间是多少」「它最后一次更新是什么时候」。

再加上对外接口该有的东西:分页、限流、缓存、以及一个明确的「数据新鲜度」字段。

毕业作品

一条可重放的全量索引管道与一个对外查询接口。

毕业项目的区别:毕业项目要的是一个完整产品,索引只是其中一层。这条 Track 只要这一层,但「全量」和「可重放」两个词把难度整个换了一个量级——全量意味着你要面对历史上所有的脏数据,可重放意味着你不能靠人工修数据。

别人能不能跑起来。 clone 之后,一条命令起依赖服务,一条命令从指定高度开始索引。README 写清楚需要哪种 RPC(是否需要归档)、全量回填大概要多久、以及在没有归档节点时可以退化到哪个高度。

给一条示例查询,让别人跑完就能看到第一个数字。

同一段高度重跑两次,结果逐字节相同。 这是「可重放」的定义,也是这份作品唯一不能商量的一条。

验收方式:选一个已经索引过的区间,清掉产物重跑,用内容校验和比对。不一致就说明管道里有非确定性——最常见的来源是用了服务器当前时间、用了无序遍历、或者用了随机批次边界。

有一次真实的重组演练,并留下记录。 构造或回放一次分叉,证明你的回滚把那一段数据删干净了,而且补上的新数据是对的。

写清楚你的重组深度假设是多少,以及超过这个深度时会发生什么——这一条属于「你明确不防什么」,要写下来而不是假装不存在。

每个对外指标都有口径文档和覆盖区间。 一个指标至少写四件事:定义、来源表与过滤条件、覆盖的高度区间、最后更新时间。

接口的响应里要带数据新鲜度。用户拿到一个不知道是什么时候的数字,比拿不到更危险。

做过一次交叉验证,并且诚实报告差异。 选一个指标,用另一条独立路径重算一遍——比如从状态直接读,或者用一个公开数据平台的同口径查询对比。

差异不需要是零,但你要能解释每一个百分点从哪来。解释得出来的差异是理解,解释不出来的差异是 bug。

怎样判断自己达标了

常见误区

「先聚合再说,原始数据太占地方」

直接从 RPC 算到业务指标,看起来省了一层存储。代价在你第一次发现解析有误的那天出现:同样一个字段错误,留了原始层只需重跑几十分钟的解码,没留就要把全链历史重新拉一遍,几天起步,还受制于 RPC 服务商。

正确做法:原始层保留未解码的字节。它的存储成本远低于一次全量重拉的代价——这是你为「将来一定会发现自己解错了」买的保险。

「重组是小概率事件,先上线再说」

重组不是异常,是链的正常行为。它的概率在你测试的那几天里可能确实很低,但它造成的不是一次报错,而是一段静默错误的数据:钱包余额算错、指标跳变,而且没有任何告警。

正确做法:重组回滚从第一天就进设计,而不是当补丁加。同时把你的重组深度假设明确写出来,超过这个深度怎么办要有答案。

「口径问题不重要,差几个点而已」

差 8% 的数字和错误的数字没有区别——使用者无法判断这 8% 会不会在下一个场景里变成 80%。而且口径不写下来,这个差异永远无法被追查,只能靠猜。

正确做法:每个指标附一份可执行的定义(一段 SQL 或一段说明),以及一次和独立路径的交叉验证结果。能解释的差异是理解,不能解释的差异是 bug。

「有了实时流,就不需要批量回填了」

实时流和批量回填解决的是不同的问题:流负责延迟,批负责完整性。只有流的系统,一旦消费端掉线或者上游断连,那段数据就永久缺失了。

正确做法:两条路径都保留,并且让它们产生相同的结果。流写入的数据要能被批量重跑覆盖且结果一致——这也是上面验收标准第二条的延伸。第 T18 章讲的可重放,说的就是这件事。

接下来

本页目录