计算Spark数据帧的大小 - SizeEstimator会产生意外结果

时间:2018-03-26 13:18:41

标签: apache-spark spark-dataframe

我正在尝试找到一种以编程方式计算Spark数据帧大小(以字节为单位)的可靠方法。

原因是我希望有一种方法来计算"最佳"分区数量("最佳"在这里可能意味着不同的东西:写入Parquet表时可能意味着having an optimal partition sizeresulting in an optimal file size - 但两者都可以假设为某些线性函数数据框大小)。换句话说,我想在数据框上调用coalesce(n)repartition(n),其中n不是固定数字,而是数据框大小的函数。

有关SO的其他主题建议使用SizeEstimator.estimate中的org.apache.spark.util来获取数据框的字节大小,但我得到的结果不一致。

首先,我将数据帧保存到内存中:

df.cache().count 

Spark UI在“存储”选项卡中显示大小为4.8GB。然后,我运行以下命令从SizeEstimator获取大小:

import org.apache.spark.util.SizeEstimator
SizeEstimator.estimate(df)

这给出了115 715&#398字节= ~116MB的结果。但是,将SizeEstimator应用于不同的对象会导致非常不同的结果。例如,我尝试分别为数据帧中的每一行计算大小并将它们相加:

df.map(row => SizeEstimator.estimate(row.asInstanceOf[ AnyRef ])).reduce(_+_)

这导致12&#3984' 698' 256字节= ~12GB的大小。或者,我可以尝试将SizeEstimator应用于每个分区:

df.mapPartitions(
    iterator => Seq(SizeEstimator.estimate(
        iterator.toList.map(row => row.asInstanceOf[ AnyRef ]))).toIterator
).reduce(_+_)

再次产生不同大小的10'  965' 376字节=〜10.8GB。

我理解存在内存优化/内存开销,但在执行这些测试后,我不知道如何使用SizeEstimator来获得足够好的数据帧大小估计(因此分区大小,或生成的Parquet文件大小)。

为了获得对数据帧大小或其分区的良好估计,应用SizeEstimator的适当方式(如果有的话)是什么?如果没有,那么建议的方法是什么?

4 个答案:

答案 0 :(得分:8)

不幸的是,我无法从SizeEstimator获得可靠的估算值,但我可以找到另一种策略 - 如果数据帧已缓存,我们可以从queryExecution中提取其大小,如下所示:

df.cache.foreach(_=>_)
val catalyst_plan = df.queryExecution.logical
val df_size_in_bytes = spark.sessionState.executePlan(
    catalyst_plan).optimizedPlan.stats.sizeInBytes

对于示例数据帧,这恰好提供了4.8GB(这也与写入未压缩的Parquet表时的文件大小相对应)。

这样做的缺点是需要缓存数据框,但在我的情况下这不是问题。

答案 1 :(得分:4)

除了您已经尝试过的大小估算器(很好的见识)。

下面是另一个选择

RDDInfo[] getRDDStorageInfo()

返回有关已缓存的RDD,是否位于 mem 中或同时位于两者上,它们占用多少空间等信息。

实际上火花存储选项卡使用此选项。Spark docs

下面是implementation from spark

 /**
   * :: DeveloperApi ::
   * Return information about what RDDs are cached, if they are in mem or on disk, how much space
   * they take, etc.
   */
  @DeveloperApi
  def getRDDStorageInfo: Array[RDDInfo] = {
    getRDDStorageInfo(_ => true)
  }

  private[spark] def getRDDStorageInfo(filter: RDD[_] => Boolean): Array[RDDInfo] = {
    assertNotStopped()
    val rddInfos = persistentRdds.values.filter(filter).map(RDDInfo.fromRdd).toArray
    rddInfos.foreach { rddInfo =>
      val rddId = rddInfo.id
      val rddStorageInfo = statusStore.asOption(statusStore.rdd(rddId))
      rddInfo.numCachedPartitions = rddStorageInfo.map(_.numCachedPartitions).getOrElse(0)
      rddInfo.memSize = rddStorageInfo.map(_.memoryUsed).getOrElse(0L)
      rddInfo.diskSize = rddStorageInfo.map(_.diskUsed).getOrElse(0L)
    }
    rddInfos.filter(_.isCached)
  }
RDD中的

yourRDD.toDebugString也使用此功能。代码here


一般说明:

我认为,要获取每个分区中的最佳记录数并检查您的分区是否正确并且它们是均匀分布的,我建议尝试如下操作...并调整您的分区数。然后测量分区的大小...会更明智。解决这类problems

yourdf.rdd.mapPartitionsWithIndex{case (index,rows) => Iterator((index,rows.size))}
  .toDF("PartitionNumber","NumberOfRecordsPerPartition")
  .show

或现有的spark函数(基于spark版本)

import org.apache.spark.sql.functions._ 

df.withColumn("partitionId", sparkPartitionId()).groupBy("partitionId").count.show

答案 2 :(得分:3)

SizeEstimator返回对象在JVM堆上占用的字节数。这包括对象引用的对象,实际对象大小几乎总是小得多。

您观察到的大小差异是因为当您在JVM上创建新对象时,引用也会占用内存,这正在计算中。

在这里查看文档 https://spark.apache.org/docs/2.2.0/api/scala/index.html#org.apache.spark.util.SizeEstimator $

答案 3 :(得分:-2)

我的建议是

from sys import getsizeof

def compare_size_two_object(one, two):
    '''compare size of two files in bites'''
    print(getsizeof(one), 'versus', getsizeof(two))