ヒートマップツール作成でハマったAWSアレコレ（S3, Lambda, API Gateway, AppFlow）

ディップのアドベントカレンダー15日目の記事です。
昨日は PHPに関する記事でしたね、PHPを使った業務経験はほぼないですがとても興味深かったです。

今日の担当は、UI/UX改善を担当している @maaaaaya9 です。
最近、サイト改善用にヒートマップツールを作成しました。そのインフラにAWSを使用したのですが、がっつりAWSを触るのは初めてということもあり挙動にあくせくしたのでその知見をまとめておきます。
同様の事象が発生して困っているひとなどの役に立てれば幸いです。
（注意：ここに記載してあることがベストプラクティスとは限らないのでご参考までに）

　

## :evergreen_tree:  もくじ
前提） [全体アーキテクチャ](#-前提--全体アーキテクチャ)
1. [S3 でオブジェクトが更新されない問題](#-s3-でオブジェクトが更新されない問題)
2. [S3 + Lambda で発生した再帰無限ループ](#-s3--lambda-で発生した再帰無限ループ)
3. [Lambda + API Gateway の連携がうまくいかない問題](#-lambda--api-gateway-の連携がうまくいかない問題)
4. [AppFlow + Google Analytics の連携がうまくいかない問題](#-appflow--google-analytics-の連携がうまくいかない問題)

## 前提） 全体アーキテクチャ
![heatmap.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105386/827a8a3a-406e-0697-e743-d0a70c8baa92.jpeg)

Google Tag Manager でユーザーの閲覧時間を計測し、Google Analytics に指標データを蓄積します。それを AppFlow を使って1日毎にデータを S3 に出力し、Lambda を使ってデータを集計して結果を別の S3 に出力するという設計にしました。細かいセグメント分けしたヒートマップ結果がみたい場合は Chrome拡張でパラメータを渡し、Api Gateway 経由で Lambda を実行させて結果を S3 に出力させます。

## ① S3 でオブジェクトが更新されない問題

#### < 事象 >
Lambda で既存の S3 オブジェクトを更新（または追記）できるようにしたかったのですが、何度実行してもファイルの中身が古いままで更新されないという事象が発生しました。

#### < 原因 >
原因を調べてみたところ、S3 での上書きと削除については「結果整合性」を提供しており、「新規作成したオブジェクトはすぐ参照できるが、更新と削除は反映されるまで古いデータを返す場合がある」らしいです。
（参考）[AWS公式 / Amazon S3 のデータ整合性モデル](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/Introduction.html#ConsistencyModel)

※他の説として、S3 のオブジェクトをキャッシュ管理していて、キー名が同じだと更新されたと判定されず古いほうを参照してしまうというような記事も見つけましたが、信用できる情報かは不明でした。

#### < 対処 >
整合性の観点から、S3 ではオブジェクトに追記したりといった更新処理はせず、ユニークなキー名でオブジェクトを新規作成するようにしました。（世間的に、新規でオブジェクトを作成する使い方が推奨されているようです）

　
## ② S3 + Lambda で発生した再帰無限ループ
#### < 事象 >
1 の事象を解決すべく、Lambda 実行毎にオブジェクトを新規作成して S3（heatmap-bucket）に配置することにしました。
![heatmap_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105386/698d1c14-9bb3-3ccb-e169-553a6ec1453a.jpeg)
しかし、1回だけ実行したはずなのに、Lambda が100回程度実行され続け、オブジェクトもその回数分作成される事象が発生しました。

#### < 原因 >
これには重大な問題がありました。S3（heatmap-bucket）のオブジェクト作成をトリガーとして起動するLambdaが存在しているので
**Lambda 起動 → S3 にオブジェクト作成 → S3 トリガーの Lambda がまた起動 → S3 にオブジェクトが作成 → S3 トリガーの Lambda がまた起動 → S3 にオブジェクトが作成...**
というように Lambda が無限に実行されてしまいます。

Lambda で S3 トリガーを設定するときの確認項目にバッチリ書いてありますね。
![スクリーンショット 2020-12-09 13.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105386/2e7a0e6a-a601-c945-bcdd-26f308aec637.png)
内容を理解してチェックを付けていましたが、まさか自分がやってしまうとは思っていませんでした…

なぜ無限実行されずに100回程度で停止したかというと、同じキー名のオブジェクトを新規作成していたため、オブジェクトがユニークになるようAWS側で自動で記号や数値をキー名に付与しており、付与され続けた結果、キー名が規定の文字数をこえてエラーになったからでした。不幸中の幸いです。

#### < 対処 >
S3（heatmap-bucket）は Lambda のトリガーになっているので、トリガーになっていない別の S3（heatmap-output）にオブジェクトを作成するようにしました。

　

## ③ Lambda + API Gateway の連携がうまくいかない問題
#### < 事象 >
API Gateway を「Lambda プロキシ統合」の設定をして作成し、Lambda 関数のトリガーに設定して API Gateway から Lambda を呼び出すようにしたかったのですが、CORSのエラーになってしまいました。

#### < 原因 >
API Gateway で「Lambda プロキシ統合」の設定をすると、既存のメソッド統合レスポンスに Access-Control-Allow-Origin ヘッダーを追加しようとしてくれるのですが、これがうまく機能しないことがあり、その場合は手動で追加する必要があるようです。
（参考）[AWS公式 / REST API リソースの CORS を有効にする](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html)


#### < 対処 >
まず API Gateway で作成したリソースに対して「CORSの有効化」を実行し、APIのデプロイも行います。その後、Lambda 関数のレスポンスに「Access-Control-Allow-Origin」を含めるよう実装します。

Pythonだと下記のようにヘッダーに Access-Control-Allow-Origin を追加します。

```python

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps('Hello from Lambda!')
    }
```

　

## ④ AppFlow + Google Analytics の連携がうまくいかない問題
AppFlow とは 2020年4月にリリースされたサービスで、外部 SaaS アプリケーションと AWS 間でのデータ連携を、コードを書かずに自動化できるフルマネージドサービスです。SaaS アプリケーションは、Datadog、Google Analytics、Salesforce、Slack、Zendesk などが対応しています。
（参考）[AWS公式 / AppFlowドキュメント](https://docs.aws.amazon.com/appflow/index.html)

#### < 事象 >
今回はこの AppFlow を使って1日1回定時に Google Analytics のデータを S3 に出力するようにしたのですが、AppFlow を実行すると 503 エラーになることがしばしば発生しました。

```
The request failed with the Googleanalytics status Code 503: Status Code 503 Error message: The service is currently unavailable., RequestId:XXXXXXXX
```

#### < 原因 >
このエラーはテスト環境では発生せず、本番環境でのみ発生するのですが、データが大量だったり複雑なリクエストを大量に送信するなど、サーバへの負荷が大きくなると Google Analytics API でこのエラーが発生することがあるようです。

#### < 対処 >
こういった場合に、データをリクエストする対象期間を短くする方法を思いつくかと思いますが、AppFlow のスケジュール設定では最短が1日1回の実行なので、1時間毎など現状以上短い期間を指定することができませんでした。エラーになっていた場合は再実行させるといった機能も見当たらず、自動実行での解決策は見つからなかったので、エラーになっていた場合は手動で再度実行させるといった対処をしました。

　

## :evergreen_tree: さいごに
AppFlow は2020年4月にリリースしたばかりのサービスということもあり情報が少なく、想定していた動作にならないといったことが多々発生したので、新しいサービスを使うときには本番環境を想定したデータでよく確認することが大切だと実感しました。また、Lambda の無限ループの件では、自分を過信しすぎずに設定などよく確認することも大切だと改めて実感しました。AWS をがっつり触ることができとても良い勉強になりました。
もっと良い解決策を知っていたり、誤りなどありましたらコメント頂けると嬉しいです。

それでは引き続き、明日のアドベントカレンダーもお楽しみに :santa_tone2: