Meta数据工程师面试:Presto查询优化实战案例
一句话总结
Presto查询优化面试不是考你背了多少调参公式,而是考你在资源受限时能不能把"跑不动"变成"跑得完"。面试官在乎的不是你最终QPS提升了多少,而是你在白板前推导出瓶颈时,有没有把"我觉得"换成"数据证明"。真正过关的候选人,往往是那些在deadline前敢砍掉一半join条件、用近似值换回稳定性的人——不是追求完美计划,而是证明不完美方案为什么是那个时刻的最优解。
适合谁看
这篇文章写给三类人。
第一类是正在冲刺Meta数据工程岗的候选人。你可能已经刷完了LeetCode SQL Hard,也看过Presto官方文档,但面对"优化一个跑了两小时的ETL"这种开放性问题,仍然不知道从哪个杠杆点开始拆解。你不是缺知识,是缺一个把知识按优先级排列的框架。
第二类是从其他大厂(尤其是Spark生态)转过来的工程师。你习惯了Spark的exchange shuffle和AQE自适应执行,看到Presto的co-located join和broadcast hint会本能地觉得"这不就是小数据集优化吗"。这种迁移惯性是危险的。Presto的架构假设和Spark完全不同:它不是为长时间运行的批处理设计的,内存即坟墓,一个oversubscribe就能让整个cluster连环挂掉。你的经验是资产,但前提是你能说出"在Presto里为什么这个假设不成立"。
第三类是面试通过后即将进入team match阶段的人。你以为offer到手就万事大吉,但HC(Hiring Committee)的debate往往比onsite更残酷。一个面试官的"weak hire"可能来自你某轮"虽然做出来了但沟通方式让人担心"。这篇文章的后半部分会带你进入那些会议室,看看你的case是怎么被讨论的。
不适合谁:想找"Presto面试八股文"套模板的人。模板在Meta面试里活不过十分钟。
为什么Presto优化题是Meta的标配
Meta的数据基础设施有一个公开的秘密:它可能是全球最大规模的Presto/Trino部署之一。不是Spark,不是BigQuery,是Presto。这个选择背后是一整套技术债和组织惯性。2013年Facebook开源Presto时,Hive on MapReduce正让数据分析师们绝望。Presto的卖点是"秒级查询",代价是内存消耗和容错能力的 trade-off。十五年过去,这个trade-off没有消失,只是被更大的集群和更激进的调度策略掩盖了。
面试官出Presto优化题,本质是在测试你是否理解这个trade-off的当代形态。
一个典型的面试开场是这样的:面试官在白板上画一个三表join,其中一张表是用户行为日志(TB级),一张是维度表(GB级),一张是聚合结果需要join的汇总表。查询跑了一个小时还没出结果,或者更常见的,OOM killed。你的任务不是重写成一个"正确"的查询,而是在有限信息下做出一系列有依据的假设,验证,调整。
这里有一个关键反直觉点:面试官给你的查询往往不是"坏"的。它可能是某个上游团队写的、在生产环境跑了半年的作业,在大多数日子里是正常工作的。今天它慢了,可能是因为数据倾斜了,可能是因为集群水位高了,可能是因为昨天上线了一个feature改变了数据分布。你不是在审判一段代码,是在诊断一个活体系统。
不是要你扮演代码审查者,而是要你扮演on-call engineer。
面试流程拆解:每一轮都在筛什么
Meta数据工程师的标准面试是五轮,总时长约五小时,通常分布在两天。但每一轮的考察重点和通过标准差异极大,混为一谈会死得很难看。
第一轮:SQL Coding(45分钟)。不是LeetCode风格,是给一个真实业务场景写查询。比如"计算每个用户在过去30天内连续活跃的最大天数"。陷阱在于,面试官会在你写出能跑的版本后追问"如果用户有十亿,这个查询在Presto里会怎么死"。很多人在第一问上花太多时间,没有留出足够的余量来讨论执行计划。通过标准:不是写出最优解,而是能快速写出可行解,然后主动提出性能隐患。
第二轮:System Design(45分钟)。设计一个数据管道或存储方案。Presto优化能力在这里以间接方式考察——你需要说明为什么选择某种数据格式(ORC vs Parquet)、分区策略、和查询引擎的配合。一个常见的错误是候选人花大篇幅讲Spark Streaming的exactly-once语义,而面试官想问的是Presto如何读取这些流式写入的文件。
第三轮:Data Modeling(45分钟)。设计星型模型或宽表结构。这一轮和Presto的关系在于,你设计的模型直接决定了查询优化空间。好的候选人会主动讨论"这个schema下,Presto的predicate pushdown能推到哪一层"。
第四轮:Behavioral(45分钟)。Meta的behavioral不是走过场。面试官有权重,如果你的coding轮是borderline,behavioral可能决定你是hire还是no-hire。准备五个故事,按LP(Leadership Principle)框架组织,但不要说教。
第五轮:Domain Deep Dive(45分钟)。这是Presto优化题最可能出现的地方。面试官会给你一个具体的查询计划,让你逐行分析。或者给你一个真实的EXPLAIN ANALYZE输出,让你指出瓶颈。
薪资参考(2024年Meta E4数据工程师,湾区):Base $145K-$165K,RSU $120K-$200K(四年 vest),Bonus 10% target。总包约$280K-$430K。E5总包可达$500K-$700K,但面试深度和系统设计广度会显著增加。
Presto查询优化:面试官真正想看到的推导过程
现在进入核心场景。假设面试官给出以下查询:
`
SELECT u.userid, u.country, COUNT(DISTINCT e.eventid)
FROM users u
JOIN events e ON u.userid = e.userid
WHERE e.dt >= '2024-01-01'
AND u.status = 'active'
GROUP BY u.user_id, u.country
`
查询跑了90分钟,然后OOM。你的第一步不是改SQL,是问问题。
但问什么,怎么问,区分了普通候选人和strong hire。普通候选人会花两分钟问"数据量多大"、"有没有索引"这种通用问题。Strong hire会快速建立诊断框架:"events表的分区策略是什么?user_id的基数和分布如何?这个查询是在adhoc场景还是scheduled job里?上一次成功运行是什么时候?"
不是问更多问题,而是问能缩小假设空间的问题。
假设面试官告诉你:events表按dt分区,每天约500M行;users表5000万行,无分区;两表userid都是高基数字段,但events表在凌晨有一批bot流量导致特定userid出现数量级倾斜。
现在你有两个方向。方向一是处理数据倾斜。方向二是减少参与join的数据量。大多数候选人会本能地选择方向一,因为"数据倾斜"是一个看起来更专业的术语。但在这个场景下,方向二往往是更可靠的杠杆。
为什么?因为Presto的join实现。Presto默认使用distributed hash join, build侧是右表(users),probe侧是左表(events)。如果users表能被广播到所有worker(broadcast join),可以避免shuffle。但5000万行的表通常太大,会触发distributed join,两边都需要shuffle。在这个架构下,即使你用salted key处理了倾斜,shuffle阶段的network IO仍然是瓶颈。
不是先解决倾斜,而是先问"能不能不让这张表参与distributed join"。
正确的推导路径是这样的:首先检查users表是否真的需要全量。WHERE u.status = 'active' 这个过滤条件下推了吗?如果users表没有按status分区,Presto的predicate pushdown帮不上忙,但你可以考虑把这个过滤提前到一个CTES,或者更激进的,维护一个active_users的物化视图。这不是优化技巧,是建模策略——在面试的语境下,它展示的是你区分"这个查询能改"和"这个查询不该这么写"的能力。
假设面试官追问"现在不能改表结构,只能改查询"。那么进入Presto特定的优化空间。
第一个杠杆:join顺序。Presto的CBO(Cost-Based Optimizer)在某些版本和配置下不够智能,特别是面对复杂查询时。你可以用join hint或者重写为子查询来强制join顺序。但更重要的是,你要能读出EXPLAIN的输出。
面试官可能会给你这样的fragment:
`
Fragment 1 [SINGLE]
Output layout: [user_id, country, count]
- Project(...)
- Aggregate(PARTIAL)
- Join[INNER | (userid = userid)]
- RemoteSource[1]
- RemoteSource[2]
`
这里的关键信号是"PARTIAL" aggregate和"RemoteSource"。PARTIAL意味着聚合是在本地先做一部分,然后shuffle到coordinator做final aggregation。如果这个聚合的group by key(user_id, country)基数极高,partial aggregation的收益有限,因为大部分group都需要被shuffle。
不是增加并行度就能解决,而是减少需要shuffle的数据量。
这时候正确的操作是:把COUNT(DISTINCT e.eventid)改写成approxdistinct,或者如果业务允许,先用更粗粒度的grouping set做一层预聚合。Meta内部有大量使用approx_distinct的场景,面试官期待你主动提出这个trade-off,而不是等他提示。
第二个杠杆:过滤条件下推和分区裁剪。上面的查询中,e.dt >= '2024-01-01'这个条件如果生效,应该只扫描最近的分区。但如果dt是string类型且存储格式有问题,或者分区列上有隐式类型转换,分区裁剪可能失效。你要能说出"我会检查information_schema或者DESCRIBE FORMATTED来确认分区列的数据类型,以及用EXPLAIN来验证是否出现了Full Table Scan"。
第三个杠杆:内存管理。Presto的query.max-memory和query.max-memory-per-node是两个关键配置。一个查询在单个worker上超过了per-node限制,会被kill;总和超过了全局限制,也会被kill。你的优化目标不仅是让查询变快,还要让它在资源约束下稳定完成。这有时候意味着接受一个更慢但内存安全的执行计划。
Insider场景一:Debrief会议室里的真实对话
面试官A:"这个候选人在Presto优化题上走了弯路,一开始死磕数据倾斜,花了十五分钟才意识到过滤条件下没推下去。但后来他自己纠正了,而且纠正的过程很清晰,有数据支撑。"
面试官B:"我担心的是他的沟通方式。我提示了两次'还有没有其他方向',他才转向过滤条件。在真实的on-call场景里,十五分钟可能意味着pager已经响了。"
Hiring Manager:"但他的技术判断最终是对的。而且他能说出'在Presto里,oversubscribe比slow query更致命',这说明他理解我们的运营压力。我给lean hire,borderline strong。"
面试官C(Bar Raiser):"我注意到他在说approx_distinct的时候用了'如果业务允许'这个前提。这不是客套,是建模能力的体现。很多候选人会直接改,不考虑下游影响。我给strong hire。"
最终决议:Hire,level E4。但在offer letter发出前,HM加了一个备注:"入职后前六个月重点观察production judgment,建议先从on-call shadow开始。"
这个场景揭示了一个残酷的真相:Meta的面试不是寻找"正确答案",是在有限信息下评估你的决策质量。一个走了弯路但能自我纠正的候选人,可能比一个直线前进但只会标准套路的人更受青睐——前提是,你的弯路不能太长,而且你要能展示出你怎么知道自己走偏了。
Insider场景二:Hiring Committee的争议时刻
HC review的是一个L5(高级工程师)候选人的packet。五轮面试中,coding和system design都是strong hire,behavioral是hire,但domain deep dive出现了一个"lean no-hire"。
提出lean no-hire的面试官反馈:"候选人在Presto优化题上展示了非常好的技术深度,甚至提到了Trino 400版本新增的fault-tolerant execution mode。但当我追问'如果TE也解决不了,你会怎么办'时,他的回答是要不要考虑迁移到Spark。这个答案本身不算错,但他的语气是'Presto不行就换引擎',而不是'在当前约束下最大化Presto的能力'。我怀疑他在面对困难技术问题时,第一反应是绕过而不是深入。"
HC Chair的总结:"这是一个经典的depth vs breadth tension。L5需要能own一个domain,但Meta的数据基础设施团队对Presto有很深的投入,我们不希望招进来的人把Presto当成过渡方案。最终决议:no-hire。建议如果有合适的Spark-focused team,可以转组重面。"
这个案例的教训是:技术面试中的"正确"答案是 context-dependent的。在同一个问题上的同一个答案,在不同level、不同team的评估标准下,可能得出完全相反的结论。不是知道得越多越好,而是要知道你的audience在乎什么。
常见错误
错误一:把Presto当成Spark来优化
BAD版本:候选人听到Presto OOM,立刻开始讲"要增加spark.executor.memory"或者"要开启AQE的skew join优化"。面试官打断:"这是Presto,不是Spark。"候选人愣住,然后试图用同样的概念硬套:"那就是增加Presto的worker内存?"
GOOD版本:候选人首先确认集群类型,然后说:"Presto和Spark的内存模型完全不同。Presto没有spill到disk的可靠机制(至少在开源版本和大多数生产配置里),所以OOM意味着死亡,不是减速。我的第一优先是确保查询能在内存限制内完成,哪怕牺牲一些性能。具体到这个查询,我会先看……"
关键区别:不是不懂装懂,而是快速建立正确的技术上下文。
错误二:过度优化未经验证的假设
BAD版本:候选人花十分钟分析可能的倾斜场景,设计复杂的salted join方案,最后面试官说"实际上这张表没有倾斜,是我故意模糊信息测试你会怎么问"。候选人的复杂方案全部作废,时间所剩无几。
GOOD版本:候选人在分析前先说:"在我给出优化方案之前,我需要确认几个假设。如果数据分布均匀,优化空间可能在过滤条件下推;如果存在倾斜,我们需要不同的策略。您能提供一下events表按user_id的分布直方图,或者告诉我是否有已知的倾斜来源吗?"
关键区别:不是先给答案再验证,而是先验证再给答案。
错误三:忽视业务约束
BAD版本:候选人为了优化性能,提议把按天分区改成按小时分区,或者把string类型的user_id改成bigint。面试官追问"这需要多少存储成本,对现有query pattern有什么影响",候选人无法回答。
GOOD版本:候选人说:"从纯技术角度,把dt的分区粒度细化到小时可以减少单次扫描量。但这会增加metastore的压力,而且大多数查询是按天聚合的,收益可能不抵成本。我会建议先做一个partition pruning的分析,确认有多少查询能受益,再决定是否值得推进这个改动。"
关键区别:不是技术最优解,而是技术-业务联合最优解。
准备清单
- 亲手跑通三个Presto查询优化案例,从EXPLAIN到实际执行,记录每一步的CPU、内存、执行时间变化。不要只看文档,Presto的执行计划和实际行为常有差距。
- 系统性拆解面试结构。PM面试手册里有完整的"数据工程岗技术面试实战复盘"可以参考,特别是关于如何在时间压力下做优先级排序的部分——那个框架不只适用于PM,对DE的开放性问题同样有效。
- 准备两个版本的技术故事:一个五分钟版,一个十五分钟版。五分钟版用于快速建立credibility,十五分钟版用于domain deep dive的深入探讨。
- 熟记Meta Presto(或内部叫法)与开源Trino的关键差异。至少了解三个:内部调度策略、内存管理配置、和Hive metastore的集成方式。不需要知道具体参数名,但要能说出"这里的不同会导致什么样的行为差异"。
- 找一个有Meta经验的人做mock interview,重点不是技术正确性,而是你的表达节奏。Meta面试官通常会在你说话的时候保持沉默,这种沉默是设计好的,不要觉得需要填满。学会在关键点后停顿,让面试官有空间插入追问。
- 准备一到两个"我搞砸过"的故事。Meta的behavioral面试重视自我反思,完美人设反而可疑。重点不是错误本身,而是你如何从错误中重建你的技术判断框架。
FAQ
我没有在Meta工作过,怎么了解内部Presto的使用细节?
你不是需要知道内部参数名,而是需要理解组织约束如何塑造技术决策。读Trino/Presto的官方博客和release note,特别是和Meta相关的贡献。然后,在GitHub上搜索Meta开源的数据基础设施项目,看他们的design doc和issue discussion。一个具体的切入点:Meta开源的Velox执行引擎和Presto的关系是什么?理解这个回答,你就有了和面试官对话的共同语言。另一个更直接的途径是在职员工的技术分享,Meta Engineering Blog和各类会议(如PrestoCon)上的talk往往包含真实场景。关键不是背诵,而是能说出"这个设计选择背后的trade-off是什么"。
面试官给的查询明显有bug,我应该直接指出还是假装没看出来?
这取决于bug的性质和面试的进程。如果是影响分析路径的根本性错误(比如join key类型不匹配导致隐式转换),应该立即指出,这展示你的代码敏感度。如果是风格问题或次优写法但不妨碍分析,可以暂时搁置,先完成核心优化路径,在时间允许时补充说明。最危险的中间态是:你发现了bug,但不确定是不是陷阱,于是犹豫不决、反复试探。这种不确定性比"没看到bug"更让面试官担忧。一个实用的判断标准:如果你能在十秒内确定这是一个问题,就说出来;如果需要更长时间,先推进主线。不是每个bug都需要在当场修复,但每个关键缺陷都需要被识别。
我的背景主要是Spark,Presto经验有限,怎么弥补?
首先,承认这个gap比掩盖它更聪明。面试官能看穿伪装。但承认的方式很重要:不是"我不太懂Presto",而是"我在Spark里处理过类似的问题,我的理解是Presto的内存模型更严格,所以同样的优化逻辑需要更谨慎地应用——我的这个判断对吗?"这展示了迁移学习能力,同时把对话引向你熟悉的领域。其次,在面试前集中时间做一个Presto的deep dive,不是泛泛地读文档,而是针对性地理解:Presto的join实现和Spark有何不同?Presto为什么没有可靠的spill机制?Presto的CBO在哪些场景下会失效?每个问题准备一到两个具体的例子。不是要成为Presto专家,而是要成为"能快速建立正确心理模型"的候选人。Meta招的不是Presto操作工,是有能力在陌生系统里快速定位杠杆点的人。
准备好系统化备战PM面试了吗?
也可在 Gumroad 获取完整手册。