【实时数仓·3】Flink多表JOIN状态爆炸——Event Time Temporal JOIN + TTL分层治理

这是【实时数仓】系列第3篇。上篇讲CDC到Doris乱序覆盖,这篇讲Flink多表JOIN状态爆炸。

周日下午接到电话,出库包裹的实时任务告警了。

打开Flink Web UI一看:Checkpoint size 12GB,状态还在涨。RocksDB的write stall已经触发,任务处理延迟从秒级飙到分钟级。

这个任务关联了10张MySQL CDC源表------shipment_package、package_trajectory、warehouse、owner、carrier...最后写一张Doris宽表。跑了两个月,状态从几百MB涨到12GB。

一上来我先给所有表加了TTL 10天,结果第二天产品就投诉物流状态没更新。排查发现轨迹类数据15天才到,10天TTL早就把状态清了。

改成30天,轨迹数据是保住了,但状态开始疯涨------从几百MB一路飙到12GB。

TTL太小丢数据,太大爆内存。两头堵。


两头堵:TTL太小丢数据,太大爆内存

TTL = 10天:轨迹丢了

业务反馈一个包裹的物流状态不对------MySQL里已经是"签收",Doris里还是"已发货"。

排查发现:这条轨迹的update_time距离包裹创建已经15天。TTL设的10天,状态早就被清理了。Flink收到这条轨迹时,找不到之前的状态上下文,当成新数据处理。

TTL = 30天:状态爆了

改成30天后,轨迹数据是保住了,但Checkpoint size开始疯涨。两个月没重启,状态从几百MB涨到12GB。RocksDB的write stall触发,任务处理延迟从秒级飙到分钟级。

问题本质:

TTL设置 轨迹数据 内存
10天 15天到的轨迹丢失 正常
30天 保住 爆炸

TTL不是解法,是两头堵。


根因:Regular JOIN的状态爆炸

TTL两头堵的根因,是Regular JOIN的状态管理机制

官方文档原文:"For streaming queries, the grammar of regular joins is the most flexible...However, this operation has important operational implications: it requires to keep both sides of the join input in Flink state forever."

官方文档原文:"You can provide a query configuration with an appropriate state time-to-live (TTL) to prevent excessive state size."

Regular JOIN的工作方式:

  • 左表来一条 → 查右表状态 → 关联 → 输出
  • 右表来一条 → 查左表状态 → 关联 → 输出
  • 两边的数据都要永久保存在状态里,直到设置TTL
  • 10表JOIN = 10个中间状态,状态可能比原始数据大很多倍

我的任务:10张表JOIN,状态从几百MB涨到12GB,两个月没重启过。TTL是补救措施,不是根本解法。


分层治理方案

Flink有两种JOIN方式,适合不同场景:

JOIN类型 适用场景 状态大小 我的表
Regular JOIN 通用,两边都要存状态 大(无界) 大部分表
Event Time Temporal JOIN 维度表,取watermark时刻的版本(存since watermark的所有版本) 小(有界) warehouse, owner

我的10张表,按JOIN方式分两层:

复制代码
第一层:Event Time Temporal JOIN(2张维度表,状态由watermark控制)
  LEFT JOIN warehouse FOR SYSTEM_TIME AS OF a.update_time d
  LEFT JOIN owner FOR SYSTEM_TIME AS OF a.update_time e

第二层:Regular JOIN + TTL(8张表,靠TTL控制状态上限)
  LEFT JOIN package_trajectory_ttl_view b ON b.package_id = a.id
  LEFT JOIN shipment_package_carrier c ON c.package_id = a.id
  LEFT JOIN data_dict f ON a.tenant_id = cast(f.tenant_id as bigint) AND d.code = f.dict_code
  LEFT JOIN package_trajectory_detail_ttl_view g ON b.id = g.package_trajectory_id
  LEFT JOIN shipment_package_statistics h ON a.id = h.package_id
  LEFT JOIN abnormal_package i ON a.id = i.package_id
  LEFT JOIN shipment_package_consignee j ON a.id = j.package_id

第一层:Event Time Temporal JOIN解决维度表状态

warehouse和owner是维度表,变化频率低。用Regular JOIN会存所有历史版本,用Event Time Temporal JOIN取watermark时刻的版本。

官方文档原文:"Event Time temporal joins allow joining against a versioned table. This means a table can be enriched with changing metadata and retrieve its value at a certain point in the time."

官方文档原文:"The versioned table will store all versions - identified by time - since the last watermark."

官方文档原文:"As time passes, no longer needed versions of the record will be removed from the state."

Event Time Temporal JOIN与Regular JOIN的本质区别:

Regular JOIN Event Time Temporal JOIN
状态范围 两边永久保存全部历史(无界) 存since watermark的所有版本(有界)
状态清理 全靠TTL Flink按watermark自动清理
极端情况 TTL设大仍会爆 watermark停滞时仍会积累,建议TTL兜底
sql 复制代码
-- 之前:Regular JOIN,两边永久保存状态
LEFT JOIN warehouse d
  ON a.tenant_id = cast(d.tenant_id as bigint) AND a.warehouse_id = d.id

-- 之后:Event Time Temporal JOIN,取watermark时刻的版本
LEFT JOIN warehouse FOR SYSTEM_TIME AS OF a.update_time d
  ON a.warehouse_id = d.id AND a.tenant_id = cast(d.tenant_id as bigint)

官方文档原文:"Flink uses the SQL syntax of FOR SYSTEM_TIME AS OF to perform this operation from the SQL:2011 standard."

官方文档:https://nightlies.apache.org/flink/flink-docs-release-1.19/docs/dev/table/sql/queries/joins/

前提条件(官方要求):

  • 维度表是Versioned Table = PRIMARY KEY + WATERMARK + changelog source(CDC天然支持)✅
  • 主表有Event Time属性(WATERMARK)✅
  • join条件包含右表的PRIMARY KEY ✅
sql 复制代码
-- 主表加WATERMARK(Event Time属性)
CREATE TABLE shipment_package(
  ...
  update_time timestamp(3),
  WATERMARK FOR update_time AS update_time - INTERVAL '10' SECOND,
  PRIMARY KEY (id) NOT ENFORCED
)

-- 维度表加WATERMARK(Versioned Table)
CREATE TABLE warehouse(
  ...
  update_time timestamp(3),
  WATERMARK FOR update_time AS update_time - INTERVAL '10' SECOND,
  PRIMARY KEY (id) NOT ENFORCED
)

Event Time vs Processing Time:

Event Time Temporal Join Processing Time Temporal Join
语法 FOR SYSTEM_TIME AS OF table1.rowtime FOR SYSTEM_TIME AS OF table1.proctime
Flink 1.19 ✅ 支持 ❌ 不支持("not support yet")
取什么版本 数据变更时刻的版本(历史版本) Flink处理时刻的最新版本
状态存储 存since watermark的所有版本 ---
替代方案 无(不需要) LATERAL TABLE(Rates(o_proctime))

官方文档原文(Processing Time章节):

"Currently, the FOR SYSTEM_TIME AS OF syntax used in temporal join with latest version of any view/table is not support yet ."

"The reason is only the semantic consideration, because the join processing for left stream doesn't wait for the complete snapshot of temporal table, this may mislead users in production environment."

所以我们用Event Time------基于CDC的update_time,join的是"数据变更时刻"的维度版本,不是"Flink处理时刻"的版本。


第二层:Regular JOIN + TTL

其他8张表是事实表或传递关联表,不能用Temporal JOIN,只能用Regular JOIN + TTL。

sql 复制代码
-- 用STATE_TTL hint为每张表单独设TTL(表名或别名均可)
SELECT /*+ STATE_TTL('b'='30d', 'c'='30d') */ *
FROM shipment_package a
LEFT JOIN package_trajectory b ON b.package_id = a.id AND b.deleted = 0
LEFT JOIN carrier c ON c.package_id = a.id
...

官方文档原文:"For stateful computation Regular Join and Group Aggregation, users can use STATE_TTL hint to specify operator-level Idle State Retention Time, which enables the aforementioned operators to have a different TTL against the pipeline level configuration table.exec.state.ttl."

STATE_TTL hint语法要点(官方):

  • key是表名或别名,如'b'='30d''package_trajectory'='30d'
  • value是时间单位:1d/3d/30d
  • 比全局配置table.exec.state.ttl更精细,可以每张表单独设
  • 只对Regular JOIN和Group Aggregation生效

TTL配置: 事实表必须设TTL(用STATE_TTL hint或全局配置)。维度表由Flink按watermark自动清理版本,但建议设置TTL作为兜底(防止watermark停滞)。


优化效果对比

对比项 优化前 优化后
JOIN方式 全部Regular JOIN Event Time Temporal JOIN(2张)+ Regular JOIN(8张)
维度表状态 存两边全部历史(无界) 存since watermark的所有版本(Flink自动清理)
TTL依赖 全靠TTL补救 维度表Flink自动清理+TTL兜底,事实表靠TTL

Event Time Temporal JOIN的限制:

  • 维度表必须有PRIMARY KEY和WATERMARK(Versioned Table)
  • 必须用Event Time(WATERMARK),Processing Time的FOR SYSTEM_TIME AS OF在Flink 1.19不支持
  • join条件必须包含右表的PRIMARY KEY
  • watermark停滞时状态仍会积累,建议设置TTL兜底

Regular JOIN + TTL的限制:

  • TTL太短丢数据,太长爆内存
  • 需要根据业务数据的实际生命周期来设TTL
  • TTL是补救措施,不是根本解法

写在最后

Flink多表JOIN状态爆炸,不是"加TTL"就能解决的问题。

TTL是补救措施,不是根本解法。根本解法是分层治理

  • 维度表用Event Time Temporal JOIN,状态由watermark控制,Flink自动清理不需要的版本
  • 事实表用Regular JOIN + TTL,靠TTL控制状态上限

什么时候该用哪种JOIN:

场景 推荐JOIN类型
维度表关联(warehouse、owner) Event Time Temporal JOIN
事实表关联(package_trajectory等) Regular JOIN + TTL
传递关联(data_dict等) Regular JOIN + TTL
不确定 先用Regular JOIN + TTL,观察状态增长

Event Time Temporal JOIN的限制:

  • 维度表必须有PRIMARY KEY和WATERMARK(Versioned Table)
  • 必须用Event Time(WATERMARK),Processing Time的FOR SYSTEM_TIME AS OF在Flink 1.19不支持
  • join条件必须包含右表的PRIMARY KEY
  • watermark停滞时状态仍会积累,建议设置TTL兜底

TTL配置经验:

  • 维度表:Temporal JOIN由Flink按watermark自动清理版本,建议设置TTL防止极端情况
  • 事实表:TTL根据业务数据的实际生命周期来设,不能一刀切

官方文档:

下一篇讲数据消重------实时数据里的重复问题怎么处理。

相关推荐
INGNIGHT1 小时前
Flink 的三种一致性语义
大数据·flink·linq
湘美书院--湘美谈教育1 小时前
湘美谈教育AI经验集锦:有些东西,它们很难蒸馏
大数据·人工智能·深度学习·机器学习
xiaofj1001 小时前
reglock工作机制
大数据·安全
xixixi777772 小时前
空天地通信、高速光模块、AI 智能体攻击、同态加密芯片四大事件解读:AI 算力底座攻防与全域通信同步升级
大数据·人工智能·深度学习·ai·大模型·光模块·智能体
数据皮皮侠2 小时前
全国消协智慧 315 平台投诉信息数据库
大数据·人工智能·算法·百度·制造
2601_959481922 小时前
CPT Markets:把平台稳定性做到位——视角梳理与提示整理
大数据
ihuyigui2 小时前
国际商超零售短信接口
大数据·前端·后端·架构·零售
湘美书院--湘美谈教育2 小时前
湘美谈教育AI经验集锦:细分领域的标准定义者
大数据·人工智能·深度学习
SelectDB2 小时前
Agentic Analytics 时代,AI Agent 真正需要怎样的数据基座?
大数据·agent·自动化运维