AuroraへのInsert TriggerでLambdaを呼びだしてServerlessで処理する



# はじめに
* [Serverless Advent Calendar 2016](http://qiita.com/advent-calendar/2016/serverless)の22日目です。
* サーバレスネタとしては、以前 [在席管理をSlackとAWSのサーバレスアーキテクチャで解決してみた（iruca API編）](http://qiita.com/ukitiyan/items/fdc83bfd99e4440c6f30) てな記事も書いてるので、そちらも良かったらチェックしてください。
* 本記事では、サーバレスで、何とか先輩の手を煩わせないためにどうしたものかと考えてた内容を書きます。
* 来年も自分のためだけじゃなくて、人のためにもサーバレスアーキテクチャを考えて生きていきたいと思います。

# 要件
* 世界展開を行うECシステムで、ネットワークレイテンシーを解消するために、AWSの各リージョンにフロントエンド機能を設置する。
* 但し、個人情報は既存のDCに保管するものとして、ECで発生したオーダーデータ等の個人情報についても一定期間以上 AWSのDBに保持してはならない。
* 尚、今回利用するECパッケージは、オーダーが入ると同一リージョンのオーダーテーブルにデータが登録される。

# ソリューション
* ECは、パッケージ利用想定なので、なるべくイジらないのがセオリー。
* で、オーダーテーブルに登録されたレコードを普通に移動させれば良いので、以下の構成は普通に思いつくわけです。

![ec architecture1](https://qiita-image-store.s3.amazonaws.com/0/39596/a4975df1-f4ff-fbe2-02d3-5b15f682ae30.png "skitch.2.png")

* 上記だと、いかんせんサーバレスじゃないので、以下のような構成が発案されます。

![ec architecture2](https://qiita-image-store.s3.amazonaws.com/0/39596/b3515890-ddbf-02e4-ad2c-598b48c2e2b9.png "skitch.png")

* しかしながら、上記だと赤丸部分でオーダーと同時にSQSにメッセージ投げるというECパッケージの改修が必要となるのでゲンナリします。（これを通すと先輩の手を煩わせることに...）
* 結合度も高いので、疎結合なサーバレスアーキテクチャとして、以下のような構成が発案されます。
* 因みにRDSのエンジンは、良いことしかないのでAuroraにしましょう。

![ec architecture3](https://qiita-image-store.s3.amazonaws.com/0/39596/9bb29d0e-ab5a-94fe-8c7c-950e2932c44e.png "skitch.1.png")

# テーブルへのInsert通知をServerlessで処理する

* 上記を踏まえてテーブルへのInsert通知をServerlessで処理する方法を紹介します。
* 具体的には、オーダーテーブルへのAfter InsertにTrigger仕込んで、Lambda経由でSQSにメッセージを登録する方法になります。
* SQSに伝わればその先は幾らでもやりようがあると思います。
* Triggerは、複雑過ぎると良いことないので、シンプルに使いましょう。

![ec architecture4](https://qiita-image-store.s3.amazonaws.com/0/39596/021e3f76-6d27-2649-e94e-d2ddd482a582.png "skitch.3.png")

## IAM Roleを作成する
* Lambda用Role

| 項目 | 設定内容 |
|:-----------|:------------|
| ロール名    | （任意：[例] LambdaDbTriggerTestRole） | 
| AWS サービスロール    | AWS Lambda | 
| アタッチするポリシー    | AmazonSQSFullAccess | 

* RDS用Role

| 項目 | 設定内容 |
|:-----------|:------------|
| ロール名    | （任意：[例] RdsDbTriggerTestRole） | 
| AWS サービスロール    | AWS RDS | 
| アタッチするポリシー    | 無し | 
| ※超重要※    | 作成後、アクセス許可タブで`AWSLamdbaRole`をアタッチ | 

## SQSでQueueを作成する
* 任意のキュー名でキューを作成します。
* 例）DbTriggerTestQueue

## LambdaでFunctionを作成する
* RDSからキックされ、SQSにメッセージ送信するLambdaファンクションを作成します。

| 項目 | 設定内容 |
|:-----------|:------------|
| Runtime    | Python 2.7 | 
| Handler| lambda_function.lambda_handler | 
| Role    | 先程作成したLambda用Role | 

* コードは以下の内容です。（queueNameは、先程作成したqueueNameを指定してください）

```Python:main.py
import boto3
import json
import logging

queueName='DbTriggerTestQueue'

def lambda_handler(event, context):
  try:
    logger.info(event)
    response = boto3.resource('sqs').get_queue_by_name(
      QueueName = queueName
    ).send_message(
      MessageBody = json.dumps(event)
    )
    logger.info(response)
    return response

  except Exception as e:
    logger.error(e)
    raise e
```


## Auroraを起動する
* 以下の内容で起動しました。

| 項目 | 設定内容 |
|:-----------|:------------|
|リージョン|ap-northeast-1|
|インスタンスクラス|db.t2.medium|
|マルチAZ|No|
|サブネットグループ|Default|
|パブリックアクセス可能|No|
|Availability Zone|ap-northeast-1a|
|パラメータグループ|新規のDB Parameter Group<br>新規のDB Cluster Parameter Group|

## AuroraにIAMロールを設定する

* クラスターのIAMロールの管理から先程作成した`RDS用Role`を紐付けます。

![skitch.4.png](https://qiita-image-store.s3.amazonaws.com/0/39596/fcc3a1e9-eee1-e33a-bddd-ad1eb548973c.png "skitch.4.png")

* 先程作成＆Auroraに指定した`DB Cluster Parameter Group`のパラメータの編集から、`aws_default_lambda_role`の値に、先程作成した`RDS用Role`のARNを設定します。
* 例）arn:aws:iam::xxxxxxxxxx:role/RdsDbTriggerTestRole

![skitch.6.png](https://qiita-image-store.s3.amazonaws.com/0/39596/1a9b396e-1f06-0245-5368-6f16bc65f976.png "skitch.6.png")

* DBクラスタパラメータグループが`pending-reboot`になるはずなので、Auroraを再起動します。

## Triggerを作成する
* 再起動したらAuroraに接続して、以下のStored Procedure、Table、Triggerを作成します。
<br><br>
* Stored Procedure
	* mysql.lambda_asyncの第一引数は、先程作成したLambdaのARNを設定します。

```SQL:sqs_order_data_ai_message.sql
DROP PROCEDURE IF EXISTS sqs_order_data_ai_message;
DELIMITER ;;
CREATE PROCEDURE sqs_order_data_ai_message (IN id INT(11), 
                                      IN item VARCHAR(50)) LANGUAGE SQL 
BEGIN
  CALL mysql.lambda_async('arn:aws:lambda:ap-northeast-1:xxxxxxxx:function:TestFunction',  
    CONCAT('{ "id" : "', id, 
            '", "item" : "', item, '" }')
     );
END
;;
DELIMITER ;
```

* Table

```SQL:order_data.sql
CREATE TABLE `order_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `item` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

* Trigger

```SQL:trigger_order_data_ai.sql
DELIMITER ;;
CREATE TRIGGER trigger_order_data_ai 
  AFTER INSERT ON order_data 
  FOR EACH ROW
BEGIN
  CALL sqs_order_data_ai_message(NEW.id, NEW.item);
END
;;
DELIMITER ;
```

## 動作テスト
* 以下のSQLを実行してみます。

```SQL:insert.sql
INSERT INTO order_data VALUES (NULL, 'item_001');
```

* 取り急ぎ、SQSの画面で利用可能なメッセージがカウントアップされていれば成功です。

![skitch.7.png](https://qiita-image-store.s3.amazonaws.com/0/39596/002c80de-3f8e-960d-dc2b-b04a8412abcf.png "skitch.7.png")


# 備考
* エンジンは`Aurora`一択です。`MySQL`を選んだところで以下のように`mysql.lambda_async`がコール出来ないです。

```SQL:MySQL
mysql> show create procedure mysql.lambda_async \G
ERROR 1305 (42000): PROCEDURE lambda_async does not exist
```

# 参考
* http://dev.classmethod.jp/cloud/aws/invoke-lambda-from-aurora/
* http://qiita.com/eci/items/3eddcd815cd0af121202
* http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.Lambda.html