大量のパーティションを持つデータソースにはAWS Glue Data Catalogパーティション・インデックスを有効にしてクエリを最適化しよう

2022.03.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS上のデータ基盤では、Athena,EMR, Redshift Spectrumからのデータアクセスに、AWS Glue Data Catalogのメタデータ管理し、

データ基盤では、データI/Oを軽減するために、パーティション分割がよく用いられます。

例えば、メタデータ管理サービスの AWS Glue Data Catalog がパーティション管理できるように、S3バケット上にデータを s3://BUCKET/PREFIX/year=2022/month=03/day=25/test.gz というようなパスで保存します。

パーティション化によって、スキャン量の軽減には成功するものの、経年とともにパーティション数があまりにも増えると、クエリ時間が大幅に伸びるという課題に遭遇するケースもありました。 パーティション一覧の取得と利用するパーティションの絞り込みに多くの時間を費やすためです。

例えば、リージョンx年x月x日x時間 でパーティションを切ると、10年分のデータのパーティション数は175万にものぼります。

Improve Performance with Amazon Athena's Latest Updates - AWS Online Tech Talks - YouTube から

AWS Glue Data Catalogで大量のパーティションがあるテーブルを管理している時は、パーティション・インデックスを検討しましょう。

パーティション・インデックスを有効化するには?

パーティションインデックスを有効化するには、次の2ステップが必要です。

  1. テーブルにパーティション・インデックスを作成
  2. テーブルのプロパティでパーティション・フィルタリングを有効化

1. テーブルにパーティション・インデックスを作成

AWS Glue Data Catalogのパーティション化されたテーブルにおいて、"Add new partition index" からパーティション・インデックスを追加します。

2. テーブルのプロパティでパーティション・フィルタリングを有効化

該当テーブルのプロパティにおいて、partition_filtering.enabled : true を追加します。

Athenaから Alter Table することも可能です。

ALTER TABLE partition_index.table_with_index
SET TBLPROPERTIES ('partition_filtering.enabled' = 'true')

パーティション・インデックスの効果を確かめる

パーティション・インデックスの効果を実感するには、大量のパーティションが存在するテーブルが必要です。

AWSの検証用CloudFormationを利用

AWSはそのようなS3バケットを用意してくれていて、CloudFormationスタックを流すだけでかんたんにこのバケットを利用したテーブルが作成されます。

$ aws s3 ls s3://awsglue-datasets/examples/highly-partitioned-table/
                           PRE year=1980/
                           PRE year=1981/
                           PRE year=1982/
                           ...
                           PRE year=2019/
                           PRE year=2020/
                           PRE year=2021/

$ aws s3 ls s3://awsglue-datasets/examples/highly-partitioned-table/year=2021/month=01/day=01/hour=00/
2021-04-05 04:35:59         75 data.json

このように

  • year
  • month
  • day
  • hour

でパーティションが切られた過去40年分のテーブルが存在すると、そのパーティション総数は

40(year) * 12(month) * 30(day) * 24(hour) = 345600 ≒ 35万パーティション です。

このS3バケットを AWS Glue Data Catalog でメタデータ管理します。

次のブログの "Launch Stack"からCloudFormationスタックを走らせましょう。

Improve Amazon Athena query performance using AWS Glue Data Catalog partition indexes | AWS Big Data Blog

  • table_with_index
  • table_without_index

という2つのデータカタログテーブルが作成されます。

テーブルtable_with_index 向けに、ブログ通りに

  • パーティション・インデックスの作成(インデックスの構築に約1時間)
  • テーブルプロパティからパーティション・フィルタリングの設定

を実施しましょう。

パーティション・インデックスの有無でクエリ時間を比較

AWS Glue Data Catalogと連携可能なサービスから、今回作成した2つのテーブルに対してクエリを投げます。

今回は計測しやすい Amazon Athena を利用しました。

SELECT count(*), sum(value) 
FROM partition_index.テーブル
WHERE year='2021' AND month='04' AND day='01'
table_without_index table_with_index
Time in queue(sec) 0.133 0.15
Run time(sec) 52.541 1.085
Data scanned(KB) 1.74 1.74

どちらもパーティションをつかっているので、スキャン範囲(Data scanned)は同じですが、 クエリの実行時間(Run time)を比較すると、パーティション・インデックスを使うことで、52.5秒から1秒へと1/50になりました。

API aws athena get-query-execution から、ブレイクダウンも確認します。

table_without_index table_with_index
DataScannedInBytes 1782 1782
EngineExecutionTimeInMillis 52541 1085
QueryPlanningTimeInMillis 51687 674
QueryQueueTimeInMillis 133 150
ServiceProcessingTimeInMillis 60 74
TotalExecutionTimeInMillis 52734 1309

2行目以降の処理時間に注目します。

TotalExecutionTimeInMillis(The number of milliseconds that Athena took to run the query)
= EngineExecutionTimeInMillis(The number of milliseconds that the query took to execute)
+ QueryQueueTimeInMillis(The number of milliseconds that the query was in your query queue waiting for resources) 
+ ServiceProcessingTimeInMillis(The number of milliseconds that Athena took to finalize and publish the query results after the query engine finished running the query)

という関係があり、パーティション情報の取得を含めたクエリープラニングフェーズ QueryPlanningTimeInMillisEngineExecutionTimeInMillis のサブセットです。

QueryExecutionTimeInMillis = EngineExecutionTimeInMillis - QueryPlanningTimeInMillis とおいて、 TotalExecutionTimeInMillis の内訳を確認します。

table_without_index table_with_index
QueryExecutionTimeInMillis 854 411
QueryPlanningTimeInMillis 51687 674
QueryQueueTimeInMillis 133 150
ServiceProcessingTimeInMillis 60 74

パーティション・インデックスが存在しない場合、Athenaクエリー処理時間のほとんど(98%)はクエリープラニングによるものであり、パーティション・インデックスを利用することで、このフェーズが51687msから674msへと1.3%に激減していることがわかります。

AWS Glue Data Catalogパーティション・インデックスとAthena Projectionの棲み分け

Athena のテーブルをパーティショニングする場合、AWS Glue Data Catalog以外にもPartition Projectionも使えます。

Partition Projectionは GetPartitions API を呼び出さないため、大量のパーティションにも強い一方で、Amazon Athenaにしか使えません。

一方で、今回紹介したパーティション・インデックスはAWS Glue Data Catalogの一機能のため、同カタログを利用するAthena, EMR, Redshift Spectrumなど様々なサービスがインデックスの効果を享受できます。

AWS上でデータレイクを構築し、様々なビッグデータサービスを利用している場合は、AWS Glue Data Catalogでメタデータを一元管理し、 AWS WAFなどのログ解析のためにピンポイントに S3/Athena だけを利用している場合は、Athena Projection も検討しましょう。

それでは。

参考

備考:aws athena get-query-execution の実行結果

# table_without_index
$ aws athena get-query-execution \
  --query-execution-id e71164b4-11bc-4524-a14c-e1cd573ffc3a \
  --query QueryExecution.Statistics --output table

--------------------------------------------
|             GetQueryExecution            |
+---------------------------------+--------+
|  DataScannedInBytes             |  1782  |
|  EngineExecutionTimeInMillis    |  52541 |
|  QueryPlanningTimeInMillis      |  51687 |
|  QueryQueueTimeInMillis         |  133   |
|  ServiceProcessingTimeInMillis  |  60    |
|  TotalExecutionTimeInMillis     |  52734 |
+---------------------------------+--------+

# table_with_index
$ aws athena get-query-execution \
  --query-execution-id 96f497e0-3273-44aa-aebd-336af1c656ce \
  --query QueryExecution.Statistics --output table

-------------------------------------------
|            GetQueryExecution            |
+---------------------------------+-------+
|  DataScannedInBytes             |  1782 |
|  EngineExecutionTimeInMillis    |  1085 |
|  QueryPlanningTimeInMillis      |  674  |
|  QueryQueueTimeInMillis         |  150  |
|  ServiceProcessingTimeInMillis  |  74   |
|  TotalExecutionTimeInMillis     |  1309 |
+---------------------------------+-------+