如何访问分区 Athena 的子目录中的数据 table

How to access data in subdirectories for partitioned Athena table

我有一个 Athena table,每天都有一个分区,其中实际文件按小时 "sub-directories",如下:

s3://my-bucket/data/2019/06/27/00/00001.json
s3://my-bucket/data/2019/06/27/00/00002.json
s3://my-bucket/data/2019/06/27/01/00001.json
s3://my-bucket/data/2019/06/27/01/00002.json

Athena 能够毫无问题地查询此 table 并找到我的数据,但是在使用 AWS Glue 时,它​​似乎无法找到此数据。

ALTER TABLE mytable ADD 
PARTITION (year=2019, month=06, day=27) LOCATION 's3://my-bucket/data/2019/06/27/01';

select day, count(*)
from mytable
group by day;

day .   count
27 .    145431

我已经尝试将分区的位置更改为以斜杠结尾 (s3://my-bucket/data/2019/06/27/01/),但这没有帮助。

以下是 Glue 中的分区属性。我希望 storedAsSubDirectories 设置会告诉它迭代子目录,但情况似乎并非如此:

{
    "StorageDescriptor": {
        "cols": {
            "FieldSchema": [
                {
                    "name": "userid",
                    "type": "string",
                    "comment": ""
                },
                {
                    "name": "labels",
                    "type": "array<string>",
                    "comment": ""
                }
            ]
        },
        "location": "s3://my-bucket/data/2019/06/27/01/",
        "inputFormat": "org.apache.hadoop.mapred.TextInputFormat",
        "outputFormat": "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat",
        "compressed": "false",
        "numBuckets": "0",
        "SerDeInfo": {
            "name": "JsonSerDe",
            "serializationLib": "org.openx.data.jsonserde.JsonSerDe",
            "parameters": {
                "serialization.format": "1"
            }
        },
        "bucketCols": [],
        "sortCols": [],
        "parameters": {},
        "SkewedInfo": {
            "skewedColNames": [],
            "skewedColValues": [],
            "skewedColValueLocationMaps": {}
        },
        "storedAsSubDirectories": "true"
    },
    "parameters": {}
}

当 Glue 针对相同的 partition/table 运行时,它找到 0 行。

但是,如果所有数据文件都出现在分区的根"directory"中(即s3://my-bucket/data/2019/06/27/00001.json),那么Athena和Glue都可以找到数据.

Glue 找不到数据文件有什么原因吗?我不希望每小时创建一个分区,因为这意味着每年 8700 个分区(Athena 的限制是每个 table 20,000 个分区)。

AWS Glue 数据目录应该定义有关实际数据的元信息,例如table 架构、分区位置等。分区的概念是一种限制 Athena 仅扫描 S3 存储桶中特定目的地的方式,以提高速度和成本效率。当您使用 Athena 查询位于 S3 存储桶中的数据时,它使用 Glue 数据目录中指定的 table 定义。这也意味着,当您在 Athena 中执行 DDL 语句时,相应的 table 会在 Glue datacatalog 中创建。所以我不确定 "Glue finds 0 rows"

是什么意思

如果您像这样使用 Athena 创建 table:

CREATE EXTERNAL TABLE `mytable`(
  `labels` array<string>, 
  `userid` string)
PARTITIONED BY ( 
  `year` string, 
  `month` string, 
  `day` string, 
  `hour` string)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe' 
WITH SERDEPROPERTIES ( 
  'paths'='labels,userid,') 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://my-bucket/data/'

请注意,LOCATION 指向您的分区开始的位置。然后添加单个分区应该是这样的:

ALTER TABLE mytable 
ADD PARTITION (year=2019, month=06, day=27, hour=00) 
LOCATION 's3://my-bucket/data/2019/06/27/00/';
ALTER TABLE mytable 
ADD PARTITION (year=2019, month=06, day=28, hour=00) 
LOCATION 's3://my-bucket/data/2019/06/28/00/';

在这两个 DDL 查询语句之后,您应该能够在 Glue datacatalog 中看到 mytable,在 View partitions 选项卡下有两个分区。现在,如果您 运行 查询没有 WHERE 语句:

SELECT 
    "day", COUNT(*)
FROM 
    mytable
GROUP BY "day";

然后将扫描分区指定的所有数据,您应该得到

| day | count          |
|-----|----------------|
| 27  | some number    |
| 28  | another number |

现在,如果要统计特定日期内的记录,则需要包含 WHERE 语句

SELECT 
    "day", COUNT(*)
FROM 
    mytable
WHERE(
    "day" = '27'
)
GROUP BY "day";

然后你的数据只在 s3://my-bucket/data/2019/06/27/ 下被扫描,你应该得到类似的东西:

| day | count          |
|-----|----------------|
| 27  | some number    |

补充说明

  • 根据 AWS,Glue 目录中的 table 最多可以有 10 million partitions,因此每年 8700 个分区几乎不是问题。
  • AWS 不会就 Athena 执行的 DDL 语句向您收费。
  • 如果您在 S3 中的路径遵循 HIVE 约定,即 s3://my-bucket/data/year=2019/month=06/day=27/hour=00/ 那么在您定义 table 之后,您可以简单地 运行 MSCK REPAIR TABLE mytableall 分区将添加到 Glue 数据目录中的 table。
  • 对于大量的分区,运行ALTER TABLE mytable ADD PARTITION ...是不可行的。相反,您可以使用:

    1. 粘胶爬虫。根据我的经验,这仅在您对数据了解不多且数据量很大时才有用。这里是AWS pricing
    2. AWS SDK,例如 boto3 用于 python。它为 Athena 和 Glue 客户端提供 API。

    Athena client you could generate ALTER TABLE mytable ADD PARTITION ... statements as a string and then send it for execution. Here is a post on Medium 可以帮助您入门。

    您也可以使用 Glue clientbatch_create_partitioncreate_partition 方法做同样的事情,但这需要与 Athena 客户端不同的输入

2019-07-03更新

如果您的数据具有类似

的结构
s3://my-bucket/data/2019/06/27/00/00001.json
s3://my-bucket/data/2019/06/27/00/00002.json
s3://my-bucket/data/2019/06/27/01/00001.json
s3://my-bucket/data/2019/06/27/01/00002.json
...
s3://my-bucket/data/2019/06/28/00/00001.json
s3://my-bucket/data/2019/06/28/00/00002.json
s3://my-bucket/data/2019/06/28/01/00001.json
s3://my-bucket/data/2019/06/28/01/00002.json

但您只想有 3 个分区,即年、月、日,那么您 table 的定义应该考虑到这一点:

CREATE EXTERNAL TABLE `mytable`(
  `labels` array<string>, 
  `userid` string)
PARTITIONED BY (  -- Here we specify only three columns 
  `year` string, 
  `month` string, 
  `day` string)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe' 
WITH SERDEPROPERTIES ( 
  'paths'='labels,userid,') 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://my-bucket/data/'

那么添加分区的DDL语句应该是:

ALTER TABLE mytable
ADD PARTITION (year=2019, month=06, day=27)
LOCATION 's3://my-bucket/data/2019/06/27/';  -- Stop at day level

ALTER TABLE mytable
ADD PARTITION (year=2019, month=06, day=28)
LOCATION 's3://my-bucket/data/2019/06/28/';  -- Stop at day level

请记住,在 S3 中没有文件夹或目录这样的东西。这就是我在 Athena、Glue 和 S3 的上下文中看待分区和位置的方式。分区是一组 S3 对象的抽象,其中分组是通过根据某个 "prefix" <=> 位置过滤所有对象来定义的。因此,当您指定 LOCATION 时,停止在 "day level"。虽然,您可以停在 "hour level",例如s3://my-bucket/data/2019/06/28/01/,但是如果您希望 Athena 能够扫描它们,则需要为所有其他时间创建分区。最重要的是,分区值的组合应该是唯一的(相当于定义了 4 个分区),否则 AWS 不允许创建它。

刚刚在我的 AWS 账户中使用类似于您的 S3 路径的数据进行了测试,并且能够在 Glue 数据目录中看到指向正确目标的分区。

显然 "recurse" 在 create_dynamic_frame 上有一个未记录的附加选项: additional_options = {"recurse": True}

示例:

athena_datasource = glueContext.create_dynamic_frame.from_catalog(database = target_database, table_name = target_table, push_down_predicate = "(year=='2019' and month=='06' and day=='27')", transformation_ctx = "athena_datasource", additional_options = {"recurse": True})

我刚刚用这个选项测试了我的 Glue 作业,可以确认它现在找到了所有 s3 文件。

我遇到过同样的情况

我为 S3 存储桶手动创建了一个 Glue 数据目录 table。该目录有一些未指定为任何分区键的子目录。通过目录 table,Athena 查询处理所有文件,甚至在子目录中。但是 Glue Job create_dynamic_frame.from_catalog 没有。添加 additional_options = {"recurse": True}from_catalog,Glue 作业在子目录中查找文件。

在我的例子中目录 table 有一个分区 属性 "storedAsSubDirectories" = "false" 因为当我用 Glue 创建目录 table 时 属性 是自动分配的控制台或 Athena DDL 查询,我无法触摸控制台上的值。尽管 属性 它与附加选项 recurse=True 一起工作。我怀疑 属性 storedAsSubDirectories 这个词的意思是行不通的。

正如@3nochroot所说,即使在今天官方文件中似乎也没有说明。