Aurora PostgreSQL の新機能「aurora_analytics」で S3・S3 Tables の直接クエリを試してみた
はじめに
2026年9月30日、Aurora PostgreSQL に aurora_analytics 拡張が加わり、S3 の Parquet ファイルと S3 Tables の Iceberg テーブルを PostgreSQL の外部テーブルとして直接クエリできるようになりました。
この記事では、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 再起動・フェイルオーバーでクリア |
まとめ
Aurora PostgreSQL 17.11 の aurora_analytics 拡張で、S3 の Parquet と S3 Tables の Iceberg を外部テーブルとして定義し、Aurora の業務テーブルと通常の JOIN 構文で結合できました。
これまで S3 上の Parquet や Iceberg を Aurora のデータと組み合わせるには、Glue などで Aurora に取り込むか、Aurora から Lambda を呼び出して取得する必要がありました。
今回の拡張では、外部テーブルを定義した後はクエリで参照するテーブル名を変えるだけです。ETL も中間テーブルも不要でした。
外部テーブルは読み取り専用で、キャッシュは再起動やフェイルオーバーで消えます。業務 DB の最新データと S3 の履歴データを突き合わせる参照系の分析が、まず試しやすい用途です。S3 のデータレイクと Aurora のデータを組み合わせて分析している場合は、ぜひお試しください。







