面灵AI→

美团 AI 数据开发一面:数据倾斜、Iceberg 与 Agent 落地

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下介绍(自我介绍)。
  2. 你是做数据平台、中台的开发,还是业务 ETL 开发,还是两者都做?
  3. 你提到一百多张表,这些表有没有主题划分?简单介绍一下。
  4. 项目中的指标你们是怎么对比的?介绍下方法。
  5. 你提到核心指标偏差 2% 就全量发布。离线数据任务,这块的发布校验是怎么规定的?
  6. 测试环境的数据和生产环境数据是否一致?
  7. 为什么选择 X% 这个偏差阈值?逻辑是什么?
  8. 这个项目识别到数据倾斜后,加盐处理是怎么做的?如何缓解数据倾斜?
  9. 那怎么发现数据倾斜、定位倾斜位置?
  10. 如果任务有很多表、几十个 stage,Spark UI 看到某个 stage 运行久、数据量大,怎么定位是哪一段代码造成的?
  11. 大数据开发中,除了数据倾斜,你还做过哪些性能优化?
  12. 那熔断和告警的策略是什么?哪些场景熔断,哪些只告警?
  13. 有没有这样的机制:当最终交付层就绪延迟,自动回溯定位是上游哪一个任务导致延迟,而不是只在每个任务后单独加 DQC?
  14. 简历提到 Hudi,有实际使用吗?
  15. 基础问题,LSM-Tree 大概是什么?
  16. 项目里用到的 AI Agent 主要在哪些方面?
  17. 第二个实习项目相当于中台角色,推广 AI 产品的时候业务团队会不会有抵触?作为实习生推进落地遇到什么困难?
  18. 相当于你是产品和业务用户之间的桥梁,业务反馈零散输入、表象不同但根因一致,你是怎么抽象、归类这些问题的?
  19. 你怎么对 bad case 做分类?有哪些类别?
  20. 那会不会继续往下追溯,比如意图识别、检索召回、大模型幻觉、权限问题?这些底层问题你会归类吗?
  21. 你了解大模型幻觉吗?怎么减少幻觉?
  22. 有没有你自主开发的 Agent(不是单纯使用)?实习或者个人项目都可以,介绍一下。
  23. 文档接入 Agent 的时候,是把文档处理成向量库吗?
  24. 提到准确率提升超过 20%,怎么验证?测试集规模是多大?怎么定义「准确/不准确」?
  25. 数据库相关,索引这块了解吗?
  26. 介绍一下 Iceberg。
  27. Iceberg 的组成、元数据结构了解多少?
  28. Iceberg 解决了传统 Hive 的哪些问题?还有哪些没有覆盖?
  29. Copy-on-Write 的概念?
  30. 一道简单的 Agent Skill 相关的题目(原帖未记录具体内容)。
  31. 你现在还在实习还是已经返校?

《参考解析》

数据倾斜:先定位再动手,加盐只是手段之一

定位顺序是「任务级 → stage 级 → 代码级」。先在 Spark UI(或引擎的等价界面)里看各 stage 的耗时与 shuffle 读写量,找那个明显偏慢、shuffle read 远大于其他 stage 的;点进去看每个 task 的输入记录数/耗时分布,如果少数 task 的数据量是大多数 task 的几十倍、且耗时集中在这几个 task,就基本确认是倾斜;再看是哪个 operator 产生的 shuffle(group by、join、distinct、窗口函数最常见)。要从 stage 落到具体代码,可以对照 DAG 里该 stage 的输入 RDD/DataFrame 回溯到查询计划(explain 里找对应的 Exchange/聚合节点),再结合代码里的分区键判断——倾斜通常来自某个热点 key(大卖家、空值、默认值、某个城市的全量数据)。

确认之后按原因选手段:① 热点 key 聚合——两阶段聚合(先给 key 加随机前缀做局部聚合,再去掉前缀做全局聚合),代价是多一次 shuffle;② 大表 join 小表——直接 broadcast,绕开 shuffle;③ 两个大表 join 且倾斜 key 有限——把热点 key 单独抽出来处理(加盐打散或单独 broadcast),其余走正常 join,最后 union;④ 空值/无效值导致的倾斜——先在 join 前过滤或给空值加随机后缀;⑤ 分区键选择不当——改用分布更均匀的键或加二级分区;⑥ 提高并行度、开启自适应执行(AQE 的 skew join 与动态合并分区)在很多引擎里能自动缓解一部分。答题时要主动补一句代价:加盐会让数据被打散多次处理,可能增加 shuffle 与中间存储,而且对「倾斜来自业务本身集中」的情况治标不治本——真正的解法往往在建模层(提前做预聚合、维表打宽),这一点是区分「用过」和「想过」的地方。

发布校验与偏差阈值:为什么是 2%,以及阈值背后的逻辑

指标对比的常规做法是双跑对账:新旧逻辑在同一时间窗口各跑一次,在共同维度上比较(总量、分维度分布、关键比率),差异超过阈值就挡住发布。阈值不是拍脑袋定的,它的依据通常是三件事:① 数据本身的可接受波动(上游延迟到达、去重口径、埋点丢报导致的自然抖动范围,可以用历史同期的波动分布来量化);② 业务能容忍的误差(这个数字要问业务方,不是数据团队自己定);③ 偏差带来的后果等级——核心营收/结算类指标阈值要收紧,辅助观测类可以放宽。

2% 这种量级的阈值通常是在「误报成本」和「漏报成本」之间取的平衡点:太严会被自然波动频繁打断发布(狼来了,团队开始习惯性忽略告警),太松会放过真实的口径错误。工程上还要注意几点:测试环境与生产环境的数据往往不一致(数据量、时效、上游依赖都不同),所以对账要么在同环境用同一份快照双跑,要么明确接受差异并做归一化对比;校验应该分层,总量级偏差做阻断、分维度偏差做告警;每次放行超阈值的发布都要留下书面结论和审批人,把例外变成有记录的决定,而不是悄悄放过。

Iceberg:元数据结构与它解决的问题

Iceberg 是一层表格式,把「表」的定义与快照从 Hive Metastore 的目录约定里抽出来,变成可控的元数据。层级大致是:catalog 指向一张表的 metadata 文件 → metadata 文件记录当前 snapshot 与 schema、分区规范、排序规则 → snapshot 指向一个 manifest list → manifest list 里是若干 manifest 文件 → 每个 manifest 记录一批 data file(以及对应的分区、列级统计如 min/max、null 数)。这种「文件清单 + 快照」的结构是它所有能力的来源:查询时先读元数据做分区裁剪与文件级过滤(不需要 list 目录,也不需要像 Hive 那样扫全部分区路径),写入时先生成新的 manifest 再原子切换 metadata,因此支持 ACID、快照隔离、时间旅行、增量读。

它相对 Hive 表解决的问题:原子性的写入与提交(Hive 写分区时失败会留下半成品);Schema 演进(新增/删除列、改列类型不必重写数据);隐藏分区与分区演进(改分区策略不需要重刷历史,因为分区值由元数据按规范计算);更准的统计信息带来更好的裁剪与执行计划;以及引擎无关——Spark、Flink、Trino 读同一张表语义一致。它没覆盖的部分也要讲得出:小文件与 manifest 膨胀需要定期 compaction(rewrite data files / manifests);快照与历史版本会持续占存储,要配过期与清理策略;元数据服务本身是高可用组件(catalog 挂了就全挂);行级更新虽然有(Merge-on-Read 的 delete file),但高频 upsert 场景的读放大与写放大仍需调优;权限、血缘、质量这些治理能力仍要外围系统补。

Copy-on-Write 与 LSM-Tree 的关系

Copy-on-Write 是 Iceberg 处理更新/删除的一种策略:只要某个 data file 里有任何一行被改,就重写整个文件并生成新文件,提交时用新文件替换旧文件在快照里的位置。读的时候非常干净——只读 data file,没有额外的合并逻辑,所以读性能好;代价是写放大,改一行可能重写 128MB 的文件,适合更新频率低、读多写少的场景(典型是 T+1 离线表)。与之对应的 Merge-on-Read 是写入时只追加 delete file 与新增数据文件,读时再按位置/主键做合并,写快读慢、需要定期 compaction。选哪种本质是「把成本放在写还是读」,Iceberg 允许按表甚至按场景配置。

LSM-Tree 是另一层的概念,它是一种面向写的索引结构:新写入先落到内存的有序表(memtable)并顺序追加 WAL,写满后刷成一个不可变的有序文件(SSTable),查询要自新到旧逐层查找并合并结果,后台再按层做 compaction;删除用墓碑标记。它的核心优势是把随机写转成顺序写,代价是读放大(可能查多层)、写放大(compaction 反复重写)和空间放大。两者容易被混在一起问,是因为它们都在解决「不可变文件的更新问题」,但层次不同:LSM-Tree 是存储引擎(HBase、RocksDB、ClickHouse 的 MergeTree 家族属于这一路),Copy-on-Write 是表格式在文件粒度上的更新策略。理解这一点,就能解释为什么 Hudi/Iceberg 这类表格式能在对象存储上做出接近数据库的更新语义——它们把 LSM 的思想搬到了文件层。

怎么减少大模型幻觉,以及怎么验证效果

减少幻觉有几条互相补充的路径,最好按「先约束输入、再约束输出、最后兜底」的顺序讲。① 检索增强:把答案限制在检索到的证据里,检索不到就明确说没有资料,同时优化切片粒度、加 rerank、保证召回的相关性——多数幻觉其实是检索没给对材料。② 约束生成:要求模型给出处(引用到具体文档与片段),结构化输出,把「不知道」作为合法答案写进指令;工具类任务优先走确定性查询而不是让模型算。③ 后验校验:对生成的数字、实体、引用做一致性检查(与源文档比对、内部自洽性检查、多次采样看是否稳定),不通过就打回重生成或降级成「建议人工确认」。④ 分工与温度:事实抽取用低温度,需要发散的环节才放开;复杂问题先拆步骤再逐步检索,减少一次性长推理带来的臆造。⑤ 兜底产品化:把「不确定」显式展示给用户,并给出原始来源让人复核,这比硬答一个错答案的体验好得多。

效果验证是面试官一定会追的点:要有带标注的测试集,并且把「准确」定义清楚——是端到端答案正确,还是引用命中、关键字段抽取正确?指标要写明白(准确率、召回、引用正确率、无答率/拒答率),口径由谁判定(人工标注规则 + 双人交叉复核,或模型评审再抽样校准)。20% 这种提升幅度,必须绑定「在什么测试集上、样本量多少、相比什么基线的什么指标、置信区间或显著性如何」,否则面试官会怀疑是把个例当成了提升。真实项目里还要盯线上指标:用户追问率、点踩率、人工复核通过率,这些比离线分数更能说明问题。