米哈游数据开发实习二面:数仓调优与AI落地
- 轮次
- 二面
- 时间
- 2026-08
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 你简历提到做主题集市类数据开发,主要负责哪些内容?
- 数据开发中部分指标普遍偏高,核心原因是什么?业务侧有哪些优化方案?
- 看板中指标波动的评判标准是什么?如何判定波动是否合规?看板的核心作用是什么?
- 你是否参与过某主题集市的搭建?搭建思路、数据粒度如何选择?
- 数据开发任务性能优化,你做过哪些相关工作?分为哪几个方向?
- 资源调优时主要调整哪些核心参数?调参的底层逻辑是什么?
- 你参与过 Agent 相关工作吗?主要做什么?有哪些核心方向?
- Agent 相关问题深挖(原帖未展开,共两问)。
- 你对大模型微调是否了解?有哪些基础认知?
- 假设交给你一份全新的数据开发需求,结合 AI 工具落地全流程,你认为有哪些关键环节需要重点考虑?
- 你做数据开发覆盖哪些链路?日常开发主要使用什么技术?底层原理学习到什么程度?
- 你如何区分宽窄依赖?开发中为什么要尽量规避宽依赖?
- 日常开发中任务参数调优的通用思路是什么?平台层面如何配置调参?
- 多表 JOIN 关联有哪些优化方案?广播 JOIN 适用场景是什么?
- 广播 JOIN 存在什么性能风险?
- 你的毕业时间是什么时候?在校课程安排如何?实习规划是怎样的?
- 你当前实习工作内容出现了什么变化?为什么在投递新岗位?
- 反问:想了解本岗位的发展路径、新人培养模式,岗位分为哪些方向?
- 反问:该岗位所属部门日常工作强度、上下班时间如何?是否常态化加班?
《参考解析》
数据倾斜的处理:倾斜的本质是「某个 key 上聚集了远超平均值的数据量」,表现为绝大多数 task 早就跑完、剩几个 task 卡在 99% 长时间不动,或者某个 executor 内存爆掉 OOM。先定位再动手:看 Spark UI 的 task 耗时分布与 shuffle read size 分布,或对 join key / group by key 做采样统计(SELECT key, count(*) GROUP BY key ORDER BY 2 DESC LIMIT 20)确认热点。处理手段按场景选:① 热点 key 单独处理——把大 key 拆出来单独算再 union 回去;② 加盐打散——给 key 拼随机前缀(如 key_0..key_9)先做局部聚合,再去掉前缀做全局聚合,这是两阶段聚合(map 端预聚合 + reduce 端最终聚合)的通用套路,注意只适用于聚合类操作,join 需要两边同时加盐扩展;③ 小表广播(MapJoin)彻底避开 shuffle;④ 开启 map 端聚合(spark.sql.shuffle.partitions 之外的 combine)、调整并行度让单 task 数据量下降;⑤ 大表 join 大表且热点集中在少数 key 时用「热点分离 + 非热点常规 join」;⑥ 数据本身问题——空值/null 全落到一个 reducer,把 null 转成随机值或提前过滤。
资源调优的核心参数与逻辑:调参的目标只有一句话——「让每个 task 的数据量与每个 executor 的并行度匹配,减少 Shuffle 与 spill」。常调参数:executor 数量(spark.executor.instances 或 --num-executors)、单 executor 的核数(spark.executor.cores)与内存(spark.executor.memory)、driver 内存、并行度(spark.sql.shuffle.partitions 或 spark.default.parallelism,经验值是「总核数的 23 倍」,配合 2 个参数并重跑对比。最忌讳一次改一堆参数,因为无法归因。平台层面通常把这些参数可视化配置(执行器数量、单执行器分片上限、内存占比),这也是「底层原理日常直接用的场景不多」的原因。spark.sql.adaptive.enabled 让 AQE 自动合并小分区)、单分区最大字节数(spark.sql.files.maxPartitionBytes,决定读文件时的分片粒度)、内存分配比例(spark.memory.fraction、spark.memory.storageFraction,即执行内存与缓存内存的划分)、shuffle 分区数与压缩、以及倾斜自动处理(spark.sql.adaptive.skewJoin.enabled)。调参逻辑可以归纳成三步:先看瓶颈在哪(CPU 打满?内存 spill?Shuffle 量过大?IO 等待?),再定位是并行度不足还是单 task 过大,最后只动 1
宽窄依赖与为什么规避宽依赖:窄依赖是「一个父分区最多被一个子分区使用」,例如 map、filter、union、mapPartitions,父 RDD 的每个分区独立流向一个子分区,不需要跨节点搬数据;宽依赖是「一个父分区被多个子分区使用」,例如 groupByKey、reduceByKey、join(未广播时)、repartition、distinct,需要 shuffle,父分区数据要按 key 重新分发到不同节点。规避宽依赖的原因有四个:一是网络与磁盘开销,shuffle 要把中间数据落盘再跨节点传输,是作业中最贵的环节;二是失败恢复代价,窄依赖只需重算丢失的那个分区,宽依赖要回溯重算多个上游分区(血缘越长越贵);三是数据倾斜与 OOM 大多发生在 shuffle 阶段,因为按 key 分发会导致某些分区远大于其他分区;四是它把执行切成多个 stage,流水线被打断,调度与等待成本上升。优化方向就是「能不加 shuffle 就不加」:用 reduceByKey/aggregateByKey 替代 groupByKey(前者有 map 端预聚合)、用广播 join 替代 shuffle join、用 coalesce 替代 repartition(不产生 shuffle)、提前 filter 减少参与 shuffle 的数据量。
广播 JOIN 的适用场景与风险:适用场景是「小表 join 大表」:把小表(经验阈值是压缩后几十 MB 以内,或 spark.sql.autoBroadcastJoinThreshold 默认 10MB 可调)全量分发到每个 executor,在 map 端完成关联,完全省掉大表的 shuffle,通常能带来数倍提升。风险面很具体:① 每个 executor 都要常驻一份小表副本,小表其实不小(比如上百 MB 或上千万行)时会吃掉大量执行内存,导致 executor OOM 或频繁 GC,此时分发开销反而超过常规 join 的 shuffle 开销;② 广播本身有 driver 收集与网络分发的成本,广播变量过多会拖慢 driver;③ 表统计信息过期会让优化器误判大小而做出错误选择(ANALYZE TABLE 要跟上,或通过 hint 显式控制);④ 广播后无法利用分区裁剪,如果小表其实带分区条件、过滤后很小,先 filter 再广播比直接广播整表更划算。一句话结论:广播 JOIN 的收益取决于「小表有多小 + 分发成本 vs shuffle 成本」,要按表体量实测决定,而不是无条件用。
指标口径与看板波动怎么判定:指标「普遍偏高」通常不是模型算错了,而是口径与业务定义错位,常见根因有:统计口径与业务口径不一致(去重维度不同,比如按订单去重还是按人去重)、时间口径不一致(自然日 vs 业务日、时区、跨天归属)、过滤条件缺失(未剔除测试账号、内部账号、退款/取消单、爬虫流量)、重复计算(上游数据重复、join 产生笛卡尔放大、多次上报未去重)、以及数据迟到与回溯补数造成的「重复叠加」。业务侧的优化方案是先把口径写进指标字典并固化(谁定义、谁评审、什么时间生效、历史数据是否回溯),再对齐数据源与埋点。看板中判断波动是否合规,常用三招:同期对比(同比/环比)、统计控制线(按历史分布算均值 ± 3σ,超出即告警)、以及变点检测与同环比结合;同时要区分「业务真实变化」与「数据管道故障」,做法是给关键指标配数据质量校验(行数波动、主键唯一性、空值率、枚举值越界),异常先查管道再找业务。看板的核心作用是「让业务与数据在同一份事实上对话」:统一口径、发现异常、支撑决策,而不是堆一堆图表。
宽表/主题集市与数据粒度怎么选:搭建主题集市的标准流程是「确定主题域 → 梳理业务过程与维度 → 设计总线矩阵(哪些事实表 × 哪些维度)→ 选粒度 → 定维度与度量 → 落地分层(ODS/DWD/DWS/ADS)」。粒度选择是核心决策,原则是「优先选最细粒度并保证原子性」——最细粒度能支撑后续任意上卷聚合,代价是存储与计算成本;如果业务明确只做粗粒度分析且数据量极大,才退到轻度汇总粒度,并且要在 DWS 层按「维度组合 + 时间窗口」预聚合(如「用户-日」「商品-日」汇总表),ADS 层再做面向看板的宽表。要说明的取舍是:一份明细打天下的做法查询慢、成本高;而上卷太早会丢掉下钻能力,所以常见架构是「明细一层 + 轻度汇总一层」并存,由查询引擎自动选择(或由 BI 语义层路由)。
AI 工具落地数据开发全流程:关键环节可以按流水线讲。① 前期业务口径对齐是重中之重:与业务方书面固化指标定义、计算口径、统计范围与验收样例,因为 AI 自动生成的 SQL 极易出现口径偏差(字段选错、去重维度错、时间窗口差一天),不提前对齐就无法保证准确性;② 标准化开发链路:建表 DDL、字段命名与注释、SQL 编写、单测样例数据生成这类有固定模式的活最适合交给 AI,配合模板与代码库示例能显著提效;③ 任务故障自动化排查:调度失败后让 AI 解析运行日志、定位是数据源延迟、依赖未就绪还是资源不足,并输出故障分析与修复建议,人来确认执行;④ 多层级数据复核校验:建表阶段让 AI 补字段中文释义与业务标签;开发完成后用 AI 对比指标文档与产出表的维度、粒度、行数、枚举值,做一致性评估,减少人工复核量,但最终口径确认仍要人签字;⑤ 人机边界与安全:AI 不应直连生产执行变更,SQL 要走审核与灰度,敏感字段脱敏,所有 AI 生成的逻辑都要有测试用例锁定行为。
Agent 与大模型微调的基础认知:数据开发场景下的 Agent 通常做三件事:把自然语言需求转成 SQL/任务(NL2SQL,配合表结构检索与指标语义层)、调度失败后的自动诊断与修复建议(读日志、比对依赖、给出重跑或调参方案)、数据质量异常的归因(指标波动时自动下钻维度找异常分群)。工程上难点是「可验证性」——SQL 生成后必须能执行并校验结果,否则幻觉无法拦截。微调方面至少要能说清基本概念:全量微调 vs 参数高效微调(LoRA/QLoRA 冻结原权重只训练低秩增量矩阵,显存需求小、可插拔)、指令微调(SFT)与偏好对齐(RLHF/DPO)的区别、微调能改变「行为与格式」但补不了知识盲区(补知识优先用 RAG)、以及数据质量与评测集比调参更决定效果。落到数据开发上,微调的合理用武之地是「让模型熟悉公司内部的表命名规范、SQL 方言与常见模式」,而不是让它记住业务口径(口径应该写进语义层,随时可改)。