S3メタデータからAthena UDFを使用して署名付きURLを生成してみた
こんにちは、データ事業本部の渡部です。
今回はS3バケットに保存した画像ファイルのメタデータが集積されたS3メタデータテーブルから、AthenaとUDF(ユーザー定義関数)を使用して署名付きURLをクエリ結果として出力してみます。
S3メタデータとは
S3メタデータはS3オブジェクトのメタデータを自動的にテーブルへ収集する、re:Invent2024で発表された新機能となります。
画像などの膨大になりがちな非構造データ群から欲しいファイルを、S3メタデータテーブルから検索して、容易に特定することを可能とします。
取得できるメタデータとしては、自動的に付与されるシステム定義メタデータ
とオブジェクトをS3に入れる時に任意に設定できるユーザー定義メタデータ
の2種類があります。
今回はユーザー定義メタデータでデータをフィルタ検索して、S3メタデータテーブルにあるバケットとオブジェクトキーから、署名付きURLを取得してみます。
システム定義メタデータ
とユーザー定義メタデータ
については以下をご参照ください。
なおS3メタデータと同じような機能として、S3インベントリがありますが、こちらは日次or週次でのメタデータ一覧化、かつメタデータについてもユーザー定義メタデータは一覧に載りません。
対してS3メタデータはニアリアルタイムでメタデータが更新され、取得可能なメタデータの種類も多いため、より素早く具体的な検索が可能です。
今回の構成
やりたいことのイメージとしては以下となります。
やってみる
S3の準備
まずはS3バケット・S3テーブルバケット・S3メタデータ統合を作成・設定します。
# S3バケットの作成
aws s3 mb s3://cm-watanabe-metadata \
--region us-east-1
# S3メタデータテーブル用のS3テーブルバケットの作成
aws s3tables create-table-bucket \
--name cm-watanabe-table-bucket \
--region us-east-1 \
# S3メタデータ統合の設定
aws s3api create-bucket-metadata-table-configuration \
--bucket cm-watanabe-metadata \
--region us-east-1 \
--metadata-table-configuration '{
"S3TablesDestination": {
"TableBucketArn": "arn:aws:s3tables:us-east-1:<account-id>:bucket/cm-watanabe-table-bucket",
"TableName": "cm_watanabe_meta_table"
}
}'
今回はAWS CLIで作成しましたが、もちろんマネジメントコンソールでも作成可能です。
その手順については以下に詳しく載っていますので、ご確認ください。
S3バケットにメタデータとともにファイル配置
この時点ではS3メタデータテーブルにクエリをしても何も表示されません。バケットにファイルを格納していないので、当たり前ですね。
というわけで、ファイルをローカルPCからS3バケットへ配置します。
何のファイルを配置すべきか小一時間悩んだのですが、弊社公式マスコットキャラクターである「くらにゃん」を3枚配置しました。
その際、ユーザー定義メタデータとしてはlocation
というKeyを用意し、それぞれの画像に{hibiya,osaka,fukuoka}
というValueを同時に渡しました。
# ファイル配置
aws s3 cp ./clanyan1.jpg s3://cm-watanabe-metadata \
--metadata '{"location":"hibiya"}' \
aws s3 cp ./clanyan2.jpg s3://cm-watanabe-metadata \
--metadata '{"location":"osaka"}' \
aws s3 cp ./clanyan3.jpg s3://cm-watanabe-metadata \
--metadata '{"location":"fukuoka"}' \
ところでユーザー定義メタデータとオブジェクトタグどちらにメタデータを付与すればいいの?
少し話は逸れるのですが、
上記ではユーザー定義メタデータに対してメタデータを付与しましたが、S3のオブジェクトにはタグも付与することが可能です。
こちらもKey-Valueで値を渡せて、かつメタデータテーブルから取得することができるのですが、一体どちらをメタデータとして使用すればいいのでしょうか?
以下に比較をしてみました。
評価観点 | ユーザー定義メタデータ | オブジェクトタグ |
---|---|---|
メタデータ更新 | アップロード後はメタデータ更新できない 変更するにはオブジェクトを上書きする必要がある |
オブジェクト上書きすることなく、タグを柔軟に更新、追加、削除できる |
サイズ/数の制限 | keyとvalueのペアの合計サイズに制限あり(約2KB) | オブジェクトに対しては最大10個のタグが付与可能。各タグのkeyは ≤ 128文字、valueは ≤ 256文字 |
文字の制限 | 特別な規定はない | keyとvalueは、アルファベット(a-z, A-Z)、数字(0-9)、_ 、. 、/ 、= 、+ 、- 、: 、@ のみを含む(正規表現:^([\p{L}\p{Z}\p{N}_.:/=+\-]*)$ )※ = 、+ 、& 、? 、およびASCII以外の文字はサポートされていない |
JSONのサポート | HTTPヘッダーの制限を超えない場合、JSON文字列を保存できる | JSONの直接保存はサポートされていない |
ユースケース | 検索用メタデータの管理:オブジェクトにメタデータを付与して、バージョン、作成日、および作成者を特定する。 | ・ライフサイクル管理:オブジェクトタグをつけることで、オブジェクト単位でのライフサイクル管理が可能 ・コスト管理:オブジェクトをプロジェクトやユーザーグループに分類するためにタグを使用し、コストの追跡と分析を容易にする。 ※オブジェクトタグはAWSのコスト分析ツールで簡単に使用され、詳細なレポートを作成する。 |
ユースケースにまとめを載せているのですが、私の個人的な意見だと、
オブジェクトタグは全社的に中央で管理したいライフサイクル管理だったり、コスト管理に使用すべきで、
今回のような検索用メタデータとしてはユーザー定義メタデータを使用すべきかなと思います。
オブジェクトタグにしかできないことをオブジェクトタグにやらせる、なんていったってオブジェクトタグは10個しか使用できませんので。という理由です。
Athena UDFたるLambdaのデプロイ
閑話休題。
Athenaのユーザー定義関数(UDF)として使用するLambdaをデプロイします。
今回はSAMを準備したので、そちらを以下に共有します。
SAMのディレクトリ構成はざっくりと以下です。
.
├── README.md
├── athena_udf_lambda
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
athena-udf
Globals:
Function:
Timeout: 300
Parameters:
PythonVersion:
Type: String
Description: Python version
AllowedValues:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
ConstraintDescription: Must be one of the allowed values.
Resources:
AthenaUDFFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: athena_udf_lambda/
FunctionName: athena_s3_presigned_url_udf
Handler: app.lambda_handler
Runtime: !Sub "python${PythonVersion}"
MemorySize: 256
Architectures:
- x86_64
Policies:
- AmazonS3ReadOnlyAccess
Outputs:
AthenaUDFFunction:
Description: "Athena UDF Function ARN"
Value: !GetAtt AthenaUDFFunction.Arn
samconfig.toml
# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1
[default.global.parameters]
stack_name = "athena-udf"
[default.build.parameters]
cached = true
parallel = true
[default.validate.parameters]
lint = true
[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
s3_prefix = "athena-udf"
region = "us-east-1"
image_repositories = []
[default.package.parameters]
resolve_s3 = true
[default.sync.parameters]
watch = true
[default.local_start_api.parameters]
warm_containers = "EAGER"
[default.local_start_lambda.parameters]
warm_containers = "EAGER"
app.py
import boto3
import athena_udf
from botocore.exceptions import ClientError
# S3クライアントを初期化してAmazon S3とやり取りします
s3_client = boto3.client('s3')
class PresignedUrlUDF(athena_udf.BaseAthenaUDF):
"""
PresignedUrlUDFクラスはBaseAthenaUDFから継承され、AWS Athenaのクエリを処理し、
S3オブジェクトに対する事前署名付きURLを返します。
主なメソッド:
- handle_athena_record: Athenaクエリの入力を受け取り、事前署名付きURLを生成して返します。
"""
@staticmethod
def handle_athena_record(input_schema, output_schema, arguments):
"""
Athena UDFのレコードを処理し、S3オブジェクトの事前署名付きURLを生成します。
引数:
input_schema (list): UDFの入力スキーマ(このロジックでは使用されません)。
output_schema (list): UDFの出力スキーマ(このロジックでは使用されません)。
arguments (list): UDFに渡される引数のリスト。引数は以下の通り:
- arguments[0]: bucket_name (str) - S3バケットの名前。
- arguments[1]: object_key (str) - バケット内のオブジェクトのキー。
- arguments[2] (オプション): expiration (int) - URLの有効期限(秒単位)。デフォルトは3600秒(1時間)。
戻り値:
str: S3オブジェクトの事前署名付きURL。
例外:
ValueError: bucket_name または object_key が不足している場合に発生します。
"""
# argumentsからbucket_nameとobject_keyを取得
bucket_name = arguments[0]
object_key = arguments[1]
expiration = int(arguments[2]) if len(
arguments) > 2 else 3600 # デフォルトは1時間
# bucket_nameとobject_keyが空でないことを確認
if not bucket_name or not object_key:
raise ValueError(
"Both bucket_name and object_key must be provided.")
# presigned URLを生成
presigned_url = generate_presigned_url(
bucket_name, object_key, expiration)
return presigned_url
def generate_presigned_url(bucket_name, object_key, expiration):
"""
S3オブジェクトのダウンロード用に事前署名付きURLを生成します。
引数:
bucket_name (str): S3バケットの名前。
object_key (str): バケット内のオブジェクトのキー。
expiration (int): URLの有効期限(秒単位)。
戻り値:
str: S3オブジェクトの事前署名付きURL。
例外:
ValueError: 事前署名付きURLの生成中にエラーが発生した場合。
"""
try:
# S3からオブジェクトをダウンロードするためのpresigned URLを生成
url = s3_client.generate_presigned_url(
'get_object',
Params={'Bucket': bucket_name, 'Key': object_key},
ExpiresIn=expiration
)
return url
except ClientError as e:
raise ValueError(f"Error generating presigned URL: {str(e)}")
# UDFの`lambda_handler`関数にlambda_handlerを割り当て
lambda_handler = PresignedUrlUDF().lambda_handler
requirements.txt
athena_udf
SAMのビルド・デプロイをしました。
sam build --parameter-overrides PythonVersion=3.11
sam deploy --parameter-overrides PythonVersion=3.11
Lake Formationでのアクセス制御
メタデータテーブルが格納されるS3 TablesはLake Formationでのアクセス許可をしなければ、アクセスできないため、アクセス許可をします。
Lake FormationのData Permission
でCatalog・Database・Tableに対してアクセス許可設定をしました。
Athenaでクエリ
まずはメタデータテーブルに対して、全件SELECTをします。
色々ファイルを何回か放り込んだ後なので、レコードが多くなってしまっていますが、ここではclanyan.{1,2,3}.jpgがメタデータとして出力されていることを見てください。
以下は抜粋結果です。user_metadata
に拠点のメタデータが格納されています。
# | bucket | key | user_metadata |
---|---|---|---|
3 | cm-watanabe-metadata | clanyan1.jpg | {location=hibiya} |
4 | cm-watanabe-metadata | clanyan3.jpg | {location=fukuoka} |
7 | cm-watanabe-metadata | clanyan2.jpg | {location=osaka} |
メタデータテーブルのテーブルスキーマは以下をご参考ください。
続けて、location = hibiya
のレコードだけ抽出してみました。
SELECT * FROM "cm_watanabe_meta_table" WHERE user_metadata['location']='hibiya'
ここから本題のUDFを使用してAthenaで署名付きURLを取得します。
SQLにUDFをかませて返り値とともに出力します。
以下のSQL、上から3行が関数の宣言で、4行以降がSELECT文となっています。
USING EXTERNAL FUNCTION s3_presigned_url(bucket VARCHAR, key VARCHAR)
RETURNS VARCHAR
LAMBDA 'arn:aws:lambda:us-east-1:<account-id>:function:athena_s3_presigned_url_udf'
SELECT
bucket,
key,
s3_presigned_url(bucket, key) as presigned_url
FROM "aws_s3_metadata"."cm_watanabe_meta_table"
WHERE user_metadata['location'] = 'hibiya'
署名付きURLが取得できました。
署名付きURLをブラウザで開いてみると、無事くらにゃん1が表示されました。
さいごに
S3メタデータを使用して、オブジェクトの検索から署名付きURLの発行までしてみました。
このクエリ結果をもとに、より検索からデータのアクセスまで短時間で可能となります。
注意点としては署名付きURLの期限は払い出した元のクレデンシャルの有効期限に依存するということです。
今回の署名付きURLを払い出したLambdaのIAM Roleのクレデンシャルはデフォルトの1時間のため、1時間が経つとアクセス不可能となります。
もっと長い有効期限を持たせたい場合は、クレデンシャルの期限を伸ばすか(最長12時間)、アクセスキー・シークレットアクセスキーを用いた払い出しにするか(最長7日)を検討ください。
それでは、ありがとうございました。