【问题标题】:Partition Athena query by S3 created date按 S3 创建日期划分 Athena 查询
【发布时间】:2020-04-14 23:26:41
【问题描述】:

我有一个包含约 7000 万个 JSON(约 15TB)的 S3 存储桶和一个按时间戳和 JSON 中定义的其他一些键查询的 athena 表。

可以保证,JSON 中的时间戳或多或少等于 JSON 的 S3-createdDate(或至少足以满足我的查询目的)

我能否通过将 createddate 添加为“分区”之类的东西来以某种方式提高查询性能(和成本) - 我理解这似乎只有前缀/文件夹才有可能?

编辑: 我目前通过使用 S3 库存 CSV 按 createdDate 进行预过滤来模拟这一点,然后下载所有 JSON 并执行其余的过滤,但如果可能的话,我想完全在 athena 内完成此操作

【问题讨论】:

    标签: amazon-s3 amazon-athena aws-glue


    【解决方案1】:

    没有办法让 Athena 使用诸如 S3 对象元数据之类的东西来进行查询计划。让 Athena 跳过读取对象的唯一方法是组织对象,以便可以设置分区表,然后使用分区键上的过滤器进行查询。

    听起来您对partitioning in Athena 的工作原理有所了解,并且我认为您不使用它是有原因的。但是,为了其他遇到此问题的类似问题的人的利益,我将首先解释如果您可以更改对象的组织方式,您可以做什么。我会在最后给出一个替代建议,你可能想直接跳到那个。

    我建议您使用包含对象部分时间戳的前缀来组织 JSON 对象。具体多少取决于您查询数据的方式。你不希望它太细,也不要太粗糙。太细化会使 Athena 花费更多时间在 S3 上列出文件,太粗化会使其读取太多文件。如果查询的最常见时间段是一个月,这是一个很好的粒度,如果最常见的时间段是几天,那么一天可能会更好。

    例如,如果日期是数据集的最佳粒度,您可以使用如下键来组织对象:

    s3://some-bucket/data/2019-03-07/object0.json
    s3://some-bucket/data/2019-03-07/object1.json
    s3://some-bucket/data/2019-03-08/object0.json
    s3://some-bucket/data/2019-03-08/object1.json
    s3://some-bucket/data/2019-03-08/object2.json
    

    您还可以使用 Hive 样式的分区方案,这是 Glue、Spark 和 Hive 等其他工具所期望的,因此除非您有理由不这样做,否则它可以在将来为您省去麻烦:

    s3://some-bucket/data/created_date=2019-03-07/object0.json
    s3://some-bucket/data/created_date=2019-03-07/object1.json
    s3://some-bucket/data/created_date=2019-03-08/object0.json
    

    我在这里选择了名称created_date,我不知道你的数据取什么名字好。您可以只使用date,但请记住始终引用它(并在 DML 和 DDL 中以不同的方式引用它......),因为它是一个保留字。

    然后你创建一个分区表:

    CREATE TABLE my_data (
      column0 string,
      column1 int
    )
    PARTITIONED BY (created_date date)
    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/'
    TBLPROPERTIES ('has_encrypted_data'='false')
    

    然后一些指南会告诉您运行MSCK REPAIR TABLE 来加载表的分区。如果您使用 Hive 样式的分区(即…/created_date=2019-03-08/…),您可以这样做,但这需要很长时间,我不建议这样做。您可以通过手动添加分区来做得更好,您可以这样做:

    ALTER TABLE my_data ADD
      PARTITION (created_date = '2019-03-07') LOCATION 's3://some-bucket/data/created_date=2019-03-07/'
      PARTITION (created_date = '2019-03-08') LOCATION 's3://some-bucket/data/created_date=2019-03-08/'
    

    最后,当您查询表时,请确保包含 created_date 列,以便为 Athena 提供它需要的信息,以便仅读取与查询相关的对象:

    SELECT COUNT(*)
    FROM my_data
    WHERE created_date >= DATE '2019-03-07'
    

    您可以通过观察从created_date >= DATE '2019-03-07' 更改为created_date = DATE '2019-03-07' 时所扫描数据的差异来验证查询是否会更便宜。


    如果您无法更改在 S3 上组织对象的方式,则有一个文档记录不充分的功能,即使您无法更改数据对象,也可以创建分区表。您所做的是创建与我上面建议的相同的前缀,但不是将 JSON 对象移动到此结构中,而是在每个分区的前缀中放置一个名为 symlink.txt 的文件:

    s3://some-bucket/data/created_date=2019-03-07/symlink.txt
    s3://some-bucket/data/created_date=2019-03-08/symlink.txt
    

    在每个symlink.txt 中,您都放置了要包含在该分区中的文件的完整 S3 URI。例如,您可以在第一个文件中放置:

    s3://data-bucket/data/object0.json
    s3://data-bucket/data/object1.json
    

    和第二个文件:

    s3://data-bucket/data/object2.json
    s3://data-bucket/data/object3.json
    s3://data-bucket/data/object4.json
    

    然后您创建一个看起来与上表非常相似的表格,但有一点不同:

    CREATE TABLE my_data (
      column0 string,
      column1 int
    )
    PARTITIONED BY (created_date date)
    ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' 
    STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat'
    OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
    LOCATION 's3://some-bucket/data/'
    TBLPROPERTIES ('has_encrypted_data'='false')
    

    注意INPUTFORMAT 属性的值。

    您可以像添加任何分区表一样添加分区:

    ALTER TABLE my_data ADD
      PARTITION (created_date = '2019-03-07') LOCATION 's3://some-bucket/data/created_date=2019-03-07/'
      PARTITION (created_date = '2019-03-08') LOCATION 's3://some-bucket/data/created_date=2019-03-08/'
    

    我遇到的关于此功能的唯一与 Athena 相关的文档是 S3 Inventory docs for integrating with Athena

    【讨论】:

    • 您在上面的回答中说“您可以只使用date”,但我尝试创建名为 date 的分区,但它不会运行查询。我尝试使用单引号和双引号以及反引号,但它不会运行。我将索引别名为date as dt,但我在s3 中的所有文件都以date 为前缀,而不是dt。在这个例子中,s3 中的文件前缀大概需要是dt 才能使用?
    • 另外,你有每日分区,但建议不要使用MSCK REPAIR TABLE。这些索引通常如何以自动方式更新?显然,每天手动运行更改表甚至修复表是不切实际的。
    • 使用“日期”作为列名很棘手,因为正如我在答案中指出的那样,引用在不同的上下文中是不同的,如果可以的话,请避免使用。
    • @BenSwinburne 对于如何自动创建分区,我有两个建议:如果您只准时分区(例如“created_date”),您可以在每个月的最后一天运行 Lambda 函数(设置一个使用事件桥计划)添加下个月的分区(S3 上不必有任何数据,分区只是元数据)。如果您分区超过时间,请使用为每个新对象运行 Lambda 函数的 S3 触发器,并检查是否需要为每个对象添加新分区(您也可以将事件放入队列以避免在每个新对象上运行)。
    • 使用 Lambda 时,我建议直接使用 Glue API 创建分区,使用 BatchCreatePartitionCreatePartition 调用。它与在 Athena 中执行 SQL 有很大不同,需要指定更多,但速度更快。
    【解决方案2】:

    我开始使用 Theo 的答案,它非常接近(感谢 Theo 出色且非常详细的回复),但是根据 documentation 添加多个分区时,您只需在靠近查询的开头。

    我尝试根据 Theo 的示例在每一行上指定“ADD”,但收到错误。但是,它仅在指定一次时有效。以下是我成功使用的格式:

    ALTER TABLE db.table_name ADD IF NOT EXISTS
     PARTITION (event_date = '2019-03-01') LOCATION 's3://bucket-name/2019-03-01/'
     PARTITION (event_date = '2019-03-02') LOCATION 's3://bucket-name/2019-03-02/'
     PARTITION (event_date = '2019-03-03') LOCATION 's3://bucket-name/2019-03-03/'
     ...
    

    【讨论】:

    • 对不起,我的回答中的语法错误,我已经修复了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-11
    • 1970-01-01
    • 2023-03-30
    相关资源
    最近更新 更多