はじめに
Athenaではスキャンしたデータによって料金が課金されます。
AthenaでWAFログなどを分析する際、全ログに対してテーブルを作成すると、分析に必要ない範囲まで読み込んでしまうことによって、無駄な料金がかかり、検索時間も長くなってしまいます。
この悩みは Projection Partition を使用して、解消することができます。
本記事は、 Projection Partition を使用してWAFログをパーティション分割し、コスト削減および検索時間を短縮する方法について紹介します。
Partition Projection とは?
パーティション分割を自動化できるのが Partition Projection
です。
通常、パーティション分割されたテーブルを作成するには、手動でパーティション分割用のクエリを実行する必要があります。
ですが、 Projection Partition を使用してテーブルを作成した場合、クエリに記載されたパーティション構造に沿ってデータをパーティション分割します。
新しいデータがS3に格納された場合、パーティション構造に沿って自動でパーティションが作成されます。
パーティション分割されたデータをクエリすることで、スキャン対象が減り、Athenaのコストを削減することができます。
Projection Partition を使用したテーブルの作成のポイント
以下はAWS公式ドキュメントで記載されている、WAFログを Projection Partition を使用して、テーブルを作成するクエリ例になります。参照
このクエリをもとに、Projection Partition を使用してテーブルを作成する際のポイントについて説明します。
CREATE EXTERNAL TABLE `waf_logs`(
`timestamp` bigint,
`formatversion` int,
`webaclid` string,
`terminatingruleid` string,
`terminatingruletype` string,
// <一部省略>
`ja3Fingerprint` string
)
PARTITIONED BY (
`region` string,
`date` string)
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://DOC-EXAMPLE-BUCKET/AWSLogs/accountID/WAFLogs/region/DOC-EXAMPLE-WEBACL/'
TBLPROPERTIES(
'projection.enabled' = 'true',
'projection.region.type' = 'enum',
'projection.region.values' = 'us-east-1,us-west-2,eu-central-1,eu-west-1',
'projection.date.type' = 'date',
'projection.date.range' = '2021/01/01,NOW',
'projection.date.format' = 'yyyy/MM/dd',
'projection.date.interval' = '1',
'projection.date.interval.unit' = 'DAYS',
'storage.location.template' = 's3://DOC-EXAMPLE-BUCKET/AWSLogs/accountID/WAFLogs/${region}/DOC-EXAMPLE-WEBACL/${date}/')
PARTITIONED BY句
ここに、パーティションしたいカラム名および型を指定します。
PARTITIONED BY (
`region` string,
`date` string)
ROW FORMAT など
次のクエリは、データの形式や、入力・出力データのフォーマットを指定するクエリになります。
個人的に、このあたりの記述は Projection Partition のテーブルの作成に重要なポイントではないと思ってます。
公式ドキュメントに、AWSサービスの各ログ毎にクエリが用意されているので、
素直にそちらの記述をコピーすればよいと思います。
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 句
LOCATION
句には、データの格納場所を指定します。
LOCATION
's3://DOC-EXAMPLE-BUCKET/AWSLogs/accountID/WAFLogs/region/DOC-EXAMPLE-WEBACL/'
TBLPROPERTIES 句
ここが一番のポイントとなります。
TBLPROPERTIES
句に、パーティションの構造を記述します。
、 Projection Partition に使用するプロパティ等については、こちらのドキュメントを参照してください。
TBLPROPERTIES(
'projection.enabled' = 'true',
'projection.region.type' = 'enum',
'projection.region.values' = 'us-east-1,us-west-2,eu-central-1,eu-west-1',
'projection.date.type' = 'date',
'projection.date.range' = '2021/01/01,NOW',
'projection.date.format' = 'yyyy/MM/dd',
'projection.date.interval' = '1',
'projection.date.interval.unit' = 'DAYS',
'storage.location.template' = 's3://DOC-EXAMPLE-BUCKET/AWSLogs/accountID/WAFLogs/${region}/DOC-EXAMPLE-WEBACL/${date}/')
-
projection.enabled
- Projection Partition が有効になっているか。
true
で有効。
- Projection Partition が有効になっているか。
-
projection.{columnName}.type
- パーティションの型を記述する。{columnName}には、
PARTITIONED BY
句で記載したカラムを記載する。 - 型によって、必要なパーティションプロパティが変わってくる。指定できる型についてはこちら
- パーティションの型を記述する。{columnName}には、
-
projection.{columnName}.values
- enum型を使用する場合に必須なプロパティ。カンマ区切りのリストを記述する。
-
projection.{columnName}.range
- 日付の範囲を指定するプロパティ。date型を使用する場合に必須。Javaのjava.time.*日付型と互換性のある形式で、最小値と最大値を記述する。
-
projection.{columnName}.format
- 日付のフォーマットを指定するプロパティ。date型を使用する場合に必須となる。Javaの日付フォーマットに沿った形式を使用できる。Javaの日付フォーマットはこちら
-
projection.{columnName}.interval
-
projection.{columnName}.interval.unit
- パーティションの間隔を決定することができるプロパティ。date型で使用できるプロパティ。
interval.unit
で時間単位を決め、interval
で、間隔をきめる 。
指定しない場合、1日または1ヶ月の間隔になる。
- パーティションの間隔を決定することができるプロパティ。date型で使用できるプロパティ。
-
storage.location.template
- パーティションを含んだS3のパスを指定する。パーティションのキーは${}で囲む。
Projection Partition を使用したテーブルの作成
WAFログが以下の構造で格納されているS3に格納されているとします。
このデータに対してProjection Partitionのテーブルを作成していきます。
aws-waf-logs-sample
└── AWSLogs
└── Account_ID
└── WAFLogs
└── ap-northeast-1
├── test-alb-waf
│ └── 2023
│ └── 12
│ └── 10
│ └── 08
│ └── 30
│ ├── Account_ID_waflogs_ap-northeast-1_test-alb-waf_20231210T0830Z_27be5e3e.log.gz
│ └── Account_ID_waflogs_ap-northeast-1_test-alb-waf_20231210T0830Z_bc2aed45.log.gz
└── test-waf
└── 2023
└── 12
├── 10
│ ├── 15
│ │ └── 50
│ │ ├── Account_ID_waflogs_ap-northeast-1_test-waf_20231210T1550Z_16ab0d2d.log.gz
│ │ └── Account_ID_waflogs_ap-northeast-1_test-waf_20231210T1550Z_2f4ac5f8.log.gz
│ └── 17
│ └── 05
│ └── Account_ID_waflogs_ap-northeast-1_test-waf_20231210T1705Z_a6e54bde.log.gz
└── 11
└── 00
├── 30
│ └── Account_ID_waflogs_ap-northeast-1_test-waf_20231211T0030Z_0ebb1860.log.gz
└── 35
└── Account_ID_waflogs_ap-northeast-1_test-waf_20231211T0035Z_40869bfc.log.gz
パーティション分割したい箇所の把握
まずは、パーティション分割したい箇所はどこかを把握することがポイントになります。
例のS3オブジェクトの構造では、WAFのWebACL毎にログが出力されているので、WebACLと日付毎に分けたいと思います。
パーティション箇所 | 分割単位 |
---|---|
WebACL | test-alb-waf、test-waf |
日付 | 1日毎 |
PARTITIONED BY句
パーティション箇所を英語にしたカラム名を記述します。
PARTITIONED BY (
`webacl` string,
`date` string)
LOCATION 句
WebACLのWAFログはリージョン配下にデータが格納されてますので、LOCATION
句は次の記述とします。
LOCATION
's3://aws-waf-logs-sample/AWSLogs/Account_ID/WAFLogs/ap-northeast-1/'
TBLPROPERTIES 句
ここにはパーティション分割したい箇所の把握の内容に沿って、記述していきます。
例の場合、以下の記述になると思います。
TBLPROPERTIES(
'projection.enabled' = 'true',
'projection.webacl.type' = 'enum',
'projection.webacl.values' = 'test-alb-waf,test-waf',
'projection.date.type' = 'date',
'projection.date.range' = '2023/12/01,NOW',
'projection.date.format' = 'yyyy/MM/dd',
'projection.date.interval' = '1',
'projection.date.interval.unit' = 'DAYS',
'storage.location.template' = 's3://aws-waf-logs-sample/AWSLogs/Account_ID/WAFLogs/ap-northeast-1/${webacl}/${date}')
完成したクエリ
最終的に以下のクエリが完成しました!では、実際にテーブルを作成して、パーティション分割されているか確認していきましょう。
CREATE EXTERNAL TABLE `waf_logs`(
`timestamp` bigint,
`formatversion` int,
`webaclid` string,
`terminatingruleid` string,
`terminatingruletype` string,
// <一部省略>
`ja3Fingerprint` string
)
PARTITIONED BY (
`webacl` string,
`date` string)
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://aws-waf-logs-sample/AWSLogs/Account_ID/WAFLogs/ap-northeast-1/'
TBLPROPERTIES(
'projection.enabled' = 'true',
'projection.webacl.type' = 'enum',
'projection.webacl.values' = 'test-alb-waf,test-waf',
'projection.date.type' = 'date',
'projection.date.range' = '2023/12/01,NOW',
'projection.date.format' = 'yyyy/MM/dd',
'projection.date.interval' = '1',
'projection.date.interval.unit' = 'DAYS',
'storage.location.template' = 's3://aws-waf-logs-sample/AWSLogs/Account_ID/WAFLogs/ap-northeast-1/${webacl}/${date}')
パーティションされているかの確認
まず、最初にテーブルの全データに対してクエリを行います。
SELECT ("timestamp", "webacl", "date")
FROM "default"."waf_logs";
スキャンしたデータが7.14KBになっていることを確認しました。
次に日付を指定して、データをクエリしてみます。
SELECT "timestamp",
"webacl",
"date"
FROM "default"."waf_logs"
WHERE "date" = '2023/12/11';
スキャンしたデータが2.3KBと、スキャン容量が少なくなっていることを確認できました!
無事にパーティション分割されていそうです!