この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部 IoT事業部の若槻です。
今回は、Amazon AthenaでALTER TABLE ADD PARTITIONコマンドを使用してnon-Hiveなパーティションを追加してみました。
パーティションのStyleにはHiveとnon-Hiveがある
Amazon Athenaのデータパーティショニングでは、Hiveおよびnon-Hive(非Hive)の2つのStyleが使用できます。
Athena can use Apache Hive style partitions, whose data paths contain key value pairs connected by equal signs (for example,
country=us/
... oryear=2021/month=01/day=26/...
). Thus, the paths include both the names of the partition keys and the values that each path represents. To load new Hive partitions into a partitioned table, you can use the MSCK REPAIR TABLE command, which works only with Hive-style partitions.Athena can also use non-Hive style partitioning schemes. For example, CloudTrail logs and Kinesis Data Firehose delivery streams use separate path components for date parts such as
data/2021/01/26/us/6fc7845e.json
. For such non-Hive style partitions, you use ALTER TABLE ADD PARTITION to add the partitions manually.
Hive StyleはKey-Valueが=
で連結されたyear=2021/month=01/day=26/
のような形式を取り、パーティションの作成はMSCK REPAIR TABLEコマンドで行います。
一方で、non-Hive Styleは2021/01/26/
のような形式を取り、パーティションの作成はALTER TABLE ADD PARTITIONコマンドで行います。
AWSだとAmazon Kinesis Data Firehoseのデータ出力がこのnon-Hive Styleを取ります。
そこで今回は、S3 Bucketのnon-Hive Styleなパスに対して、ALTER TABLE ADD PARTITIONコマンドを使用してパーティションを追加してみました。
やってみた
環境準備
AWS CDK v2(TypeScript)で次のようなCDKスタックを作成して、必要な環境を準備します。
lib/process-stack.ts
import { Construct } from 'constructs';
import {
aws_s3,
aws_athena,
RemovalPolicy,
Stack,
StackProps,
} from 'aws-cdk-lib';
import * as glue from '@aws-cdk/aws-glue-alpha';
export class ProcessStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
// ソースデータ格納バケット
const sourceDataBucket = new aws_s3.Bucket(this, 'sourceDataBucket', {
bucketName: `data-${this.account}`,
removalPolicy: RemovalPolicy.DESTROY,
});
// Athenaクエリ結果格納バケット
const athenaQueryResultBucket = new aws_s3.Bucket(
this,
'athenaQueryResultBucket',
{
bucketName: `athena-query-result-${this.account}`,
removalPolicy: RemovalPolicy.DESTROY,
},
);
// データカタログ
const dataCatalog = new glue.Database(this, 'dataCatalog', {
databaseName: 'data_catalog',
});
// データカタログテーブル
new glue.Table(this, 'sourceDataGlueTable', {
tableName: 'source_data_glue_table',
database: dataCatalog,
bucket: sourceDataBucket,
s3Prefix: 'data/',
partitionKeys: [
{
name: 'dt',
type: glue.Schema.STRING,
},
],
dataFormat: glue.DataFormat.JSON,
columns: [
{
name: 'userId',
type: glue.Schema.STRING,
},
{
name: 'count',
type: glue.Schema.INTEGER,
},
],
});
// Athenaワークグループ
new aws_athena.CfnWorkGroup(this, 'athenaWorkGroup', {
name: 'athenaWorkGroup',
workGroupConfiguration: {
resultConfiguration: {
outputLocation: `s3://${athenaQueryResultBucket.bucketName}/result-data`,
},
},
});
}
}
- データソースのデータカタログとして、パーティションキーが
dt
、カラムがuserId
およびcount
のGlueテーブルを作成しています。 - AWS CDK v2(aws-cdk-lib)にはAWS GlueのL2 ConstructのClassがまだ無いため、alphaモジュール(@aws-cdk/aws-glue-alpha)を導入しています。
@aws-cdk/aws-glue-alpha
含めCDKモジュールはバージョンをすべて統一するようにします。(しなければインストール時にエラーが発生します)
$ npm ls aws-cdk-lib aws-cdk @aws-cdk/aws-glue-alpha --depth=0
project@0.1.0 /path/to/project
├── @aws-cdk/aws-glue-alpha@2.29.1-alpha.0
├── aws-cdk-lib@2.29.1
└── aws-cdk@2.29.1
上記をCDK Deployしてスタックをデプロイします。
JSON Lines形式のデータを作成します。
$ cat data1
{"userId":"u001","count":3}
{"userId":"u001","count":1}
{"userId":"u002","count":5}
{"userId":"u002","count":8}
{"userId":"u003","count":2}
$ cat data2
{"userId":"u001","count":14}
{"userId":"u002","count":10}
{"userId":"u003","count":12}
$ cat data3
{"userId":"u001","count":4}
{"userId":"u002","count":4}
{"userId":"u003","count":0}
各ファイルをそれぞれS3 Bucketの/2022/06/26
、/2022/06/27
および/2022/06/28
のパスにアップロードします。
aws s3 cp data1 s3://${BUCKET_NAME}/data/2022/06/26/data1
aws s3 cp data2 s3://${BUCKET_NAME}/data/2022/06/27/data2
aws s3 cp data3 s3://${BUCKET_NAME}/data/2022/06/28/data3
パーティションを作成してみる
この時点でGlueテーブルにパーティションは作成されていません。
$ aws glue get-partitions \
--database-name ${GLUE_DATABASE_NAME} \
--table-name ${DATA_SOURCE_GLUE_TABLE_NAME}
{
"Partitions": []
}
なのでSELECTクエリを実行しても結果は取得できません。
SELECT *
FROM "data_catalog"."source_data_glue_table"
WHERE dt = '2022-06-28'
下記のALTER TABLE ADD PARTITIONクエリを実行します。S3 Bucketのdata/2022/06/28/
のパスに対してdt = '2022-06-28'
のパーティションを作成するクエリです。
ALTER TABLE data_catalog.source_data_glue_table ADD
PARTITION (dt = '2022-06-28')
LOCATION 's3://data-XXXXXXXXXXXX/data/2022/06/28/'
上記クエリを実行します。
するとLocations3://data-XXXXXXXXXXXX/data/2022/06/28/
に対するパーティション2022-06-28
が作成されたため、CLIでも取得できるようになりました。
$ aws glue get-partitions \
--database-name ${GLUE_DATABASE_NAME} \
--table-name ${DATA_SOURCE_GLUE_TABLE_NAME}
{
"Partitions": [
{
"Values": [
"2022-06-28"
],
"DatabaseName": "data_catalog",
"TableName": "source_data_glue_table",
"CreationTime": "2022-06-28T21:11:02+09:00",
"StorageDescriptor": {
"Columns": [
{
"Name": "userid",
"Type": "string"
},
{
"Name": "count",
"Type": "int"
}
],
"Location": "s3://data-XXXXXXXXXXXX/data/2022/06/28/",
"InputFormat": "org.apache.hadoop.mapred.TextInputFormat",
"OutputFormat": "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat",
"Compressed": false,
"NumberOfBuckets": 0,
"SerdeInfo": {
"SerializationLibrary": "org.openx.data.jsonserde.JsonSerDe"
},
"SortColumns": [],
"StoredAsSubDirectories": false
},
"CatalogId": "XXXXXXXXXXXX"
}
]
}
またSELECTクエリで2022-06-28
パーティションからデータを取得できるようになりました!
SELECT *
FROM "data_catalog"."source_data_glue_table"
WHERE dt = '2022-06-28'
追加で2022-06-27
および2022-06-26
のパーティションも作成します。
ALTER TABLE data_catalog.source_data_glue_table ADD
PARTITION (dt = '2022-06-27')
LOCATION 's3://data-XXXXXXXXXXXX/data/2022/06/27/'
PARTITION (dt = '2022-06-26')
LOCATION 's3://data-XXXXXXXXXXXX/data/2022/06/26/'
すると他のパーティションへのクエリもできるようになりました!
SELECT *
FROM "data_catalog"."source_data_glue_table"
WHERE dt <= '2022-06-28'
AND count > 3
おわりに
Amazon AthenaでALTER TABLE ADD PARTITIONコマンドでnon-Hiveなパーティション追加をしてみました。
Athenaではパーティションを作成する方法はいくつかあり迷うことがあったので、ALTER TABLE ADD PARTITIONを試すと同時に両者の整理ができて良かったです。
参考
以上