Snowflakeの外部関数からLambda関数を実行する

## <はじめに>
**API統合**を使うことで、Snowflakeの**外部関数**からAWSのLambda関数を呼び出すことができます。

SnowflakeからAPI Gatewayを経由してLambda関数を呼び出し、関数の実行結果をSnowflakeに返します。

今後のSnowflakeの運用を踏まえ、API統合によるLambda関数の実行を検証してみました。
今回の検証では、LambdaのPythonプログラムからAmazon Comprehendを呼び出し、外部関数の引数に対して感情分析させました。

## <アーキテクチャ>
Snowflakeの外部関数でLambda関数を呼び出し、Comprehendの感情分析を行う処理フローは以下①～⑧になります。
①SnowflakeでSQLから**外部関数**を実行する。
②外部関数から**API Gateway**が呼び出され、関数の引数が連携される。
③API Gatewayが**Lambda関数**を呼び出す。
④Lambda関数がPythonのコードを実行し、**Comprehend**に引数を渡す。
⑤Comprehendが引数に対して**感情分析**を行い、戻り値(Positive、Negative、Neautral)を返す。
⑥Lambda関数が戻り値をAPI Gatewayに返す。
⑦API GatewayがLambda関数の戻り値を外部関数に連携する。
⑧外部関数が呼出元のSQLに戻り値を返す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/a3e70270-4dcf-dd22-96c2-8b52f01aae49.png)

## <前提条件>
・Snowflakeのエディションは、Business Criticalエディション以上であること。
・API Gatewayは、Snowflakeと同じリージョンで作成すること。
・Lambdaは、Snowflakeと同じリージョンで作成すること。
・Comprehendは、Snowflakeと同じリージョンで利用すること。

## <作業手順>
Step1 Lambda関数を作成します。
Step2 SnowflakeがAPI Gatewayを呼出すためのIAMロールを作成します。
Step3 API Gatewayを作成します。
Step4 SnowflakeでAPI統合を作成します。
Step5 Snowflakeで外部関数を作成します。
Step6 AWSでStep2で作成したIAMロールを修正します。

## Step1 Lambda関数を作成します。
### Step1-1 AWSコンソールでLambda関数を作成します。
「ランタイム」で**Python3.9**、「実行ロール」で**基本的なLambdaアクセス権限で新しいロール**を指定してLambda関数を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/e8bdf2c2-bfed-d683-dba8-5271eefb8c17.png)

### Step1-2 Lambdaで実行するPythonプログラムを作成します。
[Snowflakeのドキュメンテーションで紹介されるサンプル同期Lambda関数](https://docs.snowflake.com/ja/sql-reference/external-functions-creating-aws-sample-synchronous.html)を参考にして作成しました。

Snowflakeの外部関数の引数は、event変数に格納されて連携されます。
以下のコードでは、row[1]から引数を取得し、Comprehendに渡して感情分析を行います。

```
import json
import boto3

comprehend = boto3.client('comprehend')

# Comprehendでの感情分析を実行。戻り値(Positive、Negative、Neautral)
def get_sentiment_result(target):
    sentiment = comprehend.detect_sentiment(Text=target, LanguageCode='ja')
    return sentiment.get('Sentiment')

def lambda_handler(event, context):
    array_of_rows_to_return = [ ]
    event_body = event["body"]
    payload = json.loads(event_body)
    rows = payload["data"]
    for row in rows:
        row_number = row[0]
        input_value = row[1]
        Response = json.dumps(get_sentiment_result(input_value), ensure_ascii=False)
        row_to_return = [row_number, Response]
        array_of_rows_to_return.append(row_to_return)
        
    json_compatible_string_to_return = json.dumps({"data" : array_of_rows_to_return})

    return {
        'statusCode': 200,
        'headers': {
            # INFO: CORS関係のエラー回避のために必要
            'Access-Control-Allow-Origin': '*'
        },
        'body': json_compatible_string_to_return
    }
```

### Step1-3 LambdaにアタッチしたIAMロールにComprehendを呼出す権限を付与します。
Lambda関数からComprehendを呼出せるように、LambdaにアタッチしたIAMロールに**ComprehendFullAccess**を付与します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/3826809e-4b4d-9091-7057-3942304d664b.png)

## Step2 SnowflakeがAPI Gatewayを呼出すためのIAMロールを作成します。
以下の内容でIAMロールを作成します。
**<信頼関係>**
  ・Principal : 現在ログインしているAWSアカウントのID
  ・ExternalID : ダミー値

**<アタッチするIAMポリシー>**
  ・AmazonAPIGatewayInvokeFullAccess

※ 信頼関係のPrincipalとExternalIDは後述の手順で修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/63555884-4032-a4bf-8894-f5a00265190d.png)

## Step3 API Gatewayを作成します。
### Step3-1 AWSコンソールでAPI Gatewayを作成してデプロイします。
デプロイ後、API Gatewayを呼出すためのURLが表示されますので、メモ帳に書き留めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/e0ae57fb-7f23-1e0a-2474-870f6680ec3d.png)

### Step3-2 API Gatewayのリソースポリシーを設定します。
#### Step3-2-1 API GatewayのARNをメモ帳に書き留めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/4b80356a-3d34-2e98-bd95-80f5924d3366.png)

#### Step3-2-2 リソースポリシーを設定します。
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:sts::<現在ログインしているアカウントのID>:assumed-role/<Step2で作成したIAMロール名>/snowflake"
            },
            "Action": "execute-api:Invoke",
            "Resource": "<Step3-2-1でメモ帳に書き留めたARN>*"
        }
    ]
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/b9f51a8f-04f4-4b8c-8944-d69fd06b46e6.png)

## Step4 Snowflakeで「API統合」を作成します。
### Step4-1 Snowflakeコンソールにログインし、で「API統合」を作成します。
以下のSQLを実行してAPI統合を作成します。
```
create or replace api integration <API統合の名称>
    api_provider=aws_api_gateway
    api_aws_role_arn='<Step2で作成したIAMロールのARN>'
    api_allowed_prefixes=('<Step3-1で作成したAPI GatewayのURL>')
    enabled=true;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/938bf749-3632-1ecc-188a-015bc62fbf18.png)

### Step4-2 DESC INTEGRATIONを実行してSnowflake側のIAMユーザーのARNとExternal IDを取得します。
```
describe integration <API統合の名称>;
```
以下の値をメモ帳に書き留めます。後述の手順で使用します。
・**API_AWS_IAM_USER_ARN**
・**API_AWS_EXTERNAL_ID**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/8f866955-23be-88b8-f442-22a15f0a467e.png)

### Step4-3 「外部関数」を使用するロールに「API統合」の使用権限を付与します。
```
GRANT USAGE ON INTEGRATION <API統合の名称> TO ROLE "外部関数を使用するロール";
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/6a8630fd-46e5-9d5e-17c8-dc76e90f9e0d.png)

## Step5 Snowflakeで外部関数を作成します。
以下のSQLを実行して外部関数を作成します。
```
create or replace external function <外部関数の名称>(V varchar)
    returns variant
    api_integration = <API統合の名称>
    as '<Step3-1で作成したAPI GatewayのURL>';
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/525a8bee-bb0b-1679-cc47-6de798ad23a6.png)

## Step6 AWSでStep2で作成したIAMロールを修正します。
IAMロールの信頼関係の以下の項目をStep4-2取得したIAMユーザーのARNとExternal IDに修正します。
・**Principal**：Step4-2で取得した**API_AWS_IAM_USER_ARN**の値
・**sts:ExternalId**：Step4-2で取得した**API_AWS_EXTERNAL_ID**の値
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/d505ec5b-8192-73fa-6ade-136996c1f850.png)

## <検証>
引数の文字列をAWSのComprehendで感情分し、"Positive"、"Negative"、"Neautral"のいづれかの結果を返します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1925708/7ef68ec2-aeec-08e7-1cd0-84f26925630b.png)

## <おわりに>
SnowflakeからAWSのLambda関数を呼び出せるようになったことで、関数の用途が広げられると考えました。
ただし、外部関数は、API GatewayやLambdaとの間でのデータ転送により実行速度が低下します。そのため、SQLの実行時間が通常の関数を使った場合と比べて長くなり、ウェアハウスの課金も増える心配があります。
さらに、LambdaやAPI Gatewayの使用でAWS側の課金とデータ転送での課金が発生するためコストには注意が必要と考えました。

# ご案内
株式会社ジールでは、「ITリテラシーがない」「初期費用がかけられない」「親切・丁寧な支援がほしい」「ノーコード・ローコードがよい」「運用・保守の手間をかけられない」などのお客様の声を受けて、オールインワン型データ活用プラットフォーム「ZEUSCloud」を月額利用料にてご提供しております。
ご興味がある方は是非下記のリンクをご覧ください：
https://www.zdh.co.jp/products-services/cloud-data/zeuscloud/?utm_source=qiita&utm_medium=referral&utm_campaign=qiita_zeuscloud_content-area