Amazon Athenaでパーティション分割を試してみる

2023/05/31に公開

Athenaにおける悩み

AthenaでWAFやELBのログを分析する際、ログが格納されている一番上のパスを指定してテーブルを作成すると、含まれるデータの範囲を気にする必要がない反面、スキャンサイズが大きくなるし、クエリ時間も長くなります。
だからと言って見たい範囲のデータに絞って毎回テーブルを作成し直すのもめんどくさいです。

パーティション分割

そんな悩みを解消するのがデータのパーティション分割です。
クエリによってスキャンされるデータの量を制限できるようになるため、パフォーマンスが向上や、コストの削減が見込めます。
https://docs.aws.amazon.com/ja_jp/athena/latest/ug/partitions.html

Apache Hive スタイルのパーティションの他に手動でパーティションを追加することで分割ができます。

AWSリソースのログ向けのテーブル作成方法

丁寧にAWS側でサンプルクエリが用意されています。

試してみる

WAFのログで実際にクエリを試してみようと思います。

テーブル作成

WAFのログ向けにパーティションを利用してテーブルを作成してみます。(2023年分のみ)

CREATE EXTERNAL TABLE `waflogs-2023`(
  `timestamp` bigint,
  `formatversion` int,
  `webaclid` string,
  `terminatingruleid` string,
  `terminatingruletype` string,
  `action` string,
  `terminatingRuleMatchDetails` array < struct < conditionType: string, location: string, matchedData: array < string > > >,
  `httpsourcename` string,
  `httpsourceid` string,
  `ruleGroupList` array < struct <
                    ruleGroupId: string,
                    terminatingRule: struct < ruleId: string, action: string >,
                    nonTerminatingMatchingRules: array < struct < action: string, ruleId: string > >,
                    excludedRules: array < struct < exclusionType: string, ruleId: string > >
   > >,
  `ratebasedrulelist` array< struct< ratebasedruleid:string, limitkey:string, maxrateallowed:int > >,
  `nonterminatingmatchingrules` array< struct< ruleid:string, action:string > >,
  `httprequest` struct<
                      clientip:string,
                      country:string,
                      headers:array< struct< name:string, value:string > >,
                      uri:string,
                      args:string,
                      httpversion:string,
                      httpmethod:string,
                      requestid:string
                      > 
)
PARTITIONED BY (
`date` string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://bucket/'
TBLPROPERTIES (
    'projection.enabled' = 'true',
    'projection.date.type' = 'date',
    'projection.date.range' = '2023/01/01,2023/12/31',
    'projection.date.format' = 'yyyy/MM/dd',
    'projection.date.interval' = '1',
    'projection.date.interval.unit' = 'DAYS',
    'storage.location.template' = 's3://bucket/${date}'
)

ちゃんとパーティション付きのテーブルができました!

クエリ実行

本当にスキャン範囲が制限できてクエリパフォーマンスが向上するのか、実際にクエリを実行して確認してみます。

パーティション分割したテーブル

SELECT
    DATE_FORMAT(FROM_UNIXTIME(logs.timestamp/1000, 'Asia/Tokyo') ,'%Y-%m-%d %h:%i:%s') as JST
FROM
    "default"."waflogs-2023" logs -- 2023年のデータが格納されたテーブル
WHERE
    logs."date" = '2023/04/11' 

キュー内の時間: 138 ミリ秒
実行時間: 6.387 秒
スキャンしたデータ: 86.07 MB

パーティション分割していないテーブル

SELECT
    DATE_FORMAT(FROM_UNIXTIME(logs.timestamp/1000, 'Asia/Tokyo') ,'%Y-%m-%d %h:%i:%s') as JST
FROM
    "default"."waflogs-2023-04" logs -- 2023年4月分のデータが格納されたテーブル
WHERE
    DATE_FORMAT(FROM_UNIXTIME(logs.timestamp/1000, 'Asia/Tokyo') ,'%Y-%m-%d') = '2023-04-11'

キュー内の時間: 178 ミリ秒
実行時間: 13.601 秒
スキャンしたデータ: 1.74 GB

実行結果

実行時間 スキャンしたデータ
パーティション分割したテーブル 6.387 秒 86.07 MB
パーティション分割していないテーブル 13.601 秒 1.74 GB

スキャンされたレコードが1日分のデータになり、クエリのパフォーマンスが向上しました!
何度も数GBのクエリを実行すると実行時間が伸びたり、コストが心配になってきますが、パーティション列を条件に含めれば問題がなさそうです。

※パーティション列を条件に加えないと全検索になってしまうので、注意が必要です。途中でキャンセルしたとしても、キャンセルまでにスキャンされたデータには課金されてしまいます。

Q: Athena でのクエリが失敗した場合、課金されますか?
クエリ単位の料金設定では、クエリが失敗しても料金は発生しません。
Q: Athena はキャンセルされたクエリに対して課金されますか?
はい。クエリをキャンセルした場合、クエリをキャンセルした時点までにスキャンされたデータ量に対して課金されます。
https://aws.amazon.com/jp/athena/faqs/

まとめ

パーティション分割によってパフォーマンスの向上やコスト最適化できることがわかりました。
ただし、全件検索には気をつけましょう。
全件検索した際の影響軽減のためにデータの範囲はある程度絞っておく方が良いかもしれません。

次回はパーティション射影(パーティション管理の自動化)についても試してみようと思います。

Discussion