在Spark中并行读取许多CSV会降低速度

时间:2018-07-24 01:57:03

标签: apache-spark amazon-s3 pyspark multiprocessing

我在S3上存储了100k + CSV文件(平均大小在2MB至10MB之间)。

在将所有CSV一起编译为一个DataFrame之前,我需要分别读取每个文件,以便为CSV分配一个ID。我有一个脚本,可以很好地工作并且可以很好地分发多达2k个文件。在读取并处理了大约2k个文件后,作业速度变慢,执行程序不起作用(CPU使用率降低到5%)。原因可能是什么?我认为这不是分区问题,因为文件不是那么大。它与在Python中启动数千个进程有关吗?

是否最好在CSV中写一个额外的列(以便作业可以异步运行),然后一次读取所有CSV?批量重写脚本是最好的解决方案吗?

这是脚本。

files = list(bucketObj.objects.filter(Prefix=subfolder))
p = ThreadPool(numNodes)
logDFs = p.map(lambda x: process(bucket, columns, x) , files)
df = unionAll(*logDFs)

def process(bucket, nameMap, item):
    logId = item['id']
    key = item['file']
    try:
        logger.info(key + ' ---- Start\n')
        fLog = spark.read.option("header", "true").option(
            "inferSchema", "true").csv(buildS3Path(bucket) + key)
        fLog = assignID(fLog)

        return fLog

    except Exception as e:
        logger.info(key + ' ---- ERROR ---- ')

更新#1 我重新编写了代码,以并行读取每个CSV,并在ID后面附加一列并保存。然后,一个单独的脚本会批量读取现在格式正确的文件。这也有同样的问题-前1500个文件运行速度很快,然后变慢。该项目正在具有10名工作人员的AWS EMR上运行。

更新#2 我已经对该脚本进行了重新设计,使其可以批量处理500个。这种脚本运行得更好,但是在稳定状态下,工作人员仅使用10%的CPU(开始时他们最多使用60%)。

for i in range(0,numBatches):
    p = ThreadPool(numNodes)
    if (i == numBatches):
        fileSlice = fileErrList[i*batchSize:]
    else:
        fileSlice = fileErrList[i*batchSize:((i+1)*batchSize)]

    logger.info('\n\n\n --------------- \n\n\n')
    logger.info('Starting Batch : ' + str(i))
    logger.info('\n\n\n --------------- \n\n\n')
    p.map(lambda x: addIdCsv(x, bucket, logger), fileSlice)
    p.terminate()

1 个答案:

答案 0 :(得分:0)

我有一个可行的解决方案,并且还学会了避免的事情:

  1. 如果您在执行聚合ETL之前被迫分别加载每个文件,请仅在单个文件级别执行必要的转换。在这种情况下,仅将ID添加到文件中。
  2. 根据经验,合并100,000多个文件效率不高。最好保存可以立即加载的新文件。就我而言,我将每个csv转换为具有添加的ID列的镶木地板文件。我在写入时指定了架构,以避免读取时出现问题。然后阅读就变得像spark.read.parquet('filepath')
  3. 一样容易
  4. 确保将火花执行器配置调整为产生的线程数。为了最大化工作者cpu,让每个执行者都使用一个核心。在我的集群上,我有10个工人,每个工人都有4个核心和4GB的内存。每个执行程序都需要分配内存开销(最少384MB)。我在$SPARK_HOME/conf/spark-defaults.conf的EMR主节点上添加了以下配置。

    spark.executor.memory            500M
    spark.executor.memoryOverhead    500M
    spark.executor.cores             1
    

    然后我生成40个线程:p = ThreadPool(40)

  5. 我编写了一个bash脚本来对10,000个文件进行批处理。
  6. 汇总读取后,即可完成其余的ETL。