

# Lambda를 사용하여 UDF 생성 및 배포
<a name="udf-creating-and-deploying"></a>

사용자 지정 UDF를 생성하려면 `UserDefinedFunctionHandler` 클래스를 확장하여 새 Java 클래스를 생성합니다. SDK의 [UserDefinedFunctionHandler.java](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-federation-sdk/src/main/java/com/amazonaws/athena/connector/lambda/handlers/UserDefinedFunctionHandler.java)용 소스 코드는 GitHub의 awslabs/aws-athena-query-federation/athena-federation-sdk [리포지토리](https://github.com/awslabs/aws-athena-query-federation/tree/master/athena-federation-sdk)에서 이용할 수 있습니다. 이와 함께 사용자 지정 UDF를 생성하기 위해 검토하고 수정할 수 있는 [UDF 구현 예제](https://github.com/awslabs/aws-athena-query-federation/tree/master/athena-udfs)도 제공합니다.

이 섹션의 단계에서는 명령줄과 배포에서 [Apache Maven](https://maven.apache.org/index.html)을 사용하여 사용자 지정 UDF Jar 파일을 작성하고 구축하는 방법을 보여줍니다.

Maven을 사용하여 Athena용 사용자 지정 UDF를 생성하려면 다음 단계를 수행합니다.

1. [SDK 복제 및 개발 환경 준비](#udf-create-install-sdk-prep-environment)

1. [Maven 프로젝트 만들기](#create-maven-project)

1. [Maven 프로젝트에 종속성 및 플러그인 추가](#udf-add-maven-dependencies)

1. [UDF에 대한 Java 코드 작성](#udf-write-java)

1. [JAR 파일 구축](#udf-create-package-jar)

1. [AWS Lambda에 JAR 배포](#udf-create-deploy)

## SDK 복제 및 개발 환경 준비
<a name="udf-create-install-sdk-prep-environment"></a>

시작하기 전에 git가 `sudo yum install git -y`를 사용하여 시스템에 설치되어 있는지 확인하세요.

**AWS query federation SDK를 설치하려면**
+ 명령줄에 다음을 입력하여 SDK 리포지토리를 복제합니다. 이 리포지토리에는 SDK, 예제 및 데이터 소스 커넥터 제품군이 포함되어 있습니다. 데이터 소스 커넥터에 대한 자세한 내용은 [Amazon Athena 페더레이션 쿼리 사용](federated-queries.md) 단원을 참고하세요.

  ```
  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\$1dev\$1env.sh](https://github.com/awslabs/aws-athena-query-federation/blob/master/tools/prepare_dev_env.sh) 스크립트를 실행합니다.

1. 을 업데이트하여 설치 프로세스에서 생성된 새 변수를 소싱하거나 터미널 세션을 다시 시작합니다.

   ```
   source ~/.profile
   ```
**중요**  
이 단계를 건너뛰면 나중에 Lambda 함수를 게시할 수 없는 AWS CLI 또는 AWS SAM 빌드 도구에 대한 오류가 발생합니다.

## Maven 프로젝트 만들기
<a name="create-maven-project"></a>

다음 명령을 실행하여 Maven 프로젝트를 만듭니다. *groupId*를 고유한 조직 ID로 바꾸고 *my-athena-udf*를 애플리케이션 이름으로 바꿉니다. 자세한 내용은 Apache Maven 문서에서 [첫 번째 Maven 프로젝트를 만들려면 어떻게 해야 합니까?](https://maven.apache.org/guides/getting-started/index.html#How_do_I_make_my_first_Maven_project)를 참조하세요.

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

## Maven 프로젝트에 종속성 및 플러그인 추가
<a name="udf-add-maven-dependencies"></a>

Maven 프로젝트 `pom.xml` 파일에 다음 구성을 추가합니다. 예를 들어 GitHub의 [pom.xml](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-udfs/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 코드 작성
<a name="udf-write-java"></a>

[UserDefinedFunctionHandler.java](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-federation-sdk/src/main/java/com/amazonaws/athena/connector/lambda/handlers/UserDefinedFunctionHandler.java)를 확장하여 새 클래스를 만듭니다. 클래스 내부에 UDF를 작성합니다.

다음 예제에서는 UDF에 대한 두 개의 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 파일 구축
<a name="udf-create-package-jar"></a>

`mvn clean install`을 실행하여 프로젝트를 빌드합니다. 성공적으로 빌드되면 JAR 파일이 `artifactId-version.jar`이라는 프로젝트의 `target` 폴더에 생성됩니다. 여기서 *artifactId*는 Maven 프로젝트에서 제공한 이름입니다(예: `my-athena-udfs`).

## AWS Lambda에 JAR 배포
<a name="udf-create-deploy"></a>

Lambda에 코드를 배포하는 두 가지 옵션이 있습니다.
+ AWS Serverless Application Repository을 사용하여 배포(권장)
+ JAR 파일에서 Lambda 함수 만들기

### 옵션 1: AWS Serverless Application Repository에 배포
<a name="udf-create-deploy-sar"></a>

AWS Serverless Application Repository에 JAR 파일을 배포할 때 애플리케이션의 아키텍처를 나타내는 AWS SAM 템플릿 YAML 파일을 만듭니다. 그런 다음 이 YAML 파일과, 애플리케이션의 아티팩트를 업로드하여 AWS Serverless Application Repository에 공개하는 Amazon S3 버킷을 지정합니다. 아래 절차에서는 앞서 복제했던 Athena Query Federation SDK의 `athena-query-federation/tools` 디렉터리에 위치한 [publish.sh](https://github.com/awslabs/aws-athena-query-federation/blob/master/tools/publish.sh) 스크립트를 사용합니다.

자세한 내용과 요구 사항은 *AWS Serverless Application Repository 개발자 안내서*의 [애플리케이션 게시](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverlessrepo-publishing-applications.html), *AWS Serverless Application Model 개발자 안내서*의 [AWS SAM 템플릿 개념](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html) 및 [AWS SAM CLI를 사용하여 서버리스 애플리케이션 게시](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html)를 참조하세요.

다음 예제에서는 YAML 파일의 파라미터를 보여줍니다. YAML 파일에 유사한 파라미터를 추가하고 프로젝트 디렉터리에 저장합니다. 전체 예제는 GitHub에서 [athena-udf.yaml](https://github.com/awslabs/aws-athena-query-federation/blob/master/athena-udfs/athena-udfs.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://amzn-s3-demo-bucket/mysarapps/athenaudf`이고 YAML 파일이 `my-athena-udfs.yaml`로 저장된 경우:

```
./publish.sh amzn-s3-demo-bucket/mysarapps/athenaudf my-athena-udfs
```

**Lambda 함수 생성**

1. [https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)에서 Lambda 콘솔을 열고 **함수 생성**을 선택한 다음 **서버리스 앱 리포지토리 찾아보기**를 선택합니다.

1. **프라이빗 애플리케이션**을 선택하거나 목록에서 애플리케이션을 찾거나 키워드를 사용하여 애플리케이션을 검색한 다음 선택합니다.

1. 애플리케이션 세부 정보를 검토하고 제공한 다음 **배포**를 선택합니다.

   이제 Lambda 함수 JAR 파일에 정의된 메서드 이름을 Athena의 UDF로 사용할 수 있습니다.

### 옵션 2: 직접 Lambda 함수 생성
<a name="udf-create-deploy-lambda"></a>

콘솔 또는 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
```