AWS Glue 如何加速处理 S3 中的大量小型 JSON 文件?
结论
这类作业的瓶颈很可能不在 5GB 数据的计算量,而在 10 万多个小文件造成的文件枚举、S3 打开请求和 Spark 任务调度开销。增加 DPU,或在读取后调用 coalesce()、repartition(),通常无法消除读取阶段的这些开销。
可以先在 AWS Glue 的 S3 数据源中启用文件分组,再查看 Spark UI,确认时间主要花在文件读取还是任务调度上。长期来看,最好在写入阶段就把小文件合并成较大的文件。若后续作业允许,也可以将历史 JSON 转为 Parquet 等列式格式。
在 Glue S3 数据源中启用文件分组
Glue 的 S3 数据源支持 groupFiles 和 groupSize,可以把多个小文件放进较少的读取分区中。直接使用 spark.read.json() 时,无法通过这些 Glue 数据源选项配置分组。可以改用 GlueContext 读取,再转换成 DataFrame:
from awsglue.context import GlueContext
from pyspark.context import SparkContext
sc = SparkContext.getOrCreate()
glueContext = GlueContext(sc)
input_path = f"s3://{bucket}/{prefix}/"
dyf = glueContext.create_dynamic_frame.from_options(
connection_type="s3",
connection_options={
"paths": [input_path],
"recurse": True,
"groupFiles": "inPartition",
"groupSize": "134217728",
},
format="json",
)
df = dyf.toDF()
这里的 groupSize 是 128 MiB,可以作为试跑时的起始值;它不能保证实际分区大小或运行耗时。根据 Spark UI 中的分区数、任务耗时和每个任务读取的数据量,再调整这个值。input_path 应指向包含目标文件的前缀目录。如果目录中有不该读取的文件,应先收窄路径,或按实际目录结构设置读取范围。
检查 schema 推断和实际耗时阶段
如果 JSON 结构稳定,可以提供明确的 schema,避免每次读取时都推断字段类型。使用原生 Spark 读取时,可以这样写:
from pyspark.sql.types import StructType
schema = StructType([...]) # 按实际 JSON 字段定义
df = spark.read.schema(schema).json(input_path)
这段代码演示了如何显式指定 schema。如果需要通过 Glue S3 数据源分组文件,应继续使用上面的 Glue 读取方式,并根据实际结构管理字段类型。
查看 Spark UI 时,可以关注文件列表或读取阶段耗时、任务数量和时长分布、每个任务的输入字节数,以及 executor 是否长时间空闲。如果时间主要花在读取前的文件发现阶段,应检查路径范围和文件枚举;如果任务数量很多、单个任务却很短,可以优先验证文件分组是否有效;如果只有少数任务特别慢,再检查数据倾斜或个别大文件。
调整数据布局和验证方式
如果历史数据会反复处理,可以在上游或单独的整理作业中合并小文件,再让后续作业读取整理后的数据。如果业务不要求保留原始 JSON 作为分析输入,也可以将数据写成 Parquet,并按常用查询条件规划分区。文件大小和分区方式需要结合数据访问模式验证,避免分区过多或单个文件过大。
coalesce() 和 repartition() 主要改变调用位置之后的分区数,也可能触发数据移动。它们不会减少读取前需要发现和打开的 S3 文件数量。df.show() 也是一个会触发作业执行的 action,但它只用于查看结果,不适合作为完整作业耗时的基准。比较优化前后的端到端运行时间时,应使用相同的数据范围和实际写入或处理逻辑,并确认输出结果一致。
备注:内容仅供参考。
