Criar e implantar uma UDF com o Lambda - Amazon Athena

Criar e implantar uma UDF com o Lambda

Para criar uma UDF personalizada, crie uma nova classe Java ao estender a classe UserDefinedFunctionHandler. O código-fonte para UserDefinedFunctionHandler.java no SDK está disponível no GitHub no repositório awslabs/aws-athena-query-federation/athena-federation-sdk, junto com um exemplo de implementações de UDF que você pode analisar e modificar para criar uma UDF personalizada.

As etapas nessa seção demonstram a criação e compilação de um arquivo Jar da UDF usando Apache Maven da linha de comando e uma implantação.

Execute as etapas a seguir para criar uma UDF personalizada para o Athena usando o Maven

Clonar o SDK e preparar o ambiente de desenvolvimento

Antes de começar, certifique-se de que o git esteja instalado no sistema usando sudo yum install git -y.

Como instalar o Query Federation SDK da AWS
  • Digite o seguinte na linha de comando para clonar o repositório do SDK. Este repositório inclui o SDK, exemplos e um conjunto de conectores de fonte de dados. Para obter mais informações sobre conectores de fonte de dados, consulte Usar a consulta federada do Amazon Athena.

    git clone https://github.com/awslabs/aws-athena-query-federation.git
Como instalar pré-requisitos para este procedimento

Se você trabalha em uma máquina de desenvolvimento que já tem o Apache Maven, a AWS CLI e a ferramenta de compilação do AWS Serverless Application Model instalados, pode ignorar esta etapa.

  1. Na raiz do diretório aws-athena-query-federation criado ao executar a clonagem, execute o script prepare_dev_env.sh que prepara o ambiente de desenvolvimento.

  2. Atualize o shell para fornecer novas variáveis criadas pelo processo de instalação ou reinicie a sessão do terminal.

    source ~/.profile
    Importante

    Se você ignorar essa etapa, receberá erros posteriormente sobre a AWS CLI ou a ferramenta de compilação do AWS SAM não conseguir publicar a função do Lambda.

Criar um projeto Maven

Execute o seguinte comando para criar o projeto Maven. Substitua groupId pelo ID exclusivo da organização e substitua my-athena-udf pelo nome do aplicativo. Para obter mais informações, consulte Como criar meu primeiro projeto Maven? na documentação Apache Maven.

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

Adicionar dependências e plugins ao projeto Maven

Adicione as configurações a seguir ao arquivo pom.xml do projeto Maven. Para ver um exemplo, consulte o arquivo pom.xml no GitHub.

<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>

Escrever código Java para UDFs

Crie uma nova classe ao estender UserDefinedFunctionHandler.java. Escreva as UDFs dentro da classe.

No exemplo a seguir, dois métodos Java para UDFs, compress() e decompress(), são criados dentro da classe 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); } }

Compilar o arquivo JAR

Execute mvn clean install para compilar o projeto. Após a compilação com êxito, um arquivo JAR é criado na pasta target do projeto nomeado como artifactId-version.jar, no qual artifactId é o nome fornecido por você no projeto Maven, por exemplo, my-athena-udfs.

Implantar o JAR no AWS Lambda

Você tem duas opções para implantar o código no Lambda:

  • Implantar usando AWS Serverless Application Repository (recomendado)

  • Criar uma função do Lambda do arquivo JAR

Opção 1: implantar no AWS Serverless Application Repository

Ao implantar o arquivo JAR no AWS Serverless Application Repository, você cria um arquivo YAML de modelo do AWS SAM que representa a arquitetura do aplicativo. Em seguida, você especifica esse arquivo YAML e um bucket do Amazon S3 no qual os artefatos da aplicação são carregados e disponibilizados para o AWS Serverless Application Repository. O procedimento abaixo usa o script publish.sh localizado no diretório athena-query-federation/tools do Athena Query Federation SDK clonado anteriormente.

Para obter mais informações e conhecer os requisitos, consulte Publicar aplicações no Guia do desenvolvedor do AWS Serverless Application Repository, Conceitos de modelo do AWS SAM no Guia do desenvolvedor do AWS Serverless Application Model e Publicar aplicações com tecnologia sem servidor usando a CLI do AWS SAM.

O exemplo a seguir demonstra parâmetros em um arquivo YAML. Adicione parâmetros similares ao arquivo YAML e salve-o no diretório de projetos. Consulte athena-udf.yaml no GitHub para ver o exemplo completo.

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

Copie o script publish.sh para o diretório de projetos no qual você salvou o arquivo YAML e execute o comando:

./publish.sh MyS3Location MyYamlFile

Por exemplo, se o local do bucket for s3://amzn-s3-demo-bucket/mysarapps/athenaudf e o arquivo YAML for salvo como my-athena-udfs.yaml:

./publish.sh amzn-s3-demo-bucket/mysarapps/athenaudf my-athena-udfs
Criar uma função do Lambda
  1. Abra o console do Lambda em https://console.aws.amazon.com/lambda/, escolha Create function (Criar função) e selecione Browse serverless app repository (Procurar repositório de aplicações sem servidor)

  2. Selecione Private applications (Aplicativos privados), localize o aplicativo na lista ou procure usando palavras-chave e selecione-o.

  3. Verifique, forneça detalhes do aplicativo e selecione Deploy (Implantar).

    Agora você pode usar os nomes dos métodos definidos no arquivo JAR da função do Lambda como UDFs no Athena.

Opção 2: criar uma função do Lambda diretamente

Você também pode criar uma função do Lambda diretamente usando o console ou a AWS CLI. O exemplo a seguir demonstra o uso do comando da CLI create-function do Lambda.

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