订阅更新

cache()和persist()用了反而更慢了——缓存什么时候该用什么时候不该用?

Spark手册 第 7 题 约 3 分钟读完

本文目录
  1. 先说结论
  2. 为什么缓存会变慢?
  3. 什么时候该缓存?
  4. cache() vs persist()的区别
  5. 实战踩坑:忘记unpersist导致内存泄漏
  6. 总结

先说结论

缓存只在一个场景下有用:同一份DF被多次重复使用。如果一份DF只用一次,cache不但没有加速效果,还会浪费内存和序列化时间。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

很多人学Spark时听到“cache可以加速”,就到处加cache。结果作业不但没变快,反而更慢了,甚至OOM了。今天把“该不该缓存”和“怎么缓存”彻底讲清楚。

为什么缓存会变慢?

每次cache都有代价:

① 序列化开销:把数据从Tungsten的紧凑格式转换为Java对象存入内存,耗时耗内存

② GC压力增大:内存中存了大量缓存对象,GC频率和停顿时间都会增加

③ 挤占Execution内存:Shuffle和Sort可用的内存减少,可能导致溢出到磁盘

所以如果DF只用一次,缓存就是“花钱买了个不需要的东西”。

什么时候该缓存?

场景一:同一份DF被多个下游使用

// ✔ 该缓存:baseDF被两个下游使用
val baseDF = spark.read.parquet("hdfs://.../user_events")
  .filter(col("dt") === "2025-03-15")
  .cache()  // 避免重复读Parquet和filter
 
// 下游一:统计DAU
val dau = baseDF.select("user_id").distinct().count()
 
// 下游二:统计事件分布
val eventDist = baseDF.groupBy("event_type").count()

场景二:迭代计算(机器学习等)

每次迭代都会重新计算整个DAG,cache让每次迭代从缓存开始而不是从源头重算。

cache() vs persist()的区别

方法存储级别适用场景
cache()MEMORY_AND_DISK(DataFrame默认)内存够用的场景,最常用
persist(MEMORY_ONLY)纯内存,溢出则重算小数据集,重算成本低
persist(MEMORY_AND_DISK)内存不够溢出到磁盘大数据集,推荐
persist(DISK_ONLY)纯磁盘数据量巨大且内存紧张
persist(MEMORY_AND_DISK_SER)序列化后存内存+磁盘省内存但多一次反序列化

DataFrame的cache()等价于persist(StorageLevel.MEMORY_AND_DISK),这和RDD的cache()(MEMORY_ONLY)不同!面试时能主动说出这个差异,非常加分。

实战踩坑:忘记unpersist导致内存泄漏

我在出海硬件公司遇到过一个问题:某个ETl作业跑着跑着越来越慢,最后OOM。排查后发现是在一个循环里每次迭代都cache了一个DataFrame,但没有unpersist。累积了几十个缓存把内存吃干净了。

// ✔ 正确做法:用完后unpersist释放内存
val cachedDF = baseDF.cache()
// ... 使用cachedDF ...
cachedDF.unpersist()  // 释放缓存

总结

缓存只在“同一份DF被多次使用”时才有价值。cache()和persist()的区别在于存储级别。用完记得unpersist()。面试时能说清楚“什么时候该缓存、什么时候不该、缓存有什么代价”,就很有深度。

下一篇聊Spark SQL执行计划怎么看——Catalyst优化器到底做了什么?这是理解Spark性能的关键。

你们生产环境用cache多吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器