AWS Athena查询分区

时间:2019-04-26 14:42:07

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

我正在尝试使用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。

1 个答案:

答案 0 :(得分: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文件大小的经验法则。毫无疑问,拥有大量小文件比拥有少量大文件要糟糕-但也确实是,扫描大量数据以过滤掉一小部分对性能和成本都非常不利。即使对数百个文件大小只有几KB的文件进行查询,Athena都可以在一秒钟内提供结果。我会担心确保Athena首先读取正确的数据,然后再确保理想的文件大小。

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


更新:鉴于Firehose不允许您更改输入数据的目录结构,并且Glue通常很糟糕,并且您在注释中提供了其他上下文,所以我会做像这样的东西:

  • 创建一个Athena表,其中包含数据中所有属性的列,并将日期作为分区键。这是您的输入表,将仅对该表运行ETL查询。不必担心输入数据具有用于年,月和日的单独目录,您只需要一个分区键。将它们作为单独的分区键只会使事情复杂化,并且具有一个含义就是它可以是DATE类型,而不是每次都需要将其组合成一个日期的三个单独的STRING列进行日期计算。

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

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

    • 将新分区添加到输入表。如果您将状态机计划为每天运行一次,则只需添加与当前日期相对应的分区即可。使用Glue CreatePartition API调用来创建分区(不幸的是,这需要大量信息才能工作,但是您可以运行GetTable调用来获取分区。例如,使用["2019-04-29"]作为Values"s3://some-bucket/firehose/year=2019/month=04/day=29"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操作完成后,列出使用胶水GetPartitions创建的临时表中创建的分区,并使用BatchCreatePartitions在查询表中创建相同的分区。
    • 最后删除属于已删除查询表分区的所有文件,并删除由CTAS操作创建的临时表。

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

这看起来很多,看起来就像Glue Crawlers和Glue ETL所做的一样-但以我的经验,他们做起来并不容易。

在您的情况下,数据使用Hive样式分区进行了分区,这是Glue Crawlers理解的,但是在许多情况下,您不会得到Hive样式分区,而仅是Y / M / D(我实际上并不知道Firehose可以以这种方式传送数据,我认为它只是执行Y / M / D)。 Glue Crawler每次运行时也会做很多额外的工作,因为它不知道在何处添加了数据,但是您知道自昨天以来添加的唯一分区是昨天的分区,因此减少了爬网一键交易。

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

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

通过此ETL过程,您获得的数据不必担心按时间划分分区,但是您仍然可以获得针对查询进行优化的表。