实时数据管道的三块积木:Pipe、Table Stream、Dynamic Table

构建实时数据管道,云器 Lakehouse 给了三个对象:Pipe、Table Stream、Dynamic Table。它们常被当成"三选一",其实大多数时候不是替代关系,而是各管一段、拼成一条管道。先认清每块积木干什么、怎么拼,比纠结"选哪个"更重要——真正需要选的只有一处。


一条主干,加一个可选旁路

数据怎么流,先记住一条主干,再把 Table Stream 当作一个可选旁路叠上去——别把它们都塞进一条线里看。

主干(常见路径):外部数据先用 Pipe(或

COPY INTO
COPY INTO
/ Studio 实时同步)进仓、变成 ODS 表 → 用 Dynamic Table 声明式加工成 DWD/DWS 结果表 → 结果被 BI、即席查询消费。这条线是大多数实时管道的默认形态:进仓、加工、查询,一路声明式,引擎自动维护。每一段的细节在后文展开。

旁路:Table Stream —— 给任意一张表装一个"变更水龙头"。 它不是主干上的某一步,而是一个按需叠加的能力:在主干上任意一张表(ODS、或结果表——动态表也行,已实测)挂一个 Table Stream,由任务消费它的增量。出路有三类:

  1. 回灌湖内
    MERGE
    MERGE
    进另一张湖内表——做下一层增量加工、审计留存完整变更历史、或维护 SCD Type 2 拉链表。这一路也正是"不想用 Dynamic Table、要自己写 MERGE"时的选择(详见下文"唯一要选的地方")。
  2. 送出湖外(reverse ETL):同步到检索(Elasticsearch)、OLAP 服务层(ClickHouse / Doris)、低延迟缓存(Redis)、业务库(MySQL / PostgreSQL)、消息队列(Kafka 再分发)。
  3. 触发动作:告警、风控,或驱动下游业务系统。

先有表,DT 和 Stream 才能上场

Dynamic Table 和 Table Stream 都只处理已经是表的数据,它们自己不负责把外部数据弄进来。所以一条实时管道的起点,总是先用某种导入手段把外部数据变成一张表。云器 Lakehouse 进仓的方式有好几种:

  • 持续、自动接住不断产生的数据(Kafka、对象存储里的新文件)→ Pipe
  • 一次性 / 批量导入 →
    COPY INTO
    COPY INTO
  • 从外部数据库做 CDC 同步 → Studio 实时同步

注意一个容易混的点:对象存储里的文件虽然在"湖"(Volume)里,但还不是表,Dynamic Table 和 Table Stream 都碰不了它;得先用上面任一种方式把它导入成表,后两者才能接手。

本文讲的是持续实时场景,所以管道起点用 Pipe;它和

COPY INTO
COPY INTO
的取舍见下一节。

Pipe:持续把外部数据导入成表

Pipe 是用 SQL 创建的持续入库对象:把 Kafka 消息、对象存储里不断上传的文件,自动、不间断地写进一张普通表。它本质是把一条

COPY INTO
COPY INTO
包装成常驻、自动调度、自动记录读取位点的微批导入——文件到了就进,无需你定时触发。

和一次性

COPY INTO
COPY INTO
的区别就是"持续 vs 一次":历史数据一把导入用
COPY INTO
COPY INTO
;数据源源不断、要系统自动接住,用 Pipe。Pipe 靠
load_history
load_history
记录已导入文件去重,同一个文件不会重复进。

Table Stream:表的变更书签

Table Stream 不是一张存数据的表,而是一个**"读到哪了"的书签**:它记住你上次消费到源表的哪个版本,下次只把这之后的变更交给你。

构成心智模型的几条规则:

  • 查询 Stream 会多一个
    __change_type
    __change_type
    列:
    INSERT
    INSERT
    /
    UPDATE_BEFORE
    UPDATE_BEFORE
    /
    UPDATE_AFTER
    UPDATE_AFTER
    /
    DELETE
    DELETE
    。一次
    UPDATE
    UPDATE
    会产生
    UPDATE_BEFORE
    UPDATE_BEFORE
    (旧值)+
    UPDATE_AFTER
    UPDATE_AFTER
    (新值)两行。
  • 只有用 DML 消费才推进位点
    SELECT
    SELECT
    看多少次数据都还在;一旦
    INSERT INTO ... SELECT ... FROM stream
    INSERT INTO ... SELECT ... FROM stream
    MERGE ... USING stream
    MERGE ... USING stream
    成功提交,位点前进,那批变更就消费掉了。事务回滚则位点不动、可重消费——这保证了不重不漏。
  • 一个 Stream 只能被一个消费者完整消费;多个下游各要一份,就各建一个 Stream(Stream 只存位点、不复制数据,多建成本很低)。
  • STANDARD
    STANDARD
    捕获增删改,
    APPEND_ONLY
    APPEND_ONLY
    只捕获插入、更轻量。

Stream 也能建在 Dynamic Table 上:动态表刷新产生的变更同样能被 Stream 捕获——所以前面说的那个"可选水龙头"可以接在结果表上(哪怕它本身是动态表),把加工结果的变更送出去。消费位点、过期(STALE)等细节见 Table Stream

Dynamic Table:声明式自维护的派生表

Dynamic Table 是在仓内做加工的主力:你写一条

SELECT
SELECT
说明目标表"应该等于什么",增量怎么算、撤销怎么减、何时刷新,全部交给引擎自动维护。它专门用来搭 ODS→DWD→DWS 这类加工链路。它怎么设计、有哪几种加工形态,见 动态表设计方法

唯一要"选"的地方:同一份加工,声明还是过程

前面说三者大多是组合关系。真正需要二选一的,只有一处:仓内的加工,用 Dynamic Table,还是 Table Stream + 任务? 两者都能把上游变更加工进目标表、保持新鲜,但方式截然不同:

  • Dynamic Table = 声明结果。 你只写一条
    SELECT
    SELECT
    ,增量、撤销、调度、依赖顺序全由引擎维护。
  • Table Stream + 任务 = 编写过程。 引擎只把"变了哪些行"交给你;
    MERGE
    MERGE
    怎么写、退款怎么减回去、失败怎么重试、怎么不重不漏,你自己写一套过程。

换句话说,Table Stream + 任务,正是 Dynamic Table 替你自动化掉的那套过程的手写版(这套过程的代价,见动态表设计方法开头"不用动态表你要写什么"那段)。

既然声明式这么省,为什么还要过程式?因为有些控制权声明式给不了。出现下面这些需求时,把过程拿回来,用 Table Stream + 任务:

  • 自定义
    MERGE
    MERGE
    / 复合主键 upsert,或一次变更要写多个目标表;
  • 维护 SCD Type 2(拉链表,保留历史版本);
  • 加工里要调存储过程、外部函数(动态表的
    SELECT
    SELECT
    不允许);
  • 要对结果表直接增删改(如 GDPR 删除请求;动态表只读);
  • 要 CRON 精确调度、自定义重试,或亚分钟级且逻辑特殊。

反之,只要目标能用一条标准

SELECT
SELECT
表达、分钟级新鲜度够用,就交给 Dynamic Table——别自己写那套过程。完整的决策树和架构模式见实时数据管道选型指南

各归其位

  • 外部数据进仓:持续接入用 Pipe;一次性 / 批量用
    COPY INTO
    COPY INTO
    ;数据库 CDC 用 Studio 实时同步
  • 仓内派生表保持新鲜、且目标能用一条
    SELECT
    SELECT
    表达 → Dynamic Table
  • 加工需要声明式给不了的控制(自定义 MERGE、SCD 拉链、调存储过程/外部函数、直接改结果、精确调度)→ Table Stream + 任务
  • 捕获某张表的变更、交给任务处理(回灌湖内 / 送出湖外 / 触发动作)→ Table Stream

相关文档

联系我们
预约咨询
微信咨询
电话咨询
邮件咨询