无法在 Athena 中加载区分大小写的分区

Yan*_*kee 3 hive amazon-s3 amazon-athena aws-glue-data-catalog

我在 S3 中有数据,该数据按YYYY/MM/DD/HH/结构分区(不是year=YYYY/month=MM/day=DD/hour=HH

我为此设置了一个 Glue 爬虫,它在 Athena 中创建了一张表,但是当我在 Athena 中查询数据时,它会给出一个错误,因为一个字段具有重复的名称( 和 ,URLSerDeurl将其转换为小写,导致名称冲突)。

为了解决这个问题,我手动创建另一个表(使用上面的表定义 SHOW CREATE TABLE),添加'case.insensitive'= FALSE到 SERDEPROPERTIES

WITH SERDEPROPERTIES ('paths'='deviceType,emailId,inactiveDuration,pageData,platform,timeStamp,totalTime,userId','case.insensitive'= FALSE) 
Run Code Online (Sandbox Code Playgroud)

我将 s3 目录结构更改为与 hive 兼容的命名year=/month=/day=/hour=,然后使用 创建表'case.insensitive'= FALSE,然后运行MSCK REPAIR TABLE新表的命令,该命令会加载所有分区。 (完成创建表查询)

但查询时,我只能找到 1 个数据列 ( platform) 和分区列,其余所有列均未解析。但我实际上已经复制了 Glue 生成的 CREATE TABLE 查询以及条件case_insensitive=false

查询结果

我怎样才能解决这个问题?

The*_*heo 6

我认为您有多个单独的问题:一个与爬虫有关,一个与 serde 有关,一个与重复的键有关:

胶水爬行器

如果 Glue Crawler 兑现了他们的承诺,那么对于大多数情况来说,它们将是一个相当好的解决方案,并且可以使我们免于一遍又一遍地编写相同的代码。不幸的是,如果您偏离了 Glue Crawler 设计的(未记录的)用例,您经常会遇到各种问题,从奇怪到完全崩溃(例如,参见这个问题这个问题这个问题这个问题这个问题,或者这个问题)。

我建议您跳过 Glue Crawler,而是手动编写表 DDL(您在爬虫创建的内容中有一个很好的模板,但它还不够好)。然后,您编写一个按计划运行的 Lambda 函数(或 shell 脚本)以添加新分区。

由于您的分区只是按时进行,因此这是一个相当简单的脚本:它只需要每隔一段时间运行一次并添加下一个周期的分区。

您的数据似乎来自 Kinesis Data Firehose,它会按小时粒度生成分区结构。除非您每小时都会收到大量数据,否则我建议您创建一个仅按日期分区的表,并每天运行一次 Lambda 函数或脚本以添加第二天的分区。

不使用 Glue Crawler 的一个好处是,您不必在路径组件和分区键之间建立一对一的对应关系。您可以拥有一个键入为 的分区键date,并像这样添加分区:ALTER TABLE foo ADD PARTITION (dt = '2020-05-13') LOCATION 's3://some-bucket/data/2020/05/13/'。这很方便,因为在完整日期上进行范围查询比在组件单独时要容易得多。

如果您确实需要每小时粒度,您可以有两个分区键,一个是日期,一个是小时,或者仅具有完整时间戳的分区键,例如ALTER TABLE foo ADD PARTITION (ts = '2020-05-13 10:00:00') LOCATION 's3://some-bucket/data/2020/05/13/10/'。然后每小时运行 Lambda 函数或脚本,添加下一小时的分区。

过于精细的分区对性能没有帮助,反而会损害性能(尽管性能损失主要来自小文件和目录)。

串行器配置

至于为什么您只看到platform列的值,这是因为这是列名和属性具有相同大小写的唯一情况。

有点令人惊讶的是,您链接到的 DDL 不起作用,但我可以确认它确实不起作用。我尝试从该 DDL 创建一个表,但没有该pagedata列(我也跳过了分区,但这不会对测试产生影响),实际上,platform当我查询该表时,只有该列具有任何值。

然而,当我删除case.insensitiveserde 属性时,它按预期工作,这让我想到它可能不会按照您想象的方式工作。我尝试将其设置为TRUE而不是FALSE,这使得表再次按预期工作。我认为我们可以从中得出结论,Athena 文档中所说的“默认情况下,Athena 要求 JSON 数据集中的所有键都使用小写”是错误的。事实上,所发生的情况是,Athena 在读取 JSON 时会小写列名称,同时也会小写属性名称。

经过进一步的实验,结果发现该path属性也是多余的。这是一张对我有用的表:

CREATE EXTERNAL TABLE `json_case_test` (
  `devicetype` string, 
  `timestamp` string, 
  `totaltime` string, 
  `inactiveduration` int, 
  `emailid` string, 
  `userid` string, 
  `platform` string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' 
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://some-bucket/data/'
Run Code Online (Sandbox Code Playgroud)

我想说这case.insensitive似乎造成的问题多于它解决的问题。

重复的钥匙

当我添加pagedata列(如struct<url:string>)并添加"pageData":{"URL":"URL","url":"url"}到数据时,出现错误:

HIVE_CURSOR_ERROR:行不是有效的 JSON 对象 - JSONException:重复键“url”

无论该pagedata列是否参与查询,我都会收到错误(例如SELECT userid FROM json_case_test也出错)。我尝试使用case.insensitiveserde 属性TRUEFALSE,但没有效果。

接下来,我查看了serde 的源文档,首先它的措辞要好得多,其次包含关键信息:当您关闭不区分大小写时,您还需要提供列的映射。

通过以下 serde 属性,我能够解决重复的密钥问题:

WITH SERDEPROPERTIES (
  "case.insensitive" = "false",
  "mapping.pagedata" = "pageData",
  "mapping.pagedata.url" = "pagedata.url",
  "mapping.pagedata.url2"= "pagedata.URL"
)
Run Code Online (Sandbox Code Playgroud)

您还必须为除 之外的所有列提供映射platform


替代方案:使用 JSON 函数

您在对此答案的评论中提到,属性的架构pageData不是恒定的。不幸的是,这是胶水爬行器不起作用的另一种情况。如果你不幸的话,你最终会得到一个包含一些属性的不稳定模式(例如参见这个问题)。

当我看到您的评论时,我意识到您的问题还有另一种解决方案:手动设置表(如上所述)并用作string列的类型pagedata。然后,您可以使用诸如JSON_EXTRACT_SCALAR在查询期间提取所需属性之类的函数。

该解决方案以增加查询的复杂性为代价,以减少跟上不断发展的模式的麻烦。