为什么我简单的PySpark代码需要这么长时间才能运行?

时间:2019-12-13 21:28:25

标签: pyspark pyspark-sql pyspark-dataframes

我的输入数据是3000万行* 113列各种类型的数据。我只是想使用PySpark来增加数据中一列的值,但是它要花很多时间才能运行(有时会因OOM错误而崩溃)。

Spark无法处理此数据规模吗?

from pyspark.sql import functions as F


# output = "/output/local"
# my_input = "/dataset/a"
def my_compute_function(my_input, ctx):
    schema_raw = my_input.dtypes

    def type_for_string(string):
        _simple_type_map = {
            "string": StringType,
            "bool": BooleanType,
            "double": DoubleType,
            "float": FloatType,
            "tinyint": ByteType,
            "int": IntegerType,
            "bigint": LongType,
            "smallint": ShortType,
            "date": DateType
        }

        return_type = _simple_type_map.get(string, None)
        if return_type is None:
            if re.search("^decimal.*", string):
                return DecimalType
            else:
                return NullType
        return return_type

    schema_formatted = list(map(lambda entry: StructField(entry[0], type_for_string(entry[1])(), True), schema_raw))

    local_rows = my_input.collect()

    returned_rows = []
    for row in local_rows:
        d = row.asDict()
        d["history_id"] = d["history_id"] + 1
        returned_rows += [d]

    df = ctx.spark_session.createDataFrame(returned_rows, StructType(schema_formatted))
    return df

不相关:如果this合并,您可能会看到此字符串显示SparkSQL功能

1 个答案:

答案 0 :(得分:0)

使用本地PySpark

让我们考虑一下此代码的本地PySpark:

from pyspark.sql import functions as F


# output = "/output/spark"
# input = "/dataset/a"
def my_compute_function(my_input):
    return my_input.withColumn("history_id", F.col("history_id") + F.lit(1).cast("integer"))

此功能的不同之处是不会将数据具体化到Spark驱动程序中,这会迫使每一行数据从执行程序重新排列回驱动程序,以进行逐行处理。

此代码在6m28s的群集中成功完成。

驾驶员与执行者

Spark中的驱动程序具有有限的并行化功能,因为它们受到分配给它们的内核数量的限制。虽然您可以导入并使用PySpark中的multiprocessing模块来实现 more 并行性,但是您并没有使用Spark的全部功能,后者可以让您并行计算 much < / em>更有效。

DataFrames的工作方式以及为何如此之快的原因是因为您的PySpark方法调用仅描述了如何您想要转换数据的方式,而无需在效率低下的Python运行时中完成此工作您。

相反,通过堆叠filter()groupBy()或其他等效的SQL方法,您只需构建输出数据集的描述,然后请Spark为您构建。

强制执行collect()方法可将这种并行性从执行者转移到单个进程,该进程甚至可能没有足够的资源来处理规模。

使用collect()在采样的数据子集上构建上述代码,例如,在集群中的2m38s中执行了1000行,而我上面编写的本机PySpark代码在{{1 }}。

可扩展性

如果使用上面包含的2m1s运行代码,则可能会OOM,而完整数据规模的PySpark代码将在6m25s内执行。这表示数据规模增加了30,000倍,而时间仅增加了3倍。

由于必须执行逻辑转换,因此在PySpark中编写代码可能会更加困难,但它对可伸缩性产生了巨大影响。

您可能还会发现我在here上的回答也很有用。