【发布时间】:2020-03-10 04:43:41
【问题描述】:
我想了解以下代码存储在哪个节点(驱动程序或工作程序/执行程序)中
df.cache() //df is a large dataframe (200GB)
并且哪个性能更好:使用sqlcachetable或cache()。我的理解是其中一个是懒惰的,另一个是渴望的。
【问题讨论】:
标签: apache-spark apache-spark-sql
我想了解以下代码存储在哪个节点(驱动程序或工作程序/执行程序)中
df.cache() //df is a large dataframe (200GB)
并且哪个性能更好:使用sqlcachetable或cache()。我的理解是其中一个是懒惰的,另一个是渴望的。
【问题讨论】:
标签: apache-spark apache-spark-sql
df.cache() 调用persist() 方法,该方法在存储级别上存储为MEMORY_AND_DISK,但您可以更改存储级别
persist() 方法调用
sparkSession.sharedState.cacheManager.cacheQuery()
当您看到cacheTable 的代码时,它也会调用相同的代码
sparkSession.sharedState.cacheManager.cacheQuery()
这意味着两者相同并且是惰性评估(仅在执行操作时评估),除了persist方法可以存储为提供的存储级别,这些是可用的存储级别
也可以使用不延迟求值的SQLCACHE TABLE,将整个表存储在内存中,这也可能导致OOM
总结:cache()、persist()、cacheTable() 被懒惰地评估,需要执行操作才能工作,而SQL CACHE TABLE 是一个渴望
在这里查看details!
您可以根据自己的要求选择!
希望这会有所帮助!
【讨论】:
cache 和默认 persist 是 MEMORY_AND_DISK
只需加上我的 25 美分。 SparkDF.cache() 会将数据加载到执行程序内存中。 它不会加载到驱动程序内存中。这是我们想要的。 这是我刚刚运行的 df.cache().count() 后 50% 的数据负载的快照。
Cache() 保存在内存和磁盘中,由 koiralo 描述,并且也是惰性求值的。
Cachedtable() 存储在磁盘上,因此对节点故障具有弹性。
【讨论】:
cache(或 persist)方法将 DataFrame 标记为缓存在内存(或磁盘,如有必要,如另一个答案所述)中,但这仅在对 DataFrame 执行操作时发生,并且仅以一种懒惰的方式,即如果您最终只读取 100 行,则只有这 100 行被缓存。创建一个临时表并使用cacheTable 是急切的,因为它会立即缓存整个表。哪个性能更好取决于您的情况。我对普通 DataFrame cache 所做的一件事是立即调用 .count(),强制缓存 DataFrame,并避免注册临时表等。
【讨论】:
火花记忆。这是由 Apache Spark 管理的内存池。它的大小可以计算为
(“Java Heap” – “Reserved Memory”) * spark.memory.fraction, and with Spark 1.6.0 defaults it gives us (“Java Heap” – 300MB) * 0.75.例如,对于 4GB 堆,这个池的大小为 2847MB。整个池分为 2 个区域——存储内存和执行内存,它们之间的边界由 spark.memory.storageFraction 参数设置,默认为 0.5。这种新的内存管理方案的优点是这个边界不是静态的,在内存压力的情况下边界会被移动,即一个区域会通过从另一个区域借用空间来增长。稍后我将讨论“移动”此边界,现在让我们关注如何使用此内存:1. Storage Memory.此池用于存储 Apache Spark 缓存数据和临时空间序列化数据“展开”。此外,所有“广播”变量都作为缓存块存储在那里。如果你好奇,这里是展开代码。正如您所看到的,它不需要足够的内存来提供展开的块 - 如果没有足够的内存来容纳整个展开的分区,如果所需的持久性级别允许,它将直接将其放入驱动器。从“广播”开始,所有广播变量都以 MEMORY_AND_DISK 持久性级别存储在缓存中。2. Execution Memory.此池用于存储 Spark 任务执行期间所需的对象.例如,它用于在内存中存储 Map 端的 shuffle 中间缓冲区,也用于存储哈希表以进行哈希聚合步骤。如果没有足够的可用内存,该池还支持溢出到磁盘上,但该池中的块不能被其他线程(任务)强行驱逐。
好的,现在让我们关注存储内存和执行内存之间的移动边界。由于执行内存的性质,您不能从该池中强制驱逐块,因为这是用于中间计算的数据,如果找不到它引用的块,则需要此内存的进程将简单地失败。但存储内存并非如此——它只是存储在 RAM 中的块缓存,如果我们从那里驱逐块,我们可以更新块元数据,反映该块被驱逐到 HDD(或简单地删除)的事实,并尝试访问此块 Spark 将从 HDD 读取它(或重新计算以防您的持久性级别不允许溢出到 HDD)。
因此,我们可以从存储内存中强制驱逐该块,但不能从执行内存中这样做。 Execution Memory pool 什么时候可以从 Storage Memory 借一些空间?它发生在以下任一情况下:
存储内存池中有可用空间,即缓存块不会使用那里的所有可用内存。然后它只是减少了存储内存池的大小,增加了执行内存池。
存储内存池大小超过了初始存储内存区域大小,并且已使用所有这些空间。这种情况会导致从存储内存池中强制驱逐块,除非它达到其初始大小。
反过来,只有当执行内存池中有一些空闲空间可用时,存储内存池才能从执行内存池中借用一些空间。
您可能还记得,初始存储内存区域大小计算为“Spark Memory” * spark.memory.storageFraction = (“Java Heap” – “Reserved Memory”) * spark.memory.fraction * spark.memory.storageFraction。使用默认值,这等于(“Java Heap” – 300MB) * 0.75 * 0.5 = (“Java Heap” – 300MB) * 0.375. For 4GB heap this would result in 1423.5MB of RAM in initial Storage Memory region。
【讨论】: