最近我留意到一个项目标题,把话说得很直接:Tabsdata – Pub/Sub for Tables to Replace ETL Pipelines。中文理解大致是“面向表的发布订阅,用来替代 ETL 管线”。第一次听到的人通常会冒出一个疑问:表是用来存的,不是用来“订阅”的,事件从哪里来?但如果你在数据团队里做过一段时间,会立刻意识到另一种感受——你可能很少在生产环境里写真正复杂的特征计算,你更多的时间是在修 A 系统和 B 系统之间的同步脚本。
这类项目的真正价值,不在“推送比定时调度快”,而在于把“请求一次数据”改成了“建立一次数据关系”。过去你在做的是排队、拉取、清洗、再写入;现在更接近发布、订阅、校验、消费。这个变化看起来只是模式不同,实际会影响你维护数据管道的方式。我后面聊的内容,不会替 Tabsdata 或同类项目打保票,而是围绕“表作为数据通道”这种设计,拆开它到底想替代什么,又替代不了什么。
1. 当 ETL 变成日常“搬数”,真正的成本就被隐藏了
1.1 你维护的不是管道,而是两套系统之间的“潜规则”
进入数据团队之后,你会慢慢发现,很多叫“ETL 管线”的东西,本质不是复杂的抽取转换加载,而是两套系统之间维持一份“潜规则”:
- 源表字段改名了,同步脚本不知道,下游就拿到空列。
- 源系统深夜扩容,连接数爆了,凌晨调度失败,第二天早上业务才反馈。
- 源端删除了一条记录,目标端的 ODS 层还留着旧数据,因为全量同步任务只做覆盖更新,不做删除。
- 业务表从一个库迁到另一个库,表名不变,但连接串变了,负责同步的人花了两小时才查出来。
这些问题表面上是调度失败、脚本 bug、字段类型不匹配,本质上是一个更底层的问题:你在用“定时抓取”的方式去维护一份需要持续保持一致的数据副本。
这里尤其要提一下 ODS 层。很多数仓里的 ODS 层,本意是“贴源存储”:源系统长什么样,你尽量原样保留。但为了完成这个“贴源”,你会不由自主地写很多清洗、去重、更新策略。这其实不是在加工数据,而是在复述上游数据。Tabsdata 这类项目瞄准的,恰恰就是这个区间:如果表本身可以变成一种发布订阅通道,你就不需要每天写一个“复述型”的同步任务。
1.2 表不是静止的仓库,它也可以成为事件流
传统意义上,我们总把数据库表理解成一个“状态”:当前有哪些行,这些行长什么样。ETL 做的就是把这个状态周期性地复制到另一个地方。但表不是每时每刻都静止的。每次 insert、update、delete,其实都是一次事件;字段值从旧版本变成新版本,也是一次事件。
“表的 Pub/Sub”做的,就是把这些事件组织成一种可以被订阅的结构。发布方不需要关心下游有多少消费者,订阅方也不需要轮询源库。你订阅的是一张表,得到的不是一串无上下文的消息,而是“这张表现在长什么样 + 它后面发生了什么变化”。
换句话说,它把数据从“被动等待你来查”,变成了“主动通知你已经变了”。对于同步型场景,这比定时 ETL 更接近数据变化的真实节奏。
2. “表的 Pub/Sub”到底在讲什么
2.1 发布端:把表当成一个可以订阅的通道
如果用一句话解释表的发布订阅,我会这么说:你不再调用对方接口去拉一份数据,而是先声明“我这张表可以被订阅”,然后由平台负责把这份表的变化分发给已经订阅的消费者。
发布端要解决的事情通常有三件:
- 如何让一张表对外可见,并控制谁能看到。
- 如何捕捉表里的行级变化,而不只是整表快照。
- 如何让新加入的订阅者拿到“完整历史状态 + 后续增量”。
中间那一步是很多方案的关键。它可能是基于数据库日志的解析,也可能依赖表里的更新时间戳、版本号或主键变化。但落到使用者视角,你不需要关心底层是 binlog 还是触发器,你只需要关心“我发布的这张表,别人能不能及时、完整地看到”。
订阅端则更简单也更难。简单在于它不再需要自己写定时任务,难在于它必须处理好重复数据、乱序更新、删除事件和字段变化。订阅者拿到的不是“一次性导出”,而是一段持续的数据流,因此消费程序本身的稳定性,变得和管道一样重要。
2.2 订阅端:拿到的不是一条日志,而是一张可用的表
普通消息队列里,你消费到的是一个事件、一条 JSON、一个字节数组。你还要自己解析字段、处理嵌套结构、维护表结构。表发布订阅想提供的抽象更高一层:你直接拿到一张结构化的表,字段名、类型、主键已经定义好,你只需要对接目标表的写入逻辑。
从工程体验看,这个抽象对数据工程师更友好。因为数据同步任务里最常见的不是“不会写逻辑”,而是“源端字段语义不稳定”。如果每个订阅本身就带 schema,可以提前校验,很多问题会在入口被拦住,而不是等到目标表写入失败以后才暴露。
更关键的是订阅状态。普通 HTTP 拉取常常是“这次拉到什么就是什么”,失败了下次再拉全量;而表订阅通常会维护每个订阅者的消费位置。断点续传、重放、追平滞后,这些能力才决定了它能不能成为生产环境里稳定使用的同步底座。
2.3 它和传统消息队列、CDC 的差异
很多人会想到 Kafka、CDC、Debezium 这一系列方案。它们确实有血缘关系,都是“变化数据捕获”或者“事件驱动”。但差异在于抽象层级:
| 维度 | 传统消息队列 | 表的发布订阅 |
|---|---|---|
| 消息内容 | 字节、JSON、业务事件 | 结构化表、行变更、schema |
| 订阅粒度 | topic / partition | table / dataset |
| 消费状态 | consumer offset | subscriber cursor / 消费位点 |
| 历史访问 | 日志保留窗口内回放 | 快照 + 增量组合 |
| 使用目标 | 解耦服务调用 | 替代表同步、数据分发、部分 ETL |
| 使用者心智 | 面向开发者写事件处理 | 面向数据工程师管理表关系 |
这样说不是为了证明谁取代谁。真实架构里,它们会共存。Tabsdata 这类产品的意义,是把数据复制这件事从“需要自己搭一套 CDC + 消息队列 + 消费程序”变回“我订阅一张表就好”。这个变化会让更多团队愿意把同步任务交给平台,而不是每个人自己维护一套轮子。
3. 能替代哪些 ETL,替代不了哪些 ETL
3.1 适合替代:面向共享数据的“同步型 ETL”
第一类适合被替代的 ETL,是把数据从一个地方复制到另一个地方,几乎不改变业务含义。比如:
- 将业务库里的客户表同步到数仓 ODS 层。
- 将主数据系统里的产品表分发给多个下游应用。
- 将订单表的核心字段复制到数据分析库,供报表查询。
- 将用户表从旧系统迁移到新系统,并保持后续持续同步。
这些任务里,真正消耗时间的是“同步”本身,而不是“转换”。你不需要跨表计算,不需要复杂清洗,只需要保证目标端的数据足够接近源端。表发布订阅天然适合这类场景。
它的优势在于增量触达。过去订单一到凌晨才批量拉一次,现在源库有变化就能订阅到;过去每次全量重跑,现在可以“先快照,再增量”。对下游来说,新鲜度提高了,对源库来说,压力也变小了。
3.2 不适合替代:面向深度计算的“加工型 ETL”
如果任务需要把订单表和用户表关联成宽表,再算出用户生命周期价值,最后写入报表层,那表发布订阅不是直接答案。你可以依靠它同步基础表,但真正的 join、聚合、去重、维度建模、业务规则校验,仍然需要一套可管理的计算过程。
这类“加工型 ETL”背后往往不是简单的表关系,而是多层依赖:ODS 到 DWD,DWD 到 DWS,DWS 到 ADS。每一层都有明确的口径和加工逻辑。表的发布订阅可以把底层 ODS 同步得更干净,但不能替你完成业务口径的定义。
还有一个边界要注意:跨业务库的强一致事务。如果目标是“订单一旦支付成功,订单表和支付流水表必须同时出现在某处,且金额对得上”,你不能只依赖表订阅。因为表订阅解决的是“送达”问题,不解决“分布式事务”问题。最终对账和一致性校验,仍然要由上层控制。
3.3 三句话判断该不该用
我用三句话来做选型判断:
- 如果下游只是想要一份尽可能接近源端的表,而且刷新越及时越好,适合用表订阅。
- 如果下游还需要多张表做复杂关联、聚合、指标口径加工,表订阅只解决前一半,后一半仍要围绕计算层设计。
- 如果业务对“多系统间的一致性和全链路审计”有硬要求,表订阅可以作为数据入口,但不能当作完整答案。
所以更准确地说,它不是“替代所有 ETL”,而是“替代 ETL 里搬运和同步的那一部分”。
4. 把旧管线迁到表订阅:一套渐进替换流程
4.1 第一步:先用小表验证“快照 + 增量”
不要第一天就把核心交易表切到新方案上。我的建议是,先找一张风险低、结构稳定、变更频率可控的小表,例如“产品分类表”或“区域配置表”。把它作为第一个订阅发布给测试消费端,验证三件事:
- 历史快照能否完整到达。
- 新增、修改、删除三种操作能否被正确识别。
- 消费端重复启动后,数据是否能从上次位点继续消费,而不是重复或丢失。
这个阶段要保留原有的同步脚本,不要直接停。两条链路并行跑一两天,比较行数、最大更新时间、关键主键,差异降为零再考虑切换。
4.2 第二步:建立契约检查,而不是默认相信字段名
表发布订阅最怕的一个问题是:源表字段悄悄变了,下游却没有任何感知。为了避免这个坑,你在发布方和订阅方之间最好显式约定一个“表契约”,至少要包含:
- 表名、schema 版本号。
- 主键字段。
- 期望的字段列表和类型。
- 变更操作类型:insert、update、delete,还是全部允许。
- 数据保留周期和回放策略。
一份示意结构大致是这样:
{ "publisher": "crm.customer", "schema_version": "2026-01-20", "primary_key": ["customer_id"], "allowed_operations": ["INSERT", "UPDATE"], "data_type_policy": "strict" }这不是某个产品的真实配置,只是一个常见写法。关键不是格式,而是“schema 版本”这个概念。一旦版本变化,订阅方可以选择自动适配,也可以选择暂停并人工确认。如果默认自动适配,你就一定要有字段变更监控,否则下游很容易写进一堆错位数据。
4.3 第三步:别把消费位点放在内存里
消费程序需要记录“我已经处理到哪个版本的数据”。实现时,很多人会用一个局部变量保存,看起来够用,一旦程序重启就会出问题。
更好的做法是把消费位点持久化到数据库或平台提供的存储里。每次成功写入目标端后,再推进消费位点。也就是“先落数据,再记进度”。如果目标写入成功,但位点没有推进,恢复后最多重复消费一段数据;如果位点先推进了,但数据写入失败,那恢复后就会漏数据。对比两种结果,前者是可控的,后者是危险的。
4.4 第四步:提前定义失败语义和服务等级
接下来要回答一个问题:消费失败时,你的团队希望它怎么处理?
通常有两种选择:
- 自动重试:适合瞬时网络抖动,但要注意重复数据。
- 进入失败队列或告警:适合需要人工介入的情况,但要避免告警风暴。
大多数生产环境会采用“自动重试 + 达到上限后转入待人工处理”的组合。目标端写入逻辑也必须幂等。最稳妥的更新方式是用业务主键做 upsert,而不是无脑插入。否则一个事件被重试两次,目标表里就会出现主键冲突或重复记录。
5. 落地时最容易翻车的四个边界问题
5.1 Schema 变了,谁最先知道
表发布订阅比传统定时同步更“近实时”,但 schema 变更不会因为消息变快而消失。源端加了一个字段、删了一个字段、改了类型,都会直接影响下游。
最怕的不是变更本身,而是变更没有被感知。建议在发布端把 schema 版本和表数据一起发布,消费端拿到数据后先校验版本。严格模式下,版本不匹配就停止消费;宽松模式下,允许新字段追加,但删除或改类型必须人工审批。
实际落地时,这套流程需要和上游研发团队约定。如果上游说“我加个字段不就行了”,简化版也能用,但你必须明确通知订阅方。这里最容易翻车的是“上游只改了测试环境,却忘了同步生产环境”。
5.2 回放窗口和生命周期
表订阅并不是无限保存所有历史。平台一般会有保存周期或回放窗口,超过窗口的数据可能无法重新消费。你需要提前知道:
- 新订阅者可以回溯多长时间。
- 如果消费端停机超过保留周期,是否会丢失数据。
- 数据量增长后,回放成本会不会超出预期。
当一个消费者长时间宕机,恢复后往往会触发大量历史数据回溯,这可能会压垮消费端和下游数据库。建议在恢复前估算回放数据量,如果积压太大,先考虑“跳过历史、从当前位置继续”,或者先快照再增量,而不是强行追平几个月的数据。
5.3 Delete 事件最容易被忽略
很多同步任务在验证时,只测试了 insert 和 update,没有测试 delete。结果源端删除一条记录后,目标端始终保留旧数据。表面上“数据多了”,实际对业务来说这是脏数据。
在表发布订阅里,delete 也是一种需要明确处理的变更。如果目标端业务上不允许物理删除,你也应该在底层记录一个“删除标记”,而不是完全忽略。否则,下游报表会一直把已取消的订单、已离职的用户算进去。
5.4 权限和行级过滤比想象中重要
发布一张表,不等于把整张表直接开放给所有订阅者。不同消费者,可能只应该看到不同行和不同列。
例如客户表共享给销售分析团队时,可能需要过滤掉某些敏感字段;订单表共享给不同业务线时,可能需要按地区或渠道做行级隔离。如果平台不支持这些能力,你在“数据可用性”和“数据安全”之间会非常被动。
所以,选择这类方案之前,至少要确认一件事:你发布的是整表快照,还是可以按字段、行、规则进行裁剪。所有外部可订阅的数据接口,最终都会变成一种对外暴露面,权限模型不能省。
5.5 问题排查:从发布端往消费端逐层查
真遇到数据对不上的时候,不要先怀疑平台。我的固定排查顺序是:
- 查发布端:源表里到底有没有这条数据?变更是否已经提交?
- 查订阅事件:数据是否真的被平台分发出来了?事件内容是否完整?
- 查消费位点:消费程序处理到哪一条了?有没有积压?
- 查目标写入:更新键是否匹配?字段是否错位?删除是否生效?
- 查最后成功记录:最后一次成功推进位点的时间是什么时候?
大多数“表同步不一致”都不是平台丢了数据,而是某个环节的提交时序理解错了。从源端到目标端,按这条链路一层层看,定位会快很多。
6. 真正的价值不是省掉 ETL,而是让数据在系统之间“讲契约”
6.1 从“搬数据”到“订阅数据”是一层认知变化
过去我们思考数据集成,默认答案往往是“搭一条管道”:它什么时候跑、跑多久、失败了重跑多少次。这些细节很重要,但也会占据太多注意力,我们常常忘了,所有管道最终是在维护同一个东西——表与表之间的关系。
表发布订阅把关系变得显式化:一张表就是发布物,一个下游就是订阅者。它不会消灭所有转换计算,也不会消灭复杂的离线数仓模型。它真正消灭的,是那些只为了“复制数据”而存在的脚本、调度、监控和加班。
这层认知变化会影响工具选型,也会影响数据团队的职责。数据工程师不再只是“管道维护者”,而更像是数据契约的管理者。你关心的是哪张表对谁可用、schema 如何演进、消费端如何保证一致性,而不是今天哪个定时任务又失败了。
6.2 下一步最该先做的一件事
如果你对这个方向感兴趣,最值得做的不是立刻把核心 ETL 全部替换,而是先挑出一张“同步价值高、逻辑简单、变更频繁”的表,验证一遍快照、增量和删除能力。
过程中你会很快确认几件关键事:平台是否支持 schema 演进?消费位点是否可以持久化?失败重试是否可控?目标端写入是否幂等?这些才是决定它能不能长期使用的核心因素,而不是它宣称的“替代 ETL”这个口号。
数据架构没有银弹。Tabsdata 这类想法真正打动我的地方,是它提醒我们:很多所谓 ETL,本不该由每个团队反复发明。把数据变成可订阅的资源,把管道变成关系,是这个方向真正值得长期关注的原因。