ユーザー定義関数を使用したクエリ - Amazon Athena

ユーザー定義関数を使用したクエリ

Amazon Athena のユーザー定義関数 (UDF) を使用すると、レコードまたはレコードのグループを処理するためのカスタム関数を作成できます。UDF は、パラメータを受け入れ、作業を実行し、結果を返します。

Athena で UDF を使用するには、SQL クエリの USING EXTERNAL FUNCTION ステートメントの前に SELECT 句を記述します。この SELECT ステートメントは UDF を参照し、クエリの実行時に、UDF に渡される変数を定義します。SQL クエリは UDF を呼び出すときに、Java ランタイムを使用して Lambda 関数を呼び出します。UDF は、Java デプロイパッケージのメソッドとして Lambda 関数内で定義されます。Lambda 関数の同じ Java デプロイパッケージで複数の UDF を定義できます。また、USING EXTERNAL FUNCTION 句で Lambda 関数の名前も指定します。

Athena UDF 用の Lambda 関数のデプロイには 2 つのオプションがあり、Lambda を使用して直接関数をデプロイすることも、AWS Serverless Application Repository を使用することもできます。UDF の既存の Lambda 関数を検索するには、パブリック AWS Serverless Application Repository またはプライベートリポジトリを検索し、Lambda にデプロイします。また、Java ソースコードを作成または変更して、JAR ファイルにパッケージ化し、Lambda または AWS Serverless Application Repository を使用してデプロイすることもできます。これを開始するための Java ソースコードとパッケージの例については、「Lambda 使用した UDF の作成とデプロイ」を参照してください。Lambda の詳細については、AWS Lambda デベロッパーガイドを参照してください。AWS Serverless Application Repository の詳細については、AWS Serverless Application Repository 開発者ガイドを参照してください。

Athena で UDF を使用してテキストを翻訳および分析する例については、AWS Machine Learning ブログ記事「Amazon Athena、Amazon Translate、および Amazon Comprehend で SQL 関数を使用してテキストを翻訳および分析する」を参照、または video をご覧ください。

UDF を使用して Amazon Athena で地理空間クエリを拡張する例については、AWS Big Data Blog の「Extend geospatial queries in Amazon Athena with UDFs and AWS Lambda」を参照してください。

考慮事項と制約事項

  • 組み込み Athena 関数 – Athena の組み込み関数は、高い性能を発揮するように設計されています。可能な場合は、UDF よりも組み込み関数を使用することをお勧めします。組み込み関数の詳細については、「Amazon Athena の関数」を参照してください。

  • スカラー UDF 限定 – Athena は、一度に 1 行を処理し、単一の列値を返すスカラー UDF のみをサポートします。Athena は、Lambda を呼び出すたびに行のバッチを UDF に渡しましますが、これは並行的に行われる可能性があります。UDF とクエリを設計するときは、この処理がネットワークトラフィックに及ぼす可能性がある影響に注意する必要があります。

  • UDF ハンドラ関数は省略形式を使用 – UDF 関数には、省略形式 (フルフォーマットではない) を使用します (例:package.Class::method の代わりに package.Class)。

  • UDF メソッドは小文字を使用する必要がある – UDF メソッドは小文字にする必要があります。キャメルケースは使用できません。

  • パラメータを必要とする UDF メソッド — UDF メソッドには少なくとも 1 つの入力パラメータが必要です。入力パラメータなしで定義された UDF を呼び出そうとすると、ランタイム例外が発生する原因となります。UDF はデータレコードに対して関数を実行するためのものですが、引数のない UDF はデータを取り込まないため、例外が発生します。

  • Java ランタイムサポート – 現在、Athena UDF は Lambda について Java 8 と Java 11 ランタイムをサポートしています。詳細については、AWS Lambda デベロッパーガイドの「Java による Lambda 関数のビルド」を参照してください。

  • IAM 許可 – Athena で UDF クエリステートメントを作成して実行するには、クエリを実行する IAM プリンシパルに、Athena 関数以外のアクションを実行することが許可されている必要があります。詳細については、「Amazon Athena ユーザー定義関数 (UDF) を許可する IAM 許可ポリシーの例」を参照してください。

  • Lambda のクォータ – UDF には Lambda のクォータが適用されます。詳細については、AWS Lambda デベロッパーガイドの「Lambda のクォータ」を参照してください。

  • 行レベルのフィルタリング – UDF では、Lake Formation の行レベルのフィルタリングはサポートされていません。

  • ビュー – UDF でビューを使用することはできません。

  • 既知の問題 – 既知の問題に関する最新のリストについては、GitHub の awslabs/aws-athena-query-federation セクションにある「Limitations and Issues」(制限と問題) を参照してください。

動画

Athena での UDF の使用に関する詳細については、以下の動画をご覧ください。

動画: Amazon Athena のユーザー定義関数 (UDF) の概要

以下の動画は、Amazon Athena で UDF を使用して、機密情報のリダクションを行う方法を紹介しています。

注記

このビデオの構文はプレリリースですが、コンセプトは同じです。AmazonAthenaPreviewFunctionality ワークグループなしで Athena を使用してください。

動画:Amazon Athena で SQL クエリを使用したテキストフィールドの翻訳、分析、およびリダクション

以下の動画は、その他の AWS のサービス と共に Amazon Athena の UDF を使用して、テキストを翻訳し、分析する方法を紹介しています。

注記

この動画で示されている構文はリリース前のものですが、概念は同じです。正しい構文については、AWS Machine Learning ブログの関連するブログ記事「Amazon Athena、Amazon Translate、および Amazon Comprehend による SQL 関数を使用したテキストの翻訳、修正、分析」を参照してください。

UDF クエリ構文

USING EXTERNAL FUNCTION 句は、クエリで後続の SELECT ステートメントで参照できる UDF または複数の UDF を指定します。UDF のメソッド名と UDF をホストする Lambda 関数の名前が必要です。Lambda 関数名の代わりに、Lambda ARN を使用できます。クロスアカウントのシナリオでは、Lambda ARN が必要です。

概要

USING EXTERNAL FUNCTION UDF_name(variable1 data_type[, variable2 data_type][,...]) RETURNS data_type LAMBDA 'lambda_function_name_or_ARN' [, EXTERNAL FUNCTION UDF_name2(variable1 data_type[, variable2 data_type][,...]) RETURNS data_type LAMBDA 'lambda_function_name_or_ARN'[,...]] SELECT [...] UDF_name(expression) [, UDF_name2(expression)] [...]

パラメータ

USING EXTERNAL FUNCTION UDF_name(variable1 data_type[, variable2 data_type][,...])

UDF_name は UDF の名前を指定します。この名前は、参照される Lambda 関数内の Java メソッドに対応している必要があります。各 variable data_type は、UDF が入力として受け入れる名前付きの変数とそれに対応するデータ型を指定します。data_type は、以下の表に記載されているサポート対象の Athena データ型の 1 つであり、対応する Java データ型にマップする必要があります。

Athena データ型 Java データ型

TIMESTAMP

java.time.LocalDateTime (UTC)

DATE

java.time.LocalDate (UTC)

TINYINT

java.lang.Byte

SMALLINT

java.lang.Short

REAL

java.lang.Float

DOUBLE

java.lang.Double

DECIMAL (「RETURNS ノート」を参照)

java.math.BigDecimal

BIGINT

java.lang.Long

INTEGER

java.lang.Int

VARCHAR

java.lang.String

VARBINARY

byte[]

BOOLEAN

java.lang.Boolean

ARRAY

java.util.List

ROW

java.util.Map<文字列,オブジェクト>

RETURNS data_type

data_type は、UDF が出力として返す SQL データ型を指定します。上記の表にリストされている Athena データ型がサポートされます。DECIMAL データ型の場合、この構文 RETURNS DECIMAL(precision, scale) を使います。精度スケールは整数です。

LAMBDA 'lambda_function'

lambda_function は、UDF の実行時に呼び出される Lambda 関数の名前を指定します。

SELECT [...] UDF_name(expression) [...]

UDF に値を渡し、結果を返す SELECT クエリです。UDF_Name は使用する UDF を指定し、値を渡すために評価される expression がその後に続きます。渡される値と返される値は、USING EXTERNAL FUNCTION 句で UDF に指定された対応するデータ型と一致する必要があります。

GitHub にある AthenaUDFHandler.java コードに基づいたクエリの例については、GitHub の「Amazon Athena UDF connector」(Amazon Athena UDF コネクタ) ページを参照してください。

Lambda 使用した UDF の作成とデプロイ

カスタム UDF を作成するには、UserDefinedFunctionHandler クラスを拡張して新しい Java クラスを作成します。SDK の UserDefinedFunctionHandler.java のソースコードは、GitHub の awslabs/aws-athena-query-federation/athena-federation-sdk リポジトリで入手できます。このコードには、検証および変更して UDF を作成するために使用できる UDF 実装例が付属しています。

このセクションの手順では、コマンドラインとデプロイから Apache Maven を使用してカスタム UDF Jar ファイルを記述し、構築する方法を示します。

SDK のクローン作成と開発環境の準備

始める前に、sudo yum install git -y を使用して git がシステムにインストールされていることを確認してください。

AWS クエリフェデレーション SDK をインストールするには
  • コマンドラインで次のコマンドを入力して、SDK リポジトリのクローンを作成します。このリポジトリには、SDK、例、データソースコネクタのスイートが含まれています。データソースコネクタの詳細については、「Amazon Athena 横串検索の使用」を参照してください。

    git clone https://github.com/awslabs/aws-athena-query-federation.git
この手順の前提条件をインストールする

Apache Maven、AWS CLI、および AWS Serverless Application Model ビルドツールが既にインストールされている開発マシンで作業している場合は、このステップをスキップできます。

  1. クローン作成時に作成した aws-athena-query-federation ディレクトリのルートから、開発環境を準備する prepare_dev_env.sh スクリプトを実行します。

  2. インストールプロセスによって作成された新しい変数を調達するようにシェルを更新するか、ターミナルセッションを再起動します。

    source ~/.profile
    重要

    このステップをスキップすると、後で AWS CLI または AWS SAM ビルドツールが Lambda 関数を公開できないというエラーが表示されます。

Maven プロジェクトの作成

次のコマンドを実行して、Maven プロジェクトを作成します。groupId を組織の一意の ID に置き換え、my-athena-udf をアプリケーション名に置き換えます。詳細については、Apache Maven ドキュメントの「How do I make my first Maven project?」を参照してください。

mvn -B archetype:generate \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DgroupId=groupId \ -DartifactId=my-athena-udfs

Maven プロジェクトへの依存関係とプラグインの追加

Maven プロジェクト pom.xml ファイルに次の設定を追加します。例については、GitHub の pom.xml ファイルを参照してください。

<properties> <aws-athena-federation-sdk.version>2022.47.1</aws-athena-federation-sdk.version> </properties> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-athena-federation-sdk</artifactId> <version>${aws-athena-federation-sdk.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

UDF に Java コードを記述

UserDefinedFunctionHandler.java を拡張して、新しいクラスを作成します。クラス内に UDF を記述します。

次の例では、UDF の 2 つの Java メソッド、compress() および decompress() がクラス MyUserDefinedFunctions 内に作成されます。

*package *com.mycompany.athena.udfs; public class MyUserDefinedFunctions extends UserDefinedFunctionHandler { private static final String SOURCE_TYPE = "MyCompany"; public MyUserDefinedFunctions() { super(SOURCE_TYPE); } /** * Compresses a valid UTF-8 String using the zlib compression library. * Encodes bytes with Base64 encoding scheme. * * @param input the String to be compressed * @return the compressed String */ public String compress(String input) { byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8); // create compressor Deflater compressor = new Deflater(); compressor.setInput(inputBytes); compressor.finish(); // compress bytes to output stream byte[] buffer = new byte[4096]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputBytes.length); while (!compressor.finished()) { int bytes = compressor.deflate(buffer); byteArrayOutputStream.write(buffer, 0, bytes); } try { byteArrayOutputStream.close(); } catch (IOException e) { throw new RuntimeException("Failed to close ByteArrayOutputStream", e); } // return encoded string byte[] compressedBytes = byteArrayOutputStream.toByteArray(); return Base64.getEncoder().encodeToString(compressedBytes); } /** * Decompresses a valid String that has been compressed using the zlib compression library. * Decodes bytes with Base64 decoding scheme. * * @param input the String to be decompressed * @return the decompressed String */ public String decompress(String input) { byte[] inputBytes = Base64.getDecoder().decode((input)); // create decompressor Inflater decompressor = new Inflater(); decompressor.setInput(inputBytes, 0, inputBytes.length); // decompress bytes to output stream byte[] buffer = new byte[4096]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputBytes.length); try { while (!decompressor.finished()) { int bytes = decompressor.inflate(buffer); if (bytes == 0 && decompressor.needsInput()) { throw new DataFormatException("Input is truncated"); } byteArrayOutputStream.write(buffer, 0, bytes); } } catch (DataFormatException e) { throw new RuntimeException("Failed to decompress string", e); } try { byteArrayOutputStream.close(); } catch (IOException e) { throw new RuntimeException("Failed to close ByteArrayOutputStream", e); } // return decoded string byte[] decompressedBytes = byteArrayOutputStream.toByteArray(); return new String(decompressedBytes, StandardCharsets.UTF_8); } }

JAR ファイルのビルド

mvn clean install を実行して、プロジェクトをビルドします。正常にビルドされると、JAR ファイルが target という名前のプロジェクトの artifactId-version.jar フォルダに作成されます。ここで、artifactId は Maven プロジェクトで指定した名前(例: my-athena-udfs)です。

JAR を AWS Lambda にデプロイする

コードを Lambda にデプロイする、次の 2 つの方法があります。

  • AWS Serverless Application Repository を使用してデプロイする(推奨)

  • JAR ファイルから Lambda 関数を作成する

オプション 1: AWS Serverless Application Repository にデプロイする

JAR ファイルを AWS Serverless Application Repository にデプロイするときは、アプリケーションのアーキテクチャを表す AWS SAM テンプレート YAML ファイルを作成します。次に、この YAML ファイルと、アプリケーションのアーティファクトがアップロードされて AWS Serverless Application Repository に対して利用可能になる Simple Storage Service (Amazon S3) バケットを指定します。以下の手順では、先ほどクローンした Athena Query Federation SDK の athena-query-federation/tools ディレクトリにある publish.sh スクリプトを使用します。

詳細と要件については、「AWS Serverless Application Repository デベロッパーガイド」の「アプリケーションの公開」、「AWS Serverless Application Model デベロッパーガイド」の「AWS SAM テンプレートの概念」、および「AWS SAM CLI を使用してサーバーレスアプリケーションを公開する」を参照してください。

次の例は、YAML ファイルのパラメータを示しています。同様のパラメータを YAML ファイルに追加し、プロジェクトディレクトリに保存します。完全な例については、GitHub の athena-udf.yaml を参照してください。

Transform: 'AWS::Serverless-2016-10-31' Metadata: 'AWS::ServerlessRepo::Application': Name: MyApplicationName Description: 'The description I write for my application' Author: 'Author Name' Labels: - athena-federation SemanticVersion: 1.0.0 Parameters: LambdaFunctionName: Description: 'The name of the Lambda function that will contain your UDFs.' Type: String LambdaTimeout: Description: 'Maximum Lambda invocation runtime in seconds. (min 1 - 900 max)' Default: 900 Type: Number LambdaMemory: Description: 'Lambda memory in MB (min 128 - 3008 max).' Default: 3008 Type: Number Resources: ConnectorConfig: Type: 'AWS::Serverless::Function' Properties: FunctionName: !Ref LambdaFunctionName Handler: "full.path.to.your.handler. For example, com.amazonaws.athena.connectors.udfs.MyUDFHandler" CodeUri: "Relative path to your JAR file. For example, ./target/athena-udfs-1.0.jar" Description: "My description of the UDFs that this Lambda function enables." Runtime: java8 Timeout: !Ref LambdaTimeout MemorySize: !Ref LambdaMemory

YAML ファイルを保存したプロジェクトディレクトリに publish.sh スクリプトをコピーし、次のコマンドを実行します。

./publish.sh MyS3Location MyYamlFile

たとえば、バケットの場所が s3://DOC-EXAMPLE-BUCKET/mysarapps/athenaudf で、YAML ファイルが my-athena-udfs.yaml として保存された場合:

./publish.sh DOC-EXAMPLE-BUCKET/mysarapps/athenaudf my-athena-udfs
Lambda 関数を作成するには
  1. https://console.aws.amazon.com/lambda/ で Lambda コンソールを開いて [Create function] (関数の作成) をクリックし、[Browse serverless app repository] (Serverless Application Repository の参照) を選択します。

  2. [プライベートアプリケーション] を選択し、リストでアプリケーションを見つけるか、キーワードを使用してアプリケーションを検索して選択します。

  3. アプリケーションの詳細を確認して指定し、[デプロイ] を選択します。

    これで、Lambda 関数の JAR ファイルで定義されたメソッド名を Athena で UDF として使用できるようになりました。

オプション 2: Lambda 関数を直接作成する

コンソールまたは AWS CLI を使用して、Lambda 関数を直接作成することもできます。以下の例は、Lambda create-function CLI コマンドを使用する方法を示しています。

aws lambda create-function \ --function-name MyLambdaFunctionName \ --runtime java8 \ --role arn:aws:iam::1234567890123:role/my_lambda_role \ --handler com.mycompany.athena.udfs.MyUserDefinedFunctions \ --timeout 900 \ --zip-file fileb://./target/my-athena-udfs-1.0-SNAPSHOT.jar