Aurora PostgreSQL の新機能「aurora_analytics」で S3・S3 Tables の直接クエリを試してみた

Aurora PostgreSQL の新機能「aurora_analytics」で S3・S3 Tables の直接クエリを試してみた

Aurora PostgreSQL 17.11 でサポートされた aurora_analytics 拡張を使い、S3 の Parquet ファイルと S3 Tables の Iceberg テーブルを外部テーブルとして定義し、Aurora の業務データと ETL なしで JOIN できました。
2026.10.01

はじめに

2026年9月30日、Aurora PostgreSQL に aurora_analytics 拡張が加わり、S3 の Parquet ファイルと S3 Tables の Iceberg テーブルを PostgreSQL の外部テーブルとして直接クエリできるようになりました。

https://aws.amazon.com/about-aws/whats-new/2026/09/aurora-postgresql-query-apache-iceberg-and-parquet/

https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/query-iceberg-and-parquet-data.html

この記事では、S3 の Parquet ファイルを直接参照するシナリオと、S3 Tables の Iceberg テーブルを参照するシナリオの2つで外部テーブルを作りました。そして、それぞれを Aurora 内の業務テーブルと JOIN できることを確認しました。

検証環境

  • Aurora PostgreSQL 17.11
  • db.r8g.large(ap-northeast-1)
  • 接続: RDS Data API

aurora_analytics 拡張の対応バージョンは Aurora PostgreSQL 17.11 以上、または 18.6 以上です。

セットアップ

aurora_analytics の利用には、S3 を読み取る IAM ロールとカスタム DB クラスターパラメータグループが必要です。今回の構成(デフォルト VPC、パブリックアクセス無効)では VPC エンドポイントも必要でした。

IAM ロール・パラメータグループ・Aurora クラスターは CloudFormation テンプレートで一括作成しました。設定で押さえておくのは3つです。VPC エンドポイントだけはテンプレートに含めず手で作成したので、後述します。

1つ目は IAM ロールの関連付けです。AssociatedRoles の FeatureName には AuroraAnalytics を指定します。S3 インポート用の s3Import とは別の機能名です。ロールには、Parquet を置くバケットへの s3:GetObject などの権限を付けます。S3 Tables を読み取る場合は s3tables:GetTableData などの権限も追加します。

2つ目はパラメータグループです。デフォルトのパラメータグループは変更できません。そのため、aurora_analytics.enabled を "1" に設定したカスタムの DB クラスターパラメータグループを作り、クラスターに割り当てます。

AuroraAnalyticsRole:
  Type: AWS::IAM::Role
  Properties:
    RoleName: !Sub "${AWS::StackName}-AuroraAnalyticsRole"
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            Service: rds.amazonaws.com
          Action: sts:AssumeRole
    Policies:
      - PolicyName: AuroraAnalyticsS3Policy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
                - s3:ListBucket
                - s3:GetBucketLocation
              Resource:
                - !Sub "arn:aws:s3:::${S3BucketName}"
                - !Sub "arn:aws:s3:::${S3BucketName}/*"
            - Effect: Allow
              Action:
                - s3tables:GetTableData
                - s3tables:GetTable
                - s3tables:ListTables
                - s3tables:GetTableMetadataLocation
              Resource: "*"

ClusterParameterGroup:
  Type: AWS::RDS::DBClusterParameterGroup
  Properties:
    Description: Aurora PostgreSQL 17 custom parameter group with aurora_analytics enabled
    Family: aurora-postgresql17
    Parameters:
      aurora_analytics.enabled: "1"

DBCluster:
  Type: AWS::RDS::DBCluster
  Properties:
    Engine: aurora-postgresql
    EngineVersion: "17.11"
    DBClusterParameterGroupName: !Ref ClusterParameterGroup
    EnableHttpEndpoint: true
    AssociatedRoles:
      - RoleArn: !GetAtt AuroraAnalyticsRole.Arn
        FeatureName: AuroraAnalytics
CloudFormation テンプレート全体

検証用のテンプレートです。削除保護なし、S3 Tables の IAM Resource は * で広めに設定しています。VPC ID、サブネット ID、SG の CIDR は自環境の値に置き換えてください。SG の 443 は後述の S3 Tables エンドポイント用です。

AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Aurora PostgreSQL 17.11 / db.r8g.large cluster for aurora_analytics verification.
  Data API enabled, custom cluster parameter group (aurora_analytics.enabled=1),
  IAM role for S3 read access, auto-generated master password via Secrets Manager.

Parameters:
  DBMasterUsername:
    Type: String
    Default: postgres
    AllowedPattern: "[a-zA-Z][a-zA-Z0-9_]*"
    MinLength: 1
    MaxLength: 16

  S3BucketName:
    Type: String
    Description: S3 bucket name for Parquet files

Resources:
  DBSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub "${AWS::StackName}/db-master-password"
      GenerateSecretString:
        SecretStringTemplate: !Sub '{"username": "${DBMasterUsername}"}'
        GenerateStringKey: password
        PasswordLength: 32
        ExcludeCharacters: '"@/\'

  AuroraAnalyticsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-AuroraAnalyticsRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AuroraAnalyticsS3Policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${S3BucketName}"
                  - !Sub "arn:aws:s3:::${S3BucketName}/*"
              - Effect: Allow
                Action:
                  - s3tables:GetTableData
                  - s3tables:GetTable
                  - s3tables:ListTables
                  - s3tables:GetTableMetadataLocation
                Resource: "*"

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for Aurora analytics cluster
      SubnetIds:
        - "<subnet-id-1>"
        - "<subnet-id-2>"
        - "<subnet-id-3>"

  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SG for Aurora analytics cluster
      VpcId: "<vpc-id>"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 5432
          ToPort: 5432
          CidrIp: "<vpc-cidr>"
          Description: PostgreSQL from within the VPC
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: "<vpc-cidr>"
          Description: HTTPS for S3 Tables Interface endpoint

  ClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: Aurora PostgreSQL 17 custom parameter group with aurora_analytics enabled
      Family: aurora-postgresql17
      Parameters:
        aurora_analytics.enabled: "1"

  DBCluster:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      Engine: aurora-postgresql
      EngineVersion: "17.11"
      DBClusterIdentifier: !Sub "${AWS::StackName}-cluster"
      MasterUsername: !Sub "{{resolve:secretsmanager:${DBSecret}:SecretString:username}}"
      MasterUserPassword: !Sub "{{resolve:secretsmanager:${DBSecret}:SecretString:password}}"
      DBSubnetGroupName: !Ref DBSubnetGroup
      VpcSecurityGroupIds:
        - !Ref DBSecurityGroup
      DBClusterParameterGroupName: !Ref ClusterParameterGroup
      EnableHttpEndpoint: true
      StorageEncrypted: true
      DeletionProtection: false
      BackupRetentionPeriod: 1
      AssociatedRoles:
        - RoleArn: !GetAtt AuroraAnalyticsRole.Arn
          FeatureName: AuroraAnalytics

  DBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceClass: db.r8g.large
      DBInstanceIdentifier: !Sub "${AWS::StackName}-instance"
      Engine: aurora-postgresql
      DBClusterIdentifier: !Ref DBCluster
      PubliclyAccessible: false

  SecretAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties:
      SecretId: !Ref DBSecret
      TargetId: !Ref DBCluster
      TargetType: AWS::RDS::DBCluster

Outputs:
  ClusterArn:
    Value: !Sub "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster:${AWS::StackName}-cluster"
  SecretArn:
    Value: !Ref DBSecret
  IAMRoleArn:
    Value: !GetAtt AuroraAnalyticsRole.Arn

3つ目は VPC エンドポイントです。参照するソースごとに種類が違いました。

  • Parquet(S3 直接参照): S3 Gateway 型 VPC エンドポイント(com.amazonaws.ap-northeast-1.s3)。クラスターのサブネットが使うルートテーブルに関連付けます。プライベート配置のインスタンスはパブリック IP を持たないため、デフォルト VPC に IGW があっても S3 には出られません。エンドポイントを作る前は、外部テーブルへのクエリが curlCode: 28, Timeout で失敗しました。NAT Gateway でも到達できますが、今回は Gateway 型エンドポイントを選びました。
  • S3 Tables(Iceberg): S3 Tables Interface 型 VPC エンドポイント(com.amazonaws.ap-northeast-1.s3tables)。プライベート DNS を有効にします。今回はクラスターの SG をエンドポイントにも流用し、TCP 443 のインバウンド(送信元: VPC の CIDR)を許可しました。テンプレートの SG に 443 のルールがあるのはこのためです。

シナリオ①: S3 の Parquet ファイルを外部テーブルとして JOIN する

まず CREATE EXTENSION で拡張を作成します。このとき、外部テーブルが参照する外部サーバー aurora_analytics_server も自動作成されます。

CREATE EXTENSION aurora_analytics;

続いて Parquet ファイルを外部テーブルとして定義します。列定義は空の () のままで、スキーマは Parquet ファイルから自動推論されます。location に S3 URI、format に 'parquet'、region を指定します。

CREATE FOREIGN TABLE IF NOT EXISTS transaction_history_parquet ()
  SERVER aurora_analytics_server
  OPTIONS (
    location 's3://<bucket>/data/transaction_history.parquet',
    format 'parquet',
    region 'ap-northeast-1'
  );

定義後、SELECT COUNT(*) で 1019 行を読めることを確認しました。

この外部テーブルを、Aurora 内の通常テーブル recent_transactions(直近 12 件の取引を入れた、業務テーブルに見立てたテーブル)と顧客 ID で JOIN しました。外部テーブルであることを意識した書き方は不要で、通常の PostgreSQL の JOIN 構文のままです。

SELECT
    r.customer_id,
    COUNT(h.transaction_id)              AS history_count,
    ROUND(SUM(h.amount)::numeric, 0)     AS history_total
FROM recent_transactions r
JOIN transaction_history_parquet h
    ON r.customer_id = h.customer_id
GROUP BY r.customer_id
ORDER BY history_total DESC;

このクエリの統計です(値は1回の実行のもの。取得方法は「S3 読み取りとキャッシュの確認方法」で説明します)。

query: SELECT r.customer_id, COUNT(h.transaction_id) AS history_count, ROUND(SU
calls      : 1
s3_read    : 0 bytes      (analytics_remote_read_bytes)
cache_hit  : 32992 bytes  (analytics_cache_hit_bytes)
mean_ms    : 71.2 ms      (mean_exec_time)
result_rows: 6

calls は「この SQL 文の実行回数」で、外部テーブルへの初回アクセスという意味ではありません。JOIN の前に実行した SELECT COUNT(*) で Parquet ファイルがキャッシュに載っていたため、この JOIN では S3 からの読み取りがゼロでした。

シナリオ②: S3 Tables の Iceberg テーブルを外部テーブルとして JOIN する

S3 Tables の場合は、location にテーブルの ARN を指定します。ARN から形式とリージョンが自動判別されるため、format と region は省略できます。

CREATE FOREIGN TABLE IF NOT EXISTS transaction_history_s3tables ()
  SERVER aurora_analytics_server
  OPTIONS (
    location 'arn:aws:s3tables:ap-northeast-1:<account>:bucket/<bucket>/table/<table-id>'
  );

こちらも SELECT COUNT(*) で 1019 行を確認してから JOIN しました。JOIN の書き方は、参照する外部テーブル名を transaction_history_s3tables に変えるだけで、SQL の構造はシナリオ①と同じです。

このクエリの統計です(値は1回の実行のもの)。

query: SELECT r.customer_id, COUNT(h.transaction_id) AS history_count, ROUND(SU
calls      : 1
s3_read    : 13195 bytes  (analytics_remote_read_bytes)
cache_hit  : 24919 bytes  (analytics_cache_hit_bytes)
mean_ms    : 54.4 ms      (mean_exec_time)
result_rows: 6

こちらは COUNT(*) を先に流していても一部を S3 から読んでいます。Iceberg はメタデータファイルとデータファイルに分かれているため、COUNT(*) だけではキャッシュに載らない部分があったと考えています。なお、シナリオ①②の実行時間はキャッシュの状態が揃っていないので、Parquet と S3 Tables の性能比較には使えません。

S3 読み取りとキャッシュの確認方法

外部テーブルへのクエリが S3 からどれだけ読み取ったか、キャッシュでどれだけ賄えたかは、pg_catalog.aurora_analytics_stat_statements() で確認できます。

SELECT
    LEFT(query, 80)                        AS query,
    calls,
    analytics_remote_read_bytes            AS s3_read,
    analytics_cache_hit_bytes              AS cache_hit,
    ROUND(mean_exec_time::numeric, 1)      AS mean_ms,
    rows                                   AS result_rows
FROM pg_catalog.aurora_analytics_stat_statements()
ORDER BY stats_since DESC;

制限事項

制限 内容
読み取り専用 INSERT / UPDATE / DELETE 不可
未対応の型 JSONB・配列型・ネットワーク型・範囲型
リージョン 単一クエリ内は同一リージョンのデータのみ
キャッシュ DB 再起動・フェイルオーバーでクリア

https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-analytics-limitations.html

まとめ

Aurora PostgreSQL 17.11 の aurora_analytics 拡張で、S3 の Parquet と S3 Tables の Iceberg を外部テーブルとして定義し、Aurora の業務テーブルと通常の JOIN 構文で結合できました。

これまで S3 上の Parquet や Iceberg を Aurora のデータと組み合わせるには、Glue などで Aurora に取り込むか、Aurora から Lambda を呼び出して取得する必要がありました。

https://dev.classmethod.jp/articles/invoke-lambda-from-amazon-aurora-postgresql/

今回の拡張では、外部テーブルを定義した後はクエリで参照するテーブル名を変えるだけです。ETL も中間テーブルも不要でした。

外部テーブルは読み取り専用で、キャッシュは再起動やフェイルオーバーで消えます。業務 DB の最新データと S3 の履歴データを突き合わせる参照系の分析が、まず試しやすい用途です。S3 のデータレイクと Aurora のデータを組み合わせて分析している場合は、ぜひお試しください。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事