
做数据平台的人可能都有过一种拧巴感报表和模型明明用的是同一批业务数据工程上却各走各的独木桥。我在一次技术架构梳理里把 EMR Serverless Spark 和 DataWorks 放进同一套体系中实践了一遍才真正把DataAI 一体化这个口号落成了可运行、可计费、可追踪的任务链。这篇文章不聊官方功能清单只讲我自己拆分职责边界、定迁移顺序、排调度窗口时积累下来的经验正打算做类似改造的数据平台同学可以对照着参考。1. 先把“DataAI一体化”翻译成可执行的架构指标1.1 为什么“能跑模型”不等于“一体化”很多团队觉得只要数据平台上能跑 Spark算法同学又能提交训练脚本就已经是 DataAI 一体化了。真去翻代码会发现完全不是这么回事。常见画面是数据团队在数仓里精心维护一张用户宽表算法团队却根本没读这张表而是自己从业务库里拉原始数据、自己做清洗、自己在训练代码里拼特征。于是同一个“用户近 30 天支付金额”数仓宽表算出来是一个值算法特征表算出来是另一个值。两边复盘半天才发现一个剔除了退款订单一个没剔除。这就是断裂带。DataAI 一体化要消除的不是“能不能跑”而是数据和特征之间那些隐性拷贝。模型能不能跑起来永远不是判断标准数据团队产出的资产能不能直接供算法消费才是。1.2 我用来判断一体化是否落地的四个检查项我在实际梳理架构时不太看 PPT 上的架构图而是直接拿四个问题去问团队算法团队是否还维护独立的取数管道只要训练代码里还有“连库拉数、临时清洗”的逻辑就说明底座还没打通。特征是否有统一目录和版本在数据地图或元数据平台上能不能查到某个特征由哪张任务产出、什么时候更新、字段结构是什么。训练样本能不能回溯重建给定一个历史时间点能否重建当时的训练样本而不是靠当时临时跑的 SQL 碰运气。推理结果是否回流到数仓做监控模型上线后的效果数据有没有变成下一轮特征更新的输入。这四个问题全部回答“是”一体化才算真正成立。后面所有技术选型都是围绕这四条去配的。2. 重新划分底座职责哪个活该交给 Serverless Spark哪个活该留在 DataWorks2.1 DataWorks 管的是“流程”Spark 管的是“算力”这套组合里最容易搞混的就是职责边界。我习惯用一个类比DataWorks 像施工计划表和质检员负责决定今天有哪些工序、按什么顺序执行、质量合不合格EMR Serverless Spark 像按需雇佣的施工队负责在给定的时间把活干完不用长期养着一帮人。所以 DataWorks 侧承载的是数据集成、任务开发、工作流编排、周期调度、告警运维、数据地图和血缘治理。它不负责具体算多少数据但负责什么时候算、谁来触发、算挂了怎么处理。EMR Serverless Spark 侧承载的是计算本身。它没有需要你管理的常驻集群作业提交后由平台按需拉起资源按实际消耗计费。你不用关心节点够不够、要不要扩容只管把作业定义清楚。2.2 数据从查询到计算的链路里谁负责什么我梳理存量任务时习惯把一条完整链路拆成六段每一段对照传统方式和改造后的承载环节传统方式改造后承载数据接入自写同步脚本定时跑DataWorks 数据集成任务数据开发临时 SQL 或本地脚本DataWorks 数据开发与 Spark 作业节点任务编排crontab 互相依赖DataWorks 工作流 DAG计算资源自建 Hadoop 集群常驻EMR Serverless Spark 弹性资源数据治理靠口口相传数据地图、血缘、质量规则监控运维人工盯日志运维中心告警与基线预警这段链路切分清楚之后后面所有改造都围绕同一个原则计算与流程解耦。DataWorks 不需要关心 Spark 底层有多少 ExecutorSpark 也不需要关心任务是被谁触发的两边通过统一的作业定义衔接逻辑上非常干净。3. 第一次改造把散落的批处理任务收敛成统一数仓资产3.1 建表与分层先想清楚 ODS/DWD/DWS/ADS 的边界很多人一上来就想迁移计算任务我反而建议先从建表规范入手。存量脚本之所以乱根子在于没有分层边界所有中间结果堆在一起时间一长没人知道哪张表能信、哪张表是废弃的。我常用的分层方式还是经典的 ODS、DWD、DWS、ADS。ODS 保留原始明细DWD 做清洗规范化DWS 做汇总指标ADS 面向应用。表命名用“分层前缀 业务域 表名 周期后缀”比如dwd_user_trade_di表示用户交易域日增量明细表。这张表建出来大概长这样CREATE TABLE IF NOT EXISTS dwd_user_trade_di ( user_id STRING COMMENT 用户ID, order_id STRING COMMENT 订单ID, pay_amount DECIMAL(12, 2) COMMENT 实付金额, pay_time DATETIME COMMENT 支付时间, dt STRING COMMENT 分区日期 ) COMMENT 用户交易明细-日增量 PARTITIONED BY (dt) STORED AS PARQUET TBLPROPERTIES (lifecycle 400);两个细节容易被忽略。第一是生命周期我当时设了 400 天因为太短会导致历史样本无法重建太长会白烧存储费这个值要根据训练回溯窗口倒推。第二是分区策略按dt日分区是起步操作但对于 AI 训练需要的长时间历史回放分区字段还要考虑能否高效裁剪否则训练读数时会扫掉大量无用分区。3.2 调度编排与质量门禁把一次性脚本变成稳定任务建表规范只是开始真正把资产“稳住”的是调度编排。我习惯把一段业务链路做成一棵 DAG依赖关系在 DataWorks 工作流里显式表达。比如订单数据接入任务和用户数据接入任务跑完才触发 DWD 清洗任务DWD 跑完才触发 DWS 汇总汇总完才更新 ADS 指标表。调度周期和运行时间也要错开。我一般把重量级任务排在凌晨两点以后避开业务库高峰也给重试留出时间窗口。工作流里的失败重跑策略默认开但重试次数控制在三次以内避免任务挂掉后无限空转。质量门禁这一步很多人会跳过但它恰恰是后期 AI 链路稳定性的命脉。我会给核心表先配三条规则而不是一次配几十条非空校验、总量波动阈值、主键唯一性校验。比如订单表的pay_amount字段非空率要求不低于 99%当日支付订单量相比前 7 天均值波动不超过 30%超过就把下游任务阻断。规则越多告警越多团队反而容易对这个系统脱敏。这里有个实操体会先迁移两张核心表完整跑两周看稳定性同时把新旧平台的产出结果做逐日对账确认无误后再批量铺开。直接全量迁移一旦指标对不上回溯成本会非常高。4. 第二次改造将 Spark 作业切到按量计费的弹性资源池4.1 从固定集群到 Serverless 资源的迁移步骤自建 Hadoop 集群最大的问题不是性能而是“不管有没有活钱都在烧”。业务波峰波谷明显的时候集群必须按峰值配置低估了任务排队高估了日常浪费。Serverless 资源正好补这个短板但迁移不是改个执行引擎就完事。我的迁移顺序是四步走先盘存量。把所有跑在集群上的任务列成清单标注运行时长、数据量、调度频率、依赖关系。挑低风险任务先迁。比如日报汇总、特征表这类离线任务失败影响小、可重跑适合当试验田。双跑对比。新平台跑一份结果旧平台也照常跑连续几天比对核心指标而不是直接切换。稳定后下线旧集群任务再迁下一批。实际迁移中最容易报错的地方通常在三个UDF 的版本依赖、Spark 版本差异导致的 API 不兼容、作业里写死的节点路径。建议提前把这些依赖统一收口放到一个可被 Spark 作业访问的共享位置而不是散落在各台机器上。4.2 关键参数与成本估算的真实口径提交到 EMR Serverless Spark 的作业重点关注三个层面Executor 规格、动态分配范围、Shuffle 并行度。我通常先用默认配置跑一遍看数据量再按需要调整示例参数长这样spark.executor.cores4 spark.executor.memory8g spark.sql.shuffle.partitions200 spark.dynamicAllocation.minExecutors2 spark.dynamicAllocation.maxExecutors20动态分配这一项很关键。小任务不会占满资源大任务也能临时弹上去资源不浪费。但并行度不要一味调大之前见过有人把shuffle.partitions设成 2000结果只有 200 个分区数据量白白产生大量空 Task运行时间反而变长。成本对比要用“有效算力消耗量”来算而不是节点数。固定集群哪怕 24 小时没有任务机器折旧和运维成本都在产生Serverless 则按作业实际运行时长计费。比如一个每天跑 30 分钟、平均占用 8 CU 的任务和一套常驻 8 节点集群的包月成本相比差距会非常明显。当然这不代表 Serverless 一定便宜——如果任务密集到全天候跑满常驻集群仍有成本优势所以迁移前一定要先拿两个任务跑一个月用真实账单说话。5. 第三次改造让特征、样本和训练跑在同一条数据链路上5.1 特征不落表AI 就永远在“手工拉数据”算法团队第一版模型大都是从临时拼特征开始的。特征定义散落在训练代码里改一个特征就要改一整段脚本没人能说清口径也没人能追溯。我的改造方式是把特征计算变成独立的 Spark 作业统一落到特征表。每个特征都有明确的表来源和计算逻辑按日分区更新。特征表有两种主流设计一种是宽表一行一个用户、几百个特征列另一种是按特征组建表行为一个用户加特征组组内用嵌套结构存 key-value。我实际更推荐后者因为 500 列的超级宽表建表容易、维护难Schema 一变上下游全要跟着动。特征表建好后还应该在元数据层做一层特征注册描述把特征名、负责人、更新频率、业务口径都登记清楚。这一步看着偏管理但真到排血缘和定位数据问题时价值会直接凸显。5.2 训练样本的可复现性要靠版本化的表快照特征表只是中间产物训练样本的生产是下一层。我通常的做法是特征表 join 标签表生成按日期分区的样本表。关键约束是——不要覆盖历史样本。比如点击率预估场景每天产出一份样本表正样本是曝光且点击负样本是曝光未点击。训练任务只读取近 7 天或 30 天的分区模型参数迭代时可以基于同一批样本做公平对比。重建历史训练数据时只要把对应的日分区重新连接起来就行不需要回滚代码。样本表的全量快照会占存储但从训练稳定性看完全值回票价。没有这份快照每次调参都在跑一个悄悄变化的数据集模型效果忽上忽下根本没法归因。5.3 闭环的最后一步推理结果回写与血缘追踪特征和训练打通后还要把推理结果接回数仓这个闭环才算完整。离线批量预测场景下Spark 作业读取特征表和模型文件输出预测结果表直接落回数仓。在线推理场景下特征表通过数据服务接口提供给线上系统线上产生的曝光、点击、转化数据再回流到日志链路作为下一轮特征更新和模型评估的输入。这套链路的威力在于血缘。在 DataWorks 上可以看到完整的依赖关系业务表到特征任务特征任务到特征表特征表到训练任务训练任务到模型文件模型文件到预测任务预测结果再到效果表。某天模型效果突然下降第一步不是翻训练日志而是看特征表的数据是否正常、上游同步任务有没有失败。血缘把问题定位从几小时压缩到几分钟。我实际跑通的日级节奏大概是凌晨 1 点同步业务数据2 点产出特征表3 点训练模型4 点批量预测5 点结果写回并生成效果评估表。整个 AI 迭代周期从原来的以周计压缩到以天计。6. 真实改造中容易被忽略的坑小文件、权限与调度窗口6.1 小文件问题症状、定位路径和根治办法小文件问题几乎是每个 Spark 作业迁移到 Serverless 后都会遇到的坑。症状很典型任务越跑越慢“读取输入数据量”没怎么涨但“作业执行时间”明显拉长。去分布式文件系统里一看分区目录下躺着成千上万个几十 KB 的小文件。定位路径按三步走先看目标分区当天的文件数和平均文件大小再看 Spark 执行历史里读取阶段的耗时占比最后确认是不是文件数与任务数失衡导致大量调度开销。如果平均文件大小只有几 MB、文件数却上万基本可以锁定。根治方法有三个写入时控制并行度与分区数避免每个 Task 只写少量数据定期跑合并任务把分区数据重新repartition成固定数量文件再覆盖写回对增量场景尽量做成每日快照表避免一年下来积累几万个分片。小文件影响的不只是跑批任务特征表如果有小文件问题训练阶段读数也会被拖慢。6.2 权限与资源组边界为什么任务“看得到却跑不了”改造后期最让团队困惑的报错是任务明明部署了开发同学也能看到工作流一点运行却报权限不足。我排查过几次后发现很多人把 DataWorks 空间权限、引擎权限和资源组权限当成了一回事实际上这三层是独立的。空间权限决定谁能看、谁能改任务引擎权限决定谁能提交作业到 Spark 计算资源资源组权限决定作业被调度到哪个资源池。三个不对齐就会出现“看得到、跑不了”或者“跑得动、但用的不是预期资源池”的情况。我的建议是提前规划开发、测试、生产三套空间和对应角色服务账号只授予必要权限血缘和项目归属绑定到团队而不是个人。千万不要所有人共用一个超级账号否则任务出问题后连责任人是谁都说不清。6.3 调度窗口撞车与监控指标的读法Serverless 资源是共享弹性的但如果所有任务都约好了整点跑短时间内的资源挤兑照样会发生。我观察到一个规律某几个任务运行时长突然拉长第一反应别怀疑代码先去看是不是同时段有其他大任务在抢资源。解决办法就一句话错峰调度。把不同业务域的调度时间拉开。我这边一般是 ODS 同步排在 00:30DWD 清洗在 01:30 之后DWS 汇总放到 03:00AI 特征表在 04:00训练作业再往后排。这样即使单个任务重跑也不太会撞到别人的窗口。监控这块我也建议换个思路别只盯成功失败而是盯三个趋势指标输入数据量、运行时长、消耗的 CU/成本。数据量没变但运行时长涨了大概率是资源竞争或代码劣化运行时长没变但消耗 CU 涨了大概率是数据倾斜。给核心任务设置基线预警在基线时间前完成即可而不是被一堆零散告警牵着走。最后按我个人习惯给个顺序建议先花一周做资产盘点把每张表、每个任务的负责人、依赖关系和运行频率摸清楚再挑两个最容易出成果的任务做双跑对比确认指标一致后再铺开随后优先把特征链路跑起来因为这是算法团队最能感知到变化的地方。数据平台改造最怕一口吃成胖子数据资产、调度依赖和血缘都是一天天垒起来的模型效果只是最后长出来的果实。这套节奏听起来慢实际推进速度反而是最快的。