【问题标题】:AWS Athena Query PartitioningAWS Athena 查询分区
【发布时间】:2019-09-16 02:34:30
【问题描述】:

我正在尝试使用AWS Athena 为现有平台提供分析。目前的流程是这样的:

  1. 数据作为 JSON 事件泵入 Kinesis Firehose。
  2. Firehose 使用AWS Glue 中的表将数据转换为镶木地板,并每 15 分钟或在流达到 128 MB(支持的最大值)时写入 S3。
  3. 当数据写入 S3 时,它使用路径 /year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/... 进行分区
  4. AWS Glue 爬虫每 24 小时使用最新的分区数据更新表,并使其可供查询。

基本流程有效。但是,这有几个问题......

首先(也是最重要的)是该数据是多租户应用程序的一部分。每个事件中都有一个名为account_id 的属性。将发出的每个查询都将由特定帐户发出,我不想为每个查询扫描所有帐户数据。我需要找到一种可扩展的方式只查询相关数据。我确实考虑过尝试让我们 Kinesis 提取 account_id 并将其用作分区。但是,目前不支持此功能,如果帐户数量超过 10,000 个,AWS 20k 分区限制很快就会成为问题。

第二个问题是文件大小! AWS 建议文件不要小于 128 MB,因为这会对查询时间产生不利影响,因为执行引擎可能会花费额外的时间来处理打开 Amazon S3 文件的开销。鉴于 Firehose 的性质,我只能达到每个文件 128 MB 的最大大小。

【问题讨论】:

    标签: amazon-web-services amazon-s3 amazon-athena amazon-kinesis-firehose


    【解决方案1】:

    有这么多帐户,您可能出于多种原因不想使用account_id 作为分区键。我认为你在限制方面很好,the partition limit per table is 1M,但这并不意味着这是一个好主意。

    不过,您可以通过对部分帐户 ID 进行分区来显着减少扫描的数据量。如果您的账户 ID 是均匀分布的(如 AWS 账户 ID),您可以按前缀进行分区。如果您的帐户 ID 是第一位数字分区,则每个查询将扫描的数据量减少 90%,两位数减少 99% - 同时仍将分区数量保持在非常合理的水平。

    不幸的是,我也不知道如何使用 Glue 来做到这一点。在进行 ETL 时,我发现 Glue 通常非常无用。根据我的经验,即使是简单的事情也很难。使用 Athena 的 CTAS 功能与一些简单的 S3 操作相结合,将 CTAS 操作生成的数据作为分区添加到现有表中,我取得了更大的成功。

    如果您想出一种提取帐户 ID 的方法,您还可以尝试为每个帐户使用单独的表,you can have 100K tables in a database。它与表中的分区没有太大区别,但可能更快,具体取决于 Athena 如何确定要查询的分区。

    不要太担心 128 MB 文件大小的经验法则。拥有大量小文件确实比拥有少量大文件更糟糕 - 但扫描大量数据以过滤掉一小部分对性能和成本非常不利,这也是事实。 Athena 可以在一秒钟内提供结果,即使是对数百个只有几 KB 大小的文件的查询也是如此。我会担心确保 Athena 先读取正确的数据,然后再考虑理想的文件大小。

    如果您告诉我更多关于每个帐户的数据量和帐户的预期寿命,我可以就目标提供更详细的建议。


    更新:鉴于 Firehose 不允许您更改输入数据的目录结构,并且 Glue 通常非常糟糕,以及您在评论中提供的附加上下文,我会这样做像这样:

    • 创建一个 Athena 表,其中包含数据中所有属性的列,并将日期作为分区键。这是您的输入表,只会针对此表运行 ETL 查询。不用担心输入数据有单独的年月日目录,你只需要一个分区键。将这些作为单独的分区键只会使事情复杂化,并且拥有一个意味着它可以是 DATE 类型,而不是每次你想约会时都必须组合成一个日期的三个单独的 STRING 列计算。

    • 使用相同的列创建另一个 Athena 表,但按 account_id_prefix 和日期或月份进行分区。这将是您对其运行查询的表。 account_id_prefix 将是您帐户 ID 中的一两个字符——您必须测试最有效的方法。您还必须决定是按日期还是更长的时间跨度进行分区。日期将使 ETL 更容易、更便宜,但更长的时间跨度会产生更少和更大的文件,这可以提高查询效率(但可能更昂贵)。

    • 创建执行以下操作的 Step Functions 状态机(在 Lambda 函数中):

      • 向输入表添加新分区。如果您安排您的状态机每天运行一次,它只需添加与当前日期对应的分区即可。使用 Glue CreatePartition API 调用来创建分区(不幸的是,这需要大量信息才能工作,但是您可以运行 GetTable 调用来获取它。例如,将 ["2019-04-29"] 用作 Values 和 @987654337 @ as StorageDescriptor.Location。这相当于运行 ALTER TABLE some_table ADD PARTITION (date = '2019-04-29) LOCATION 's3://some-bucket/firehose/year=2019/month=04/day=29' - 但通过 Glue 执行此操作比在 Athena 中运行查询更快,更适合 Lambda。
      • 在输入表上启动CTAS query,并使用当前日期的过滤器,按第一个字符或帐户 ID 和当前日期进行分区。使用低于查询表位置的 CTAS 输出位置。为 CTAS 操作创建的表生成一个随机名称,该表将在后面的步骤中删除。使用 Parquet 作为格式。
      • 查看 Poll for Job Status 示例状态机,了解如何等待 CTAS 操作完成。
      • CTAS 操作完成后,列出使用 Glue GetPartitions 创建的临时表中创建的分区,并使用 BatchCreatePartitions 在查询表中创建相同的分区。
      • 最后删除属于您删除的查询表分区的所有文件,并删除CTAS操作创建的临时表。

    如果您决定对比 date 更长的时间进行分区,您仍然可以使用上述过程,但您还需要删除查询表中的分区和 S3 上的相应数据,因为每次更新都会替换现有数据(例如按月分区,我建议您尝试,每天您都会为整个月创建新文件,这意味着需要删除旧文件)。如果您想每天多次更新您的查询表,那将是相同的。

    这看起来很多,看起来就像 Glue Crawlers 和 Glue ETL 所做的那样 - 但根据我的经验,它们并没有那么容易。

    在您的情况下,数据是使用 Glue Crawlers 理解的 Hive 样式分区进行分区的,但在许多情况下,您没有获得 Hive 样式分区,而只是 Y/M/D(我实际上并不知道 Firehose 可以以这种方式传递数据,我以为它只做 Y/M/D)。 Glue Crawler 每次运行时也会做很多额外的工作,因为它无法知道数据添加到了哪里,但是你知道从昨天开始添加的唯一分区是昨天的分区,因此减少了爬取一步到位。

    Glue ETL 也使事情变得非常困难,与 Lambda 和 Step Functions 相比,它是一项昂贵的服务。您要做的就是将原始数据从 JSON 转换为 Parquet 并重新分区。据我所知,使用比 Athena CTAS 查询更少的代码是不可能的。即使您可以使用 Glue ETL 以更少的代码进行转换操作,您仍然需要编写大量代码来替换目标表中的分区 - 因为这是 Glue ETL 和 Spark 根本不支持的。

    Athena CTAS 并不是真的要做 ETL,我认为我上面概述的方法比它应该的要复杂得多,但我相信它比尝试做同样的事情要简单(即根据另一个表中的数据不断更新和可能替换一个表中的分区,而不是每次都重建整个表。

    通过此 ETL 过程,您获得的好处是,您的摄取不必担心分区超过时间,但您仍然可以获得针对查询进行优化的表。

    【讨论】:

    • 谢谢@Theo。我真的很喜欢数字 ID 分区的想法,很棒的想法......不幸的是 account_id 是一个 8 字符的字母数字哈希码:例如ab12cd34。它是专门为帮助 dynamoDB 查询而设计的。关于数据,表格是物联网传感器数据。它基本上由一个索引哈希(例如00000000-0a628d7c-0a75929d-f2482c9d)组成,它标识了消息的来源(前 8 个字符标识了帐户)。一个属性名称(例如temp)和一个值(例如24)。我们有大约 10 亿个事物,每个事物都有约 10 个属性。想法?
    • 您的帐户 ID 听起来非常适合进行分区,但数学上有点不同,如果您在第一个字符上进行分区,您将获得 16 个分区,并将扫描的数据量减少到 1/16,并且两个字符 256 个分区和扫描数据的 1/256。我会用更多建议更新我的答案。
    • 我的数学不太好,在这里我很容易弄错……字母表有 26 个字母,在 0 到 9 之间有 10 种可能的数字,每个字符有 36 个可能的选项。仅为第一个字符创建分区将导致 36 个分区,我认为会将扫描的数据减少到 1/36。对 2 进行分区将产生 36 * 36 = 1296 分区,并将扫描的数据减少到 1/1296。 (我将尝试你上面的一些想法)
    • 您比我更了解您的数据,但您的 ID 似乎是十六进制而不是基数 36。
    猜你喜欢
    • 2021-01-20
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    • 2020-09-12
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多