T3 · Onchain Data Engineer
把一条链上散落的字节,变成别人敢拿去做决策的数字。
面向数据工程与分析方向。
本页里的「T3」指这条 Track。课程章节一律写成「第 T17 章」并带链接,两者不是一回事——章节 T3 讲的是一次交易从代码到链上。
这条 Track 适合谁
你写过脚本拉数据,但不敢说它是全的。 循环调 RPC、落进一张表、跑通了。可要是有人问「从创世到现在有没有漏块」,你答不上来,也没有办法证明。
你的数字和别人的对不上,而你不知道谁错了。 同一个协议的日交易量,你算出来的和平台上的差 8%。你怀疑是口径问题,但你说不出自己的口径到底是什么——因为它散在三段 SQL 和两个脚本里。
你被重组坑过一次,从此在代码里加了一堆补丁。 补丁能挡住你见过的那一种情况,你也知道它挡不住下一种,但不知道正确的结构应该长什么样。
你会查,但不会建。 给你一个现成的数据平台,你能写出很复杂的查询。让你从 RPC 开始搭一条自己的管道,你不知道第一步该分几层。
分界线是这一句:你交付的不是一堆数据,是一条任何人都能重跑、并且重跑结果逐字节一致的管道。
前置
这条 Track 的前置比多数人预期的长,原因很实在:要把字节解码成语义,你就绕不开合约标准。 图谱里第 T13 章依赖第 T9 章,而第 T16、T17 章又一路依赖回去。
- 第 T1 章
- 第 T3 章
- 第 T4 章
- 第 T7–T9 章
- 第 T13–T15 章
- 第 T16 章
- 第 T17 章
- 第 T25 章
| 必须先读 | 图谱里的理由 |
|---|---|
| 第 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 章这条边值得单独说一句:数据工程不能脱离业务语义。 你要索引一个兑换事件,就得先知道路由、池子、报价各自是什么,否则你连表该怎么建都定不下来。
你会学什么
- RPC
- Scanner
- Indexer
- ETL
- Postgres
- ClickHouse
- DuckDB
- Data Pipeline
八个 topic 分成四组,每组解决一个具体的失败模式。
一、采集层:RPC 与 Scanner —— 解决「不知道自己漏了什么」
这一组要做到的是:任何时刻都能回答「我当前覆盖到哪个高度、中间有没有洞」。
要练的东西包括:多节点回退与一致性比对(两家的同一个高度返回不同结果时你信谁)、批量请求的分页与限速、归档数据的可用范围、以及最重要的那一件——进度必须记在数据后面,不能记在数据前面。先写 checkpoint 再写数据,崩溃一次就永久丢一段。
二、加工层:Indexer 与 ETL —— 解决「重跑一次结果就变了」
核心是分层:原始层保留字节不解码,解码层按 ABI 还原结构,业务层做聚合。分层的全部价值在于让代价最高的那一步只做一次——解码错了只需重跑解码,而不是把全链历史重拉一遍。
配套的是三条硬规则:分区键必须来自区块时间而不是服务器时间;一个分区要么完整存在要么完全不存在;重组回滚要按高度删干净,不能留半截。
三、存储层:关系库、列存与嵌入式分析 —— 解决「选错了引擎,后面全是补丁」
三种引擎在这条管道里扮演不同角色,不是三选一:
| 角色 | 它擅长 | 放什么 |
|---|---|---|
| 关系数据库 | 事务、约束、点查 | 游标与 checkpoint、需要强一致的业务表、对外接口的读模型 |
| 列存数据库 | 大规模扫描与聚合 | 解码后的事件明细、需要按时间范围做统计的宽表 |
| 嵌入式分析引擎 | 零运维地读本地或对象存储上的列式文件 | 探索、回填校验、一次性的口径比对 |
选错的代价是具体的:把事件明细放进关系库,几亿行之后每一次聚合都要人等;把 checkpoint 放进列存,你会发现它没有你需要的那种事务。
四、交付层:Data Pipeline 与对外接口 —— 解决「没人敢用你的数字」
数据工程的产出不是表,是可被追问的数字。这一组训练的是口径管理:每一个指标都要能回答「它是从哪几张表、按什么条件算出来的」「它覆盖的高度区间是多少」「它最后一次更新是什么时候」。
再加上对外接口该有的东西:分页、限流、缓存、以及一个明确的「数据新鲜度」字段。
毕业作品
一条可重放的全量索引管道与一个对外查询接口。
和毕业项目的区别:毕业项目要的是一个完整产品,索引只是其中一层。这条 Track 只要这一层,但「全量」和「可重放」两个词把难度整个换了一个量级——全量意味着你要面对历史上所有的脏数据,可重放意味着你不能靠人工修数据。
别人能不能跑起来。 clone 之后,一条命令起依赖服务,一条命令从指定高度开始索引。README 写清楚需要哪种 RPC(是否需要归档)、全量回填大概要多久、以及在没有归档节点时可以退化到哪个高度。
给一条示例查询,让别人跑完就能看到第一个数字。
同一段高度重跑两次,结果逐字节相同。 这是「可重放」的定义,也是这份作品唯一不能商量的一条。
验收方式:选一个已经索引过的区间,清掉产物重跑,用内容校验和比对。不一致就说明管道里有非确定性——最常见的来源是用了服务器当前时间、用了无序遍历、或者用了随机批次边界。
有一次真实的重组演练,并留下记录。 构造或回放一次分叉,证明你的回滚把那一段数据删干净了,而且补上的新数据是对的。
写清楚你的重组深度假设是多少,以及超过这个深度时会发生什么——这一条属于「你明确不防什么」,要写下来而不是假装不存在。
每个对外指标都有口径文档和覆盖区间。 一个指标至少写四件事:定义、来源表与过滤条件、覆盖的高度区间、最后更新时间。
接口的响应里要带数据新鲜度。用户拿到一个不知道是什么时候的数字,比拿不到更危险。
做过一次交叉验证,并且诚实报告差异。 选一个指标,用另一条独立路径重算一遍——比如从状态直接读,或者用一个公开数据平台的同口径查询对比。
差异不需要是零,但你要能解释每一个百分点从哪来。解释得出来的差异是理解,解释不出来的差异是 bug。
怎样判断自己达标了
因为崩溃随时可能发生在两次写入之间。
先写数据后写 checkpoint:崩溃后 checkpoint 落后于实际数据,重启会重做一段。只要写入是幂等的,重做无害。
先写 checkpoint 后写数据:崩溃后 checkpoint 说「这段已经处理完了」,而数据根本没落地。这一段永远不会被重做,你的库里从此有一个洞,而且没有任何东西会告诉你它存在。
这是同一条规则的两个面:宁可重做,不可跳过。
因为重放的前提是确定性。用写入时间做分区,同一批数据今天跑和明天重跑会落进不同的分区目录,两次运行的产物结构就不一样了,「重跑结果一致」这条直接失效。
更麻烦的是回填:你补一段三个月前的历史,它会全部落进今天的分区,查询按时间裁剪时永远找不到它。
规则一句话:时间戳来自区块头,不来自你的服务器。
日志是合约主动发出的投影,不是事实本身。三类常见偏差:
第一,有些状态变化根本不发事件——直接转账、通过内部调用发生的余额变化、以及一些老合约的非标准实现。只看日志,这部分完全不存在。
第二,事件语义会随合约升级改变。同一个签名,升级前后含义不同,你按一套 ABI 解全部历史就会算错。
第三,交易失败时的日志不会保留,但 Gas 消耗和 nonce 变化是真实发生的。做「用户活跃度」这类指标时,只数成功交易会低估真实的交互次数。
正确做法:日志用于事件流,余额与持仓这类状态量要能从状态侧独立校验一次。
物理分区按日期切(从区块时间推导,统一时区),分区内按区块高度、其次日志序号排序。
这样安排的理由是两边各取所需:人提的问题永远是按时间的,所以分区按日期切才有裁剪效果;而写入、去重、重做的单位是区块高度,所以分区内按高度有序,既能让引擎跳过整段数据,也让「这个分区覆盖哪个高度区间」成为一个精确可查的事实。
再加两个细节:分区粒度不要细到小时,否则小文件的元数据开销会吃掉列存的全部好处;每个分区在 manifest 里登记行数、高度区间、内容校验和与解码器版本。
没有标准答案,检查三件事:
- 你能不能在不通知任何人的情况下安全地改一次口径。答不能,说明你需要版本化的指标定义。
- 你的数据出错时,下游多久会知道。有没有一条自动的一致性检查在跑,而不是等人来报。
- 你休假两周,回填任务失败了,有人能替你重跑吗。这一条考的是文档,不是代码。
常见误区
「先聚合再说,原始数据太占地方」
直接从 RPC 算到业务指标,看起来省了一层存储。代价在你第一次发现解析有误的那天出现:同样一个字段错误,留了原始层只需重跑几十分钟的解码,没留就要把全链历史重新拉一遍,几天起步,还受制于 RPC 服务商。
正确做法:原始层保留未解码的字节。它的存储成本远低于一次全量重拉的代价——这是你为「将来一定会发现自己解错了」买的保险。
「重组是小概率事件,先上线再说」
重组不是异常,是链的正常行为。它的概率在你测试的那几天里可能确实很低,但它造成的不是一次报错,而是一段静默错误的数据:钱包余额算错、指标跳变,而且没有任何告警。
正确做法:重组回滚从第一天就进设计,而不是当补丁加。同时把你的重组深度假设明确写出来,超过这个深度怎么办要有答案。
「口径问题不重要,差几个点而已」
差 8% 的数字和错误的数字没有区别——使用者无法判断这 8% 会不会在下一个场景里变成 80%。而且口径不写下来,这个差异永远无法被追查,只能靠猜。
正确做法:每个指标附一份可执行的定义(一段 SQL 或一段说明),以及一次和独立路径的交叉验证结果。能解释的差异是理解,不能解释的差异是 bug。
「有了实时流,就不需要批量回填了」
实时流和批量回填解决的是不同的问题:流负责延迟,批负责完整性。只有流的系统,一旦消费端掉线或者上游断连,那段数据就永久缺失了。
正确做法:两条路径都保留,并且让它们产生相同的结果。流写入的数据要能被批量重跑覆盖且结果一致——这也是上面验收标准第二条的延伸。第 T18 章讲的可重放,说的就是这件事。
接下来
- 带着自己的管道重读:第 T16 章、第 T17 章、第 T25 章。
- 想让数字能回答业务问题,去读 第 23 章 · 链上数据如何观察真实世界——分析口径的对错,最终由业务语义决定。
- 常见组合:加 Track T4 · DeFi Engineer 让你看得懂自己在索引什么,加 Track T6 · AI × Crypto Engineer 把管道做成 Agent 的数据源,加 Track T2 · Solana / SVM Engineer 扩到非 EVM 的数据形态。
- 跨到非技术侧:Track N1 · Crypto Research & Investment。研究报告里「链上数据必须是你自己取的」这条验收,对你几乎是免费的——而对多数研究员来说,它是最难的一节。
- 想把管道接进完整产品,去毕业项目。