[Amazon Athena] INSERT INTO クエリ実行の前後で Glue パーティション作成は必要なのか確認してみた

2023.06.16

こんにちは、CX事業本部 Delivery部の若槻です。

Amazon AthenaINSERT INTO クエリを使用すると、Glue テーブルにデータを作成することができます。

この時、「INSERT INTO クエリの実行前に Glue パーティションが作成されている必要はあるんだっけ?」 そして 「INSERT INTO クエリ実行時にパーティションが自動で作成されるから、実行後の別途作成は不要なんだっけ?」 というのが気になりました。

パーティションは、データ参照を高速化するために用いられます。AWS ではデータセットのメタデータとして AWS Glue Data Catalog で管理されます。例えばS3バケット上にデータを s3://BUCKET/PREFIX/year=2023/month=06/day=16/XXXXXXXXXXXXXXXXX.gz という形式のパスで保存した場合は、year, month, day の3つのパーティションキーを持つテーブルを作成することで、パーティションを利用してデータを参照することができます。

このように S3 bucket および Glue Data Catalog で管理しているデータセットを Athena でクエリするようなシステムでは、パーティションの利用はほぼ必須となります。

今回は、Amazon Athena での INSERT INTO クエリ実行の前後で、この Glue パーティションの作成が必要なのか確認してみました。

先に結論

  • INSERT INTO クエリの実行前後でパーティション作成は不要。
    • パーティションが作成されていなくても、INSERT INTO クエリでデータを作成できる。
    • INSERT INTO クエリ実行により、作成したデータのキーでパーティションが自動で作成される。
  • ただし、一度の INSERT INTO クエリ実行により作成できるパーティションは100個まで。

確認してみた

実際にパーティションキーを持つ Glue テーブルに対して INSERT INTO クエリを実行して確認してみます。

検証用の環境構築

AWS CDK(TypeSCript)で必要なリソースを作成します。

lib/cdk-sample-stack.ts

import { aws_s3, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

import * as glue_alpha from '@aws-cdk/aws-glue-alpha';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const myDestinationBucket = new aws_s3.Bucket(this, 'MyDestinationBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
    });

    const myDatabase = new glue_alpha.Database(this, 'MyDatabase', {
      databaseName: 'my_database',
    });

    new glue_alpha.Table(this, 'MyDestinationTable', {
      database: myDatabase,
      tableName: 'my_destination_table',
      // パーティションキー
      partitionKeys: [
        {
          name: 'year',
          type: glue_alpha.Schema.STRING,
        },
        {
          name: 'month',
          type: glue_alpha.Schema.STRING,
        },
        {
          name: 'day',
          type: glue_alpha.Schema.STRING,
        },
      ],
      // カラム
      columns: [
        {
          name: 'id',
          type: glue_alpha.Schema.STRING,
        },
        {
          name: 'timestamp',
          type: glue_alpha.Schema.FLOAT,
        },
      ],
      dataFormat: glue_alpha.DataFormat.JSON,
      bucket: myDestinationBucket,
      s3Prefix: 'data',
    });
  }
}

作成する Glue テーブルにはパーティションキーを設定しています。

INSERT INTO の実行

テーブル作成直後なので、パーティションは未作成です。

$ aws glue get-partitions \
  --database-name my_database \
  --table-name my_destination_table
{
    "Partitions": []
}

INSERT INTO クエリを実行します。

INSERT INTO my_database.my_destination_table
VALUES
    ('d001', 1686920823, '2023', '06', '16')

クエリを実行することができました。

再度テーブルのパーティションを取得してみると、作成したデータのキーでパーティションが作成されていることが確認できます。

$ aws glue get-partitions \
  --database-name my_database \
  --table-name my_destination_table
{
    "Partitions": [
        {
            "Values": [
                "2023",
                "06",
                "16"
            ],
            "DatabaseName": "my_database",
            "TableName": "my_destination_table",
            "CreationTime": "2023-06-16T22:11:31+09:00",
            "StorageDescriptor": {
                "Columns": [
                    {
                        "Name": "id",
                        "Type": "string",
                        "Comment": ""
                    },
                    {
                        "Name": "timestamp",
                        "Type": "float",
                        "Comment": ""
                    }
                ],
                "Location": "s3://cdksamplestack-mydestinationbucket77f58013-140r8wj6gh5zo/data/year=2023/month=06/day=16",
                "InputFormat": "org.apache.hadoop.mapred.TextInputFormat",
                "OutputFormat": "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat",
                "Compressed": false,
                "NumberOfBuckets": 0,
                "SerdeInfo": {
                    "Name": "my_destination_table",
                    "SerializationLibrary": "org.openx.data.jsonserde.JsonSerDe",
                    "Parameters": {}
                },
                "SortColumns": [],
                "Parameters": {},
                "StoredAsSubDirectories": false
            },
            "Parameters": {
                "should_skip_create": "false",
                "presto_query_id": "20230616_131130_00017_9iy5n",
                "presto_version": "0.215-18065-ge2f002b"
            },
            "CatalogId": "XXXXXXXXXXXX"
        }
    ]
}

SELECT クエリでデータを取得してみます。

SELECT * FROM my_database.my_destination_table

パーティションが作成されているので、データを取得することができました。

制限:一度の INSERT INTO クエリ実行により作成できるパーティションは100個まで。

これで万事よしかと思いきや、INSERT INTO クエリ実行時のパーティション作成には制限がありました。

The INSERT INTO statement supports writing a maximum of 100 partitions to the destination table. If you run the SELECT clause on a table with more than 100 partitions, the query fails unless the SELECT query is limited to 100 partitions or fewer.

INSERT INTO ステートメントは、宛先テーブルへの最大 100 個のパーティションの書き込みをサポートします。 100 を超えるパーティションを持つテーブルで SELECT 句を実行すると、SELECT クエリが 100 パーティション以下に制限されていない限り、クエリは失敗します。

以下エントリで紹介したような、SELECT クエリで別テーブルから取得したデータを INSERT INTO で別テーブルに書き込むようなケースでは、パーティションが100個を超える可能性があるので注意が必要そうです。

まとめ

  • パーティションが作成されていないキーに対しても、INSERT INTO クエリでデータを作成できる。
  • INSERT INTO クエリ実行により、作成したデータのキーでパーティションが自動で作成される。
  • 制限として、100個を超えるパーティションを作成する場合は、INSERT INTO クエリを複数回に分けて実行する必要がある。

参考

以上