如果一张数据库表能被当成消息订阅源来使用,下游每次拿到的不是“今天重新全量跑一遍”的数据,而是“从上次读完之后发生变化的那几行”,你还会不会坚持用定时 ETL 把数据搬到下一层?这是 Tabsdata 这个项目最让人印象深刻的地方:它把 Pub/Sub 的消息订阅逻辑,套在了“表”这个数据开发最熟悉的资源上,目标直指传统 ETL Pipeline。
我第一次看到“Pub/Sub for Tables to Replace ETL Pipelines”这个定位时,觉得这句话很有吸引力,但也很容易引起误读。因为它不是在说“消息队列能替代数仓”,也不是在说“以后不用做数据清洗了”。它真正想改变的是数据流转方式:从“定时跑批、按批搬表”,变成“表被订阅后持续推送变更”。
这篇文章不是官方文档翻译,也不是产品测评。我会从数据工程日常最常遇到的 ETL、ODS 层、增量同步、批量任务这些概念出发,拆一下这个方向到底在解决什么问题,哪些场景下可以真的减少 ETL 任务,哪些场景下你还是得老老实实把调度和治理补上。
1. 先看懂题目:Pub/Sub for Tables 解决的是表结构数据的实时流转
很多人一看到“Pub/Sub”,会立刻想到 Kafka、RabbitMQ、云厂商的消息队列服务,然后开始纠结“把表变更发到消息队列里不就行了,为什么还要再造一个概念”。
我觉得这种反应挺正常的。真正要理解 Pub/Sub for Tables,得先把它和“CDC + 消息队列”的区别分清楚。
1.1 它不是给消息队列加了一层 SQL 语法
普通消息队列里流动的是一条条消息。消息是事件,不是状态。消息消费之后可以删除,可以重新消费,但消息本身没有“主键冲突”“字段变更”“Schema 演进”这些概念。如果下游拿到的不是一个已经结构化的对象,而是 JSON 文本,那解析、校验、转换的规则还是得靠下游应用自己维护。
Tabsdata 这类方向的思路,是把“表”本身当成一个可订阅资源。上游表里新增一行、更新一行、删除一行,下游可以通过订阅关系实时感知到变化。再进一步,订阅者看到的不再是一堆原始日志,而是一张更接近“数据表形态”的变更流。
这样做的好处是:消费者不需要在每条消息里重新解析字段,不用自己拼主键,不用手工维护“这条更新属于哪张表”的逻辑。表结构、字段类型、主键这些信息被提升为一等公民。
不过这也意味着,它不是一个简单的消息转发器,而是一个需要理解 Schema、主键、变更语义的数据中间层。
1.2 核心是把“表的变化”变成可持续订阅的资源
传统做法里,如果业务库的订单表每 10 分钟有新数据,数仓通常是这样处理的:写一个定时任务,每 10 分钟查一次订单表,把大于上次时间戳的数据拉出来,写入 ODS 层,再跑后续加工。
这个流程里,源表只是“一次性查询的对象”。每次都是新任务、新连接、新 SQL,每次都要自己维护游标、时间水位或自增 ID。
Pub/Sub for Tables 的思路完全反过来:源表从“被查询的静态对象”变成“能够持续产生事件的动态主题”。你可以在表上建立订阅,消费者自己不会反复去源表做全表扫描,而是等待变化事件推送过来。
所以它不是取消了 ETL 的存在,而是把 ETL 中最容易出错、最难维护的“轮询拉数据”这一段换成了“订阅接收变更”。
2. ETL 让人想换掉的点,往往出在 ODS 层
讨论 ETL 时,大家经常提“ETL 的 ODS 层”。如果不做数据仓库,可能不清楚这个词,但只要你在做数据接入,就一定和它打过交道。
ODS 全称是 Operational Data Store,中文常叫操作数据存储或贴源层。它承载的是“离业务原始数据最近”的那一层。以往绝大多数团队会把业务库的数据先同步到 ODS,后续数仓计算再从 ODS 继续往下游加工。
2.1 ODS 层为什么最容易堆任务
问题不是 ODS 这个层应不应该存在,而是很多人把 ODS 层当成“各种临时同步脚本的存放处”。
比如订单表要同步到 ODS,用户表要同步到 ODS,商品表也要同步到 ODS。于是每个表配一个同步任务,每个任务都有自己的调度频率、日志目录、重试策略。表多了以后,任务数量爆炸,互相之间的依赖关系变成一团乱麻。
我见过不少团队的 ODS 层,表面上是分层清晰的数仓架构,实际上每天跑批时都是几十个同步任务排队运行。某个上游接口超时,导致下游任务一起失败;某张源表加了字段,同步任务只覆盖了常用字段,新字段始终没有进入数仓。
这些问题的共同点,不是“清洗逻辑太难”,而是“源到 ODS 的数据传递方式太脆弱”。
2.2 Pub/Sub for Tables 是在改写 ODS 的交付方式
如果把表作为 Pub/Sub 资源,ODS 层仍然可以存在,但它的交付方式会发生变化。
以前是:调度系统驱动任务,任务连接源库执行 SQL,将结果写入 ODS 表。
以后可以是:源表被发布为一个可供订阅的表资源,ODS 层作为其中一个订阅者,持续接收变更,并落到自己的存储里。业务看数、下游加工继续读 ODS 层。
这样带来的第一个好处,是不再需要每张表都单独维护一个“增量时间戳”。你不需要反复比较“上次跑到哪了”,也不需要担心时间字段在源库里没索引导致慢查询。因为变更事件是基于数据库事务日志或等价机制捕获的,而不是业务时间字段猜出来的。
第二个好处,是处理逻辑和同步逻辑分开了。同步逻辑交给订阅关系负责,清洗、去重、聚合这些语义加工继续由 ODS 之后的流程负责。也就是说,ODS 层的接入门槛变低了,但后续数据治理仍然存在。
这里要强调的是,ODS 层不是说能删掉,而是它可能从“大批量生成的表集合”变成“由订阅关系持续维护的数据集合”。如果你只是为了减少 ODS 层任务数量而导流,自己却没有处理好删数、改数、回放这些场景,那问题反而会更严重。
3. 用这个思路落地之前,先梳理数据资产和订阅条件
很多人评估这类方案时,第一步就在看功能清单:支不支持增量、支不支持批量回放、能不能订阅多张表。
这些当然重要。但我觉得更优先的,是先回到自己的表上去盘一遍:哪些表适合被订阅,哪些表本身就不适合。
3.1 不是每一张表都适合用 Pub/Sub 推送
有一类表适合:业务明细表、订单表、流水表、日志表、用户行为表。这类表的特点是新增频繁,少量更新,数据一旦产生就基本固定,下游主要按时间维度消费。
另一类表要小心:配置表、汇总表、价格表、库存表。这类表有时更新很频繁,但一条记录会在一天内被反复修改。如果下游只想看每天最终结果,而订阅系统把每一次修改都推送出去,下游反而会收到大量中间状态。
我一般会用这个标准判断:
- 下游是希望“见到每次变化”,还是只希望“拿到一个尽可能新的结果”。
- 如果希望拿到新结果,那么推送原始变更事件给你,并不比一张刷新后的快照表更方便。
- 如果下游确实需要感知每一次变化,比如事件驱动、审计、实时风控,这才适合往 Pub/Sub 方向发展。
所以第一步不是问“Tabsdata 能不能订阅这张表”,而是问“这张表最值钱的输出形态是什么”。
3.2 订阅前,把四件事写进验收条件
我在做数据同步方案选型时,不太先看界面漂不漂亮,更关注几个基础能力能不能讲清楚:
| 验收维度 | 重点问题 | 说明 |
|---|---|---|
| 主键语义 | 每条变更是否能稳定关联到唯一记录 | 没有主键或主键会变化,推送时很容易重复 |
| 变更捕获方式 | 是基于日志捕获还是基于时间戳轮询 | 日志捕获更完整,但需要源库开启相应能力 |
| 保留策略 | 事件多久后会被清理,能否重新消费 | 订阅服务不是无限存储,要有保留上限 |
| 回放能力 | 从某个历史点位重新跑到最新是否可行 | 缺少回放能力,出问题后只能手工补数据 |
这几个问题不问清楚,后面所有实时性优势都会变成数据处理灾难。比如一个订阅消费者挂掉了 30 分钟,恢复之后能不能从上次消费到的位置继续处理?如果不能,这 30 分钟的变更可能直接丢失;如果能,你需要知道系统保证的是不是“至少一次”,如果允许重复,下游目标表必须做幂等。
另外一个经常被忽略的点,是字段级别 Schema 变更。源表加了字段,订阅端看到的新事件里多了一个属性,旧事件还是旧格式。下游能接受吗?如果没有统一处理,哪怕 Tabsdata 这类平台自动更新了表结构,下游消费逻辑也可能无法平滑适配。
4. 是“替代 ETL”还是“替代某个阶段的 ETL”,两者完全不一样
“Replacement for ETL”这个口号容易让人以为,以后可以不用再设计数据任务了。我接触过几个团队后,发现真正的问题不是“要不要做 ETL”,而是“以前那些 ETL 里,很大一部分根本不是 ETL,而是无脑搬运”。
4.1 适合被替代的:机械同步、字段搬运、固定过滤、基础类型转换
我把这类数据流叫作“搬砖型 ETL”。它们的典型特征是:
- 从 A 库读到 B 库,字段基本不变。
- 只做简单过滤和类型转换。
- 目标表结构几乎和源表一一对应。
- 更新频率随着表数量线性增长。
- 任务失败后,重跑即可,不需要复杂的状态恢复。
这类工作用传统 ETL 调度完全能跑,但没必要。因为每新增一张源表,就要新增一个任务、一套调度配置、一套监控规则。而 Pub/Sub for Tables 把同步抽成“源表发布、目标订阅”,新增一张表时只需要定义订阅关系,剩余工作由平台处理。这种场景下,替换掉的是管道搭建方式,不是数据开发岗位。
4.2 不适合被替代的:复杂 Join、窗口聚合、清洗治理、指标口径、回溯补数
下面这些工作,我目前不认为仅靠表订阅能直接解决:
- 多表关联生成宽表。订阅消息能告诉你订单表和商品表各自发生了什么,但关联逻辑本身需要下游计算。
- 复杂窗口聚合。比如计算过去 7 天每个用户的累计订单金额,这不是单条消息推送能代替的。
- 质量规则校验。比如空值率、重复率、异常值预警。
- 指标口径维护。同一个指标在报表、算法、运营侧可能定义不同,需要专门治理。
- 灾难恢复和回溯补数。如果上游业务库发生一次事故,需要从更早的时间点重算,这时候稳定的批式回放和快照备份仍然是底线。
这里关键区别在于:Tabsdata 这类“Pub/Sub for Tables”替代的是“管道”,也就是表与表之间的数据搬运;而传统 ETL 的“T”和“L”背后,还有大量基于业务语义的计算和判断,这部分很难被一个消息体系彻底取消。
4.3 我建议用一张表看待替代范围
| ETL 环节 | 是否适合被替换 | 原因 |
|---|---|---|
| 源表增量抽取 | 非常适合 | 订阅比轮询的延迟更低、维护成本更低 |
| 字段名映射 | 部分适合 | 需要确认系统能不能在 Schema 层做映射 |
| 简单过滤 | 适合 | 可以做成发布端过滤或订阅端过滤 |
| 增量去重 | 可以 | 前提是系统保证主键和变更顺序一致 |
| 多表 Join | 不适合 | 还需要下游计算引擎处理 |
| 聚合统计 | 不适合 | 实时流和批量跑批各有用处 |
| 数据质量检查 | 不适合 | 订阅只负责送达,不负责证明数据正确 |
| 历史回放 | 要重点验证 | 不同实现差异很大 |
把这张表想清楚,你就不会把“替代 ETL”理解成“消灭数据工程”。它更接近:把 ETL 里不产生业务价值的物理搬移任务交给“表订阅”去做,让工程师把时间留给真正需要思考逻辑的地方。
5. 如果要做概念验证,按什么顺序试
我对第一次用这类方案的项目,最不建议的做法是直接把生产环境最重要的几十张表全部切换过去。更稳妥的流程,是先选一张影响面小、更新频率中等、主键清晰的业务表,从四个线索做 24 到 72 小时的验证。
5.1 第一条线索:单表连续变更能否稳定送达
不要只看演示环境里插入一条、下游立刻收到了。你要做的事情会更苛刻一点:
- 同时验证新增、更新、删除三类操作。
- 连续运行一段足够长的时间,不要只测 10 分钟。
- 在下游订阅端做一个小任务,统计收到的变更数是否等于源表实际变化的次数。
- 观察如果一条记录被连续更新 5 次,下游是收到 5 次事件,还是只收到最新状态。这会影响你后面怎么做聚合。
这里最容易出现的问题是,演示时增量追加很正常,一旦源表做批量更新,订阅事件数量会突然暴涨。上游一条 SQL 更新了 10 万行,如果平台按行生成事件,下游要立刻处理 10 万条消息。你不做好积压预估,整个订阅关系会变成新的瓶颈。
5.2 第二条线索:历史数据和最近变更如何衔接
很多项目刚接入时,目标表里不是完全空的。你需要把存量历史数据先初始化到目标端,然后再开启增量订阅。这个初始化过程中,如果源表还在持续写入,最常见的做法是先做一次快照初始化,再从订阅日志的某个准确位点开始拉取。
业务要求这里的逻辑必须闭环:不能漏掉初始化过程中产生的新变更,也不能因为消费者已经从头读取导致重复加载一遍。
我在第一次跑这种验证时,会在目标表里加一个类似last_updated_at或版本号的字段,记录每条数据最后被更新到的时间。这样,即使某条数据被重复推送,只要重复事件里携带的版本不比当前目标行的版本旧,就可以安全忽略。
5.3 第三条线索:延迟、乱序和重复数据如何处理
严格讲,分布式系统里“完全有序”是一个非常昂贵的能力。你经常会碰到这种情况:
- 记录 A 先更新,后删除,但删除事件先到。
- 两次更新事件乱序到达,目标表最终保留了旧值。
- 下游任务积压时,事件到达顺序与源库真实操作顺序不一致。
评估时要弄清楚,平台提供的是“分区内有序”还是“全局有序”。大多数场景下,按主键分区内有序已经够用了,并不需要全局有序。你需要做的是在目标端实现幂等写入:按主键去重,按版本号或业务时间判断哪一条更新生效。
5.4 第四条线索:失败回放和 Schema 变更谁来负责
概念验证里一定要人为制造一次失败。比如让下游消费者停 10 分钟,再重新启动,看系统能不能恢复处理。
几个需要记录的现象:
- 恢复后是从上次提交的位置继续,还是从头消费?
- 如果失败了 100 条消息,是自动重试还是进入死信队列?谁来检查死信?
- 死信消息最终是人工处理,还是手工丢弃?
同时还得验证源表加字段、改字段类型这些情形。如果平台自动同步了 Schema,你需要确认下游数据库里的字段类型是否按预期变化;如果平台不做 Schema 更新,那你要有一个手工变更流程。
我的判断标准很简单:一个都不能漏,比一个都不能重复容易做到;但如果既不漏又不重复,很难,需要在下游端设计幂等保证。
6. 最终要改的不是“ETL 任务”,而是数据契约
把 Tabsdata 这类方向放在更大的数据工程演进里看,我发现很多人讨论“替代 ETL”时,真正的问题是团队里除了“定时任务”之外,没有一个更清晰的方式去描述“表 A 的数据应该以什么频率、什么质量、什么字段维度给表 B”。
传统 ETL 中的很多问题,不是发生在 SQL 写错了,而是发生在数据生产者和数据消费者之间没有明确约定。源表改了字段,下游不知道;源表删除历史数据,下游还在按快照重复统计;同一个“订单状态”字段,不同消费者理解完全不同。
6.1 订阅关系的本质,是一份被平台执行的数据契约
当表变成可订阅资源时,你实际上是在系统层面声明:“我发布这张表,允许订阅读取这些字段,变更粒度是行级,变更事件里包含这些元数据。”下游收到的不只是一条数据,还有一套可校验的约定。
这对团队最大的价值,是会逼你把很多以前模糊的东西固定下来。比如主键是什么,字段类型是什么,一条数据会被更新多少次,删除事件是否保留。这些以前散落在各个 SQL 脚本里的经验,现在会集中反映在“表发布配置”里。
6.2 数据开发的工作重心会从“维护管道”变成“维护边界”
以前新增一张业务表,第一反应是去调度系统里建一个同步任务,然后写清洗逻辑。如果采用表订阅思路,第一反应会变成:这张表应该发布成什么形态?
这个观念变化很重要。因为它把问题从“我这一段的 SQL 怎么写”提前到了“这张表的消费者到底需要什么”。消费者需要的是一次变化事件,还是一份最新状态,还是历史快照,对应发布方式应该完全不同。明确边界之后,你会发现真正的 ETL 逻辑仍然要写,但可以写在更合适的位置,而不是写在一堆重复的调度管道里。
6.3 真正值得长期保存的能力,是回放、幂等、契约校验和语义层
最后我想说,任何一个工具或平台都很难永远满足所有场景。今天你可能因为一张表换到 Pub/Sub for Tables 的方案减少了任务数量,明天如果系统做不到长时间稳定回放、复杂链路可观测、质量规则可配置,你仍然会回到自己造轮子或者混合使用批流两条路线。
所以与其问“Tabsdata 能不能彻底替代 ETL”,不如问:你团队的数据管道能不能做到“新表接入不需要改几十行同步代码,表结构变更不需要靠人工通知,消费逻辑能不能在做本地重构时仍然复用”。
踩过几轮之后我的感受是,Pub/Sub for Tables 最值得关注的地方,不是“实时推送”这个炫技点,而是它把数据生产和数据消费之间的约束,从文档和口头约定,变成了系统可执行的订阅关系。至于跑批调度、复杂加工、指标治理到底要不要保留,那要看你的表后面接的是报表、算法,还是另一个核心业务系统。把每种表最合适的交付方式想清楚,再落地也不迟。