AWS Glue:如何处理具有不同模式的嵌套JSON

时间:2018-03-23 21:09:18

标签: amazon-redshift aws-glue amazon-dynamodb-streams amazon-redshift-spectrum

目的: 我们希望使用AWS Glue Data Catalog为存储在S3存储桶中的JSON数据创建一个表,然后我们将通过Redshift Spectrum进行查询和解析。

背景 JSON数据来自DynamoDB Streams并且是深层嵌套的。第一级JSON具有一组一致的元素:Keys,NewImage,OldImage,SequenceNumber,ApproximateCreationDateTime,SizeBytes和EventName。唯一的变化是有些记录没有NewImage,有些没有OldImage。但是,在第一级以下,架构差异很大。

理想情况下,我们希望使用Glue仅解析第一级JSON,并且基本上将较低级别视为大型STRING对象(我们将根据需要使用Redshift Spectrum对其进行解析)。目前,我们将整个记录加载到Redshift中的单个VARCHAR列中,但记录接近Redshift中数据类型的最大大小(最大VARCHAR长度为65535)。因此,我们希望在记录达到Redshift之前执行第一级解析。

到目前为止我们尝试/引用的内容:

  • 将AWS Glue Crawler指向S3存储桶会导致数百个表具有一致的顶级架构(上面列出的属性),但STRUCT元素中更深层次的架构会有所不同。我们还没有找到一种方法来创建一个从所有这些表中读取并将其加载到单个表中的Glue ETL作业。
  • 手动创建表并不富有成效。我们尝试将每列设置为STRING数据类型,但是作业没有成功加载数据(可能因为这会涉及从STRUCT到STRING的一些转换)。将列设置为STRUCT时,它需要一个已定义的模式 - 但这正是从一个记录到另一个记录的不同,因此我们无法提供适用于所有相关记录的通用STRUCT模式。
  • AWS Glue Relationalize transform很有趣,但不是我们在这个场景中寻找的东西(因为我们希望保留一些JSON完整,而不是完全展平它)。 Redshift Spectrum支持几周前的scalar JSON数据,但这不适用于我们正在处理的嵌套JSON。这些似乎都没有帮助处理由Glue Crawler创建的数百个表。

问题: 我们如何使用Glue(或其他方法)允许我们只解析这些记录的第一级 - 同时忽略顶层元素下面的不同模式 - 这样我们就可以从Spectrum访问它或将其物理加载到红移?

我是Glue的新手。我花了很多时间在Glue文档中并在论坛上查看(有些稀疏)信息。我可能会遗漏一些明显的东西 - 或者这可能是目前形式的胶水限制。欢迎任何建议。

谢谢!

5 个答案:

答案 0 :(得分:1)

我不确定您是否可以使用表定义执行此操作,但您可以使用映射函数将顶级值强制转换为JSON字符串,从而通过ETL作业完成此操作。文档:[link]

import json

# Your mapping function
def flatten(rec):
    for key in rec:
        rec[key] = json.dumps(rec[key])
    return rec

old_df = glueContext.create_dynamic_frame.from_options(
    's3',
    {"paths": ['s3://...']},
    "json")

# Apply mapping function f to all DynamicRecords in DynamicFrame
new_df = Map.apply(frame=old_df, f=flatten)

从这里你可以选择导出到S3(可能是Parquet或其他一些柱状格式以优化查询)或者直接从我的理解中直接进入Redshift,尽管我还没有尝试过。

答案 1 :(得分:0)

这是目前胶水的限制。你看过胶水分类器了吗?这是我还没有用过的唯一一件,但可能适合你的需求。您可以为字段或类似内容定义JSON路径。

除此之外 - Glue Jobs是要走的路。它是背景中的Spark,所以你几乎可以做任何事情。设置开发端点并使用它。在过去三周里,我遇到了各种障碍,并决定完全放弃任何和所有Glue功能,只有Spark,这样既可移动又实际工作。

在设置开发端点时,您可能需要记住的一件事是IAM角色必须具有“/”路径,因此您很可能需要手动创建具有此路径的单独角色。自动创建的路径为“/ service-role /".

答案 2 :(得分:0)

你应该添加胶水分类器,最好是$ [*]

在s3中抓取json文件时,它将读取文件的第一行。

您可以创建粘贴作业,以便将此json文件的数据目录表加载到redshift中。

我唯一的问题是Redshift Spectrum在读取数据目录中的json表时遇到问题。

如果您找到了解决方案,请告诉我

答案 3 :(得分:0)

我发现对浅层嵌套json有用的过程

  1. i=0 corpus = lapply(corpus, function(x) ifelse(x == "short", {i<<-i+1;paste0("short", i)}, x ) ) # appends index to each occurrence so itoken distinguishes them it = itoken(corpus) tcm = create_vocabulary(it) %>% vocab_vectorizer() %>% create_tcm(it, . , skip_grams_window = 2, weights = rep(1,2)) attempt = as.matrix(forceSymmetric(tcm, "U") %>% .[grep("^short", rownames(.)), -grep("^short", colnames(.))] ) # filters the resulting full tcm # yields intended result but is hacky/slow: print(attempt) different here document is a short2 1 0 1 0 1 short1 0 0 1 1 1 的第一级应用映射;

  2. 分解datasource0struct对象以摆脱元素级别 array,其中df1 = datasource0.toDF().select(id,col1,col2,...,explode(coln).alias(coln)需要explode;

  3. 通过from pyspark.sql.functions import explode选择要保持完整的JSON对象。

  4. intact_json = df1.select(id, itct1, itct2,..., itctm)转换回dynamicFrame并关联 dynamicFrame以及通过df1删除完整的列;

  5. 使用基于'id'的完整表联接关系表 列。

答案 4 :(得分:0)

截至2018年12月20日,我能够手动将具有第一级json字段的表定义为类型为STRING的列。然后在胶水脚本中,dynamicframe将列作为字符串。从那里,您可以在字段上执行Unbox类型的json操作。这将对字段进行json解析并得出真实的模式。将UnboxFilter结合使用,可以循环访问并处理来自同一输入的异构json模式,前提是您可以遍历模式列表。

但是,请注意,这太慢了。我认为胶水是在循环的每次迭代期间从s3下载源文件。我一直在尝试找到一种方法来保留初始源数据,但是即使您将它们指定为粘胶StringType,.toDF仍会派生出字符串json字段的架构。如果可以找出性能更好的解决方案,请在此处添加评论。