如何使用AWS Glue / Spark将在S3中分区和拆分的CSV转换为分区和拆分Parquet

时间:2018-02-08 20:09:20

标签: amazon-web-services apache-spark amazon-emr aws-glue

在AWS Glue的目录中,我有一个外部表,其中的分区在S3中看起来大致如此,并且每天都会添加新日期的分区:

s3://my-data-lake/test-table/
    2017/01/01/
        part-0000-blah.csv.gz
        .
        .
        part-8000-blah.csv.gz
    2017/01/02/
        part-0000-blah.csv.gz
        .
        .
        part-7666-blah.csv.gz

我怎样才能使用Glue / Spark将其转换为按日期划分并且每天分割为 n 文件的拼花地板?这些示例不包括分区或拆分或配置(有多少节点和多大)。每天包含几百GB。

因为源CSV不一定在正确的分区(错误的日期)并且大小不一致,所以我希望用正确的分区和更一致的大小写入分区镶木地板。

2 个答案:

答案 0 :(得分:7)

由于源CSV文件不一定是正确的日期,您可以向他们添加有关收集日期时间的其他信息(或使用任何已有的日期):

{"collectDateTime": {
    "timestamp": 1518091828,
    "timestampMs": 1518091828116,
    "day": 8,
    "month": 2,
    "year": 2018
}}

然后,您的作业可以在输出DynamicFrame中使用此信息,并最终将它们用作分区。一些如何实现此目的的示例代码:

from awsglue.transforms import *
from pyspark.sql.types import *
from awsglue.context import GlueContext
from awsglue.utils import getResolvedOptions

import sys
import datetime

###
# CREATE THE NEW SIMPLIFIED LINE
##
def create_simplified_line(event_dict):

    # collect date time
    collect_date_time_dict = event_dict["collectDateTime"]

    new_line = {
        # TODO: COPY YOUR DATA HERE
        "myData": event_dict["myData"],
        "someOtherData": event_dict["someOtherData"],
        "timestamp": collect_date_time_dict["timestamp"],
        "timestampmilliseconds": long(collect_date_time_dict["timestamp"]) * 1000,
        "year": collect_date_time_dict["year"],
        "month": collect_date_time_dict["month"],
        "day": collect_date_time_dict["day"]
    }

    return new_line


###
# MAIN FUNCTION
##

# context
glueContext = GlueContext(SparkContext.getOrCreate())

# fetch from previous day source bucket
previous_date = datetime.datetime.utcnow() - datetime.timedelta(days=1)

# build s3 paths
s3_path = "s3://source-bucket/path/year={}/month={}/day={}/".format(previous_date.year, previous_date.month, previous_date.day)

# create dynamic_frame
dynamic_frame = glueContext.create_dynamic_frame.from_options(connection_type="s3", connection_options={"paths": [s3_path]}, format="json", format_options={}, transformation_ctx="dynamic_frame")

# resolve choices (optional)
dynamic_frame_resolved = ResolveChoice.apply(frame=dynamic_frame,choice="project:double",transformation_ctx="dynamic_frame_resolved")

# transform the source dynamic frame into a simplified version
result_frame = Map.apply(frame=dynamic_frame_resolved, f=create_simplified_line)

# write to simple storage service in parquet format
glueContext.write_dynamic_frame.from_options(frame=result_frame, connection_type="s3", connection_options={"path":"s3://target-bucket/path/","partitionKeys":["year", "month", "day"]}, format="parquet")

没有测试它,但脚本只是如何实现这一点的一个示例,并且非常简单。

更新

1)至于在输出分区中具有特定文件大小/数字

Spark的coalesce and repartition功能尚未在Glue的Python API中实现(仅限Scala)。

您可以将动态帧转换为数据框,并利用Spark的partition capabilities

  

根据“partition_col”转换为数据框和分区

     

partitioned_dataframe = datasource0.toDF()。repartition(1)

     

转换回DynamicFrame进行进一步处理。

     

partitioned_dynamicframe = DynamicFrame.fromDF(partitioned_dataframe,   glueContext,“partitioned_df”)

好消息是Glue有一个有趣的功能,如果每个分区有超过50,000个输入文件,它会自动将它们分组给你。

如果您想要专门设置此行为而不考虑输入文件编号(您的情况),您可以在“从选项创建动态帧”时设置以下connection_options:< / p>

dynamic_frame = glueContext.create_dynamic_frame.from_options(connection_type="s3", connection_options={"paths": [s3_path], 'groupFiles': 'inPartition', 'groupSize': 1024 * 1024}, format="json", format_options={}, transformation_ctx="dynamic_frame")

在前面的示例中,它会尝试将文件分组为1MB组。

值得一提的是,这与 coalesce 不同,但如果你的目标是减少每个分区的文件数量,它可能会有所帮助。

2)如果目的地中已存在文件,它是否会安全地添加(不会覆盖或删除)

write_dynamic_frame.from_options的Glue默认 SaveMode 追加

  

将DataFrame保存到数据源时,如果已经存在数据/表   存在,DataFrame的内容应该附加到   现有数据。

3)鉴于每个源分区可能是30-100GB,DPU的指南是什么

恐怕我无法回答这个问题。这取决于它加载输入文件的速度(大小/数量),脚本的转换等等。

答案 1 :(得分:0)

导入日期时间库

import datetime

根据分区条件分割时间戳

now=datetime.datetime.now()
year= str(now.year)

month= str(now.month)     day= str(now.day)

currdate= "s3:/Destination/"+year+"/"+month+"/"+day 

在writer类的路径地址中添加变量currdate。结果将是镶木地板文件。