AWS Athenaを利用した複数のウェブアプリの実行履歴を統合管理

この記事は[Fusic その2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/fusic-2)の8日目の記事です。

# 概要
複数のウェブアプリのログをAWS S3に押し込めて置いたログファイルに、多様なQueryができ、安く早く結果を取得できる[AWS Athena](https://aws.amazon.com/jp/athena/)を利用した実行履歴を統合管理できるマイクロサービスを実装した話です。

# Athena特徴
AWS Athenaは標準SQLを使用してAWS S3でのデータの直接分析を簡易化するインタラクティブなクエリサービスです。
特徴を羅列すると以下の通りです。

- サーバーレスであるため、インフラストラクチャの設定や管理は不要
- AWS S3の複数のオブジェクトに対するQuery対象を定義可能
- 標準SQLを利用して複雑なDML Queryや色んなDDL Queryができて実行できる完全管理型分析サービス
- データをロードしたり複雑なETL処理する必要なくAWS S3に保存されたデータを直接使用可能
- 実行したクエリに対するスキャンしたデータ量のみ[課金](https://aws.amazon.com/jp/athena/pricing/?nc1=h_ls)（スキャンされたデータ 1TB あたり 5.00USD）
- データの圧縮、分割、列形式への変換を行うことでコストを30%～90%削減し、パフォーマンスを向上可能
 ([Amazon Athena のパフォーマンスチューニング Tips トップ 10](https://aws.amazon.com/jp/blogs/news/top-10-performance-tuning-tips-for-amazon-athena/
))
- など

最初には[AWS S3 Select](https://qiita.com/leedohyung-dba/items/097ab65795244986b720)を利用する予定でしたが、実行できるDMLに制約があり、何よりもAWS S3の複数のオブジェクトに対してQuery対象に定義をできないことですぐAthenaに切り替えました。

# Athenaテーブル生成
テーブル生成することはとても簡単です。
AWS Athenaマネジメントコンソールからボチボチ選択して生成もできますが、
以下の実行履歴テーブルの生成DDLのように生成することも可能です。
※コスト削減するためパーティション切れる構成を設定(dt)してスキャンするデータ量を減らしています。

```sql
-- DB生成DDL
CREATE DATABASE jikko_rireki

-- TABLE生成DDL
CREATE EXTERNAL TABLE `{ウェブアプリ名}`(
  `field1` string, 
  `field2` int,
  `field3` timestamp)
PARTITIONED BY ( 
  `dt` date)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat'
LOCATION
  's3://{Bucket名}/jikko_rirekis/{ウェブアプリ名}'
```

# 実行履歴を統合管理するプロセスフロー
実行履歴を統合管理するシステムのプロセスフローは以下の通りです。
### プロセスフロー図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108364/45747a49-5b0d-3c0c-53b6-70f250ddcf45.png)
※ 複数のウェブアプリはLaravelフレームワークで構築されている前提
※ ActionLoggerクラスはRequestとResponseデータから必要なデータを収集してAPI GatewayにPOSTする
※ Athenaを含めて利用中のNon-VPCのサービスはすべてVPC Endpointが生成できるのでVPC Endpointで連携

プロセスをフローごとにざっくり説明させて頂きます。
### ① 【LOG SET】 Middleware(terminate)でActionLoggerをQueueにdispatchする
Laravel Middleware(terminate)でActionLoggerのインスタンスをSQS Queueにdispatchします。

```LogAction.php
<?php
namespace App\Http\Middleware;

use App\Jobs\ActionLogger;
use Illuminate\Foundation\Bus\DispatchesJobs;

...

class LogAction
{
    use DispatchesJobs;

    ...

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  null|\Illuminate\Http\Response  $response
     * @param ActionLogger $logger
     */
    public function terminate($request, $response, ActionLogger $logger): void
    {
        $logger->setLogData($request, $response);
        $this->dispatch($this->logger);
    }

    ...
```

### ② 【LOG SET】 ActionLoggerは収集したデータをAPI GatewayにPOSTする
ActionLoggerでは収集したデータをAPI Gatewayのset-logメソッドにPOSTします。
※ profixでappcodeを設定したのはロギング先のアプリケーションを区分するためです。

```ActionLogger.php
<?php
namespace App\Jobs;

use App\Http\Requests\JikkoRirekiRequestInterface;
use Illuminate\Contracts\Queue\ShouldQueue;

...

class ActionLogger implements ShouldQueue
{

    ...

    public function handle(JikkoRirekiRequestInterface $JikkoRireki)
    {
        $jikkoRireki = $this->newRecord();
        // POST to Api Gateway(set)
        return $JikkoRireki->save($jikkoRireki);
    }

    ...
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108364/8c78e83a-ba58-e6c7-53f8-e85093499282.png)

### ③ 【LOG SET】 S3 Bucketのオブジェクトに実行履歴レコードデータを格納する
API Gatewayのset-logメソッドと統合リクエストになってるLambda set-logの関数で、
Athena Tableの実行履歴データ先、S3 Bucketのオブジェクトに実行履歴レコードデータを格納(append)します。
※ 参考コード
- [leedohyung-dba/athena-provider/set.js](https://github.com/leedohyung-dba/athena-provider/blob/master/set.js)
- [leedohyung-dba/athena-provider/src/AthenaProvider.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/AthenaProvider.js)
- [leedohyung-dba/athena-provider/src/util/queryGenerator.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/util/queryGenerator.js)

### ④ 【LOG SET】 格納データに対するパーティションがなかったら追加する
※ 格納する日付に対するオブジェクトが無い場合、格納データに対するパーティションがなかったら追加するQueryを実行します。
※ 参考コード
- [leedohyung-dba/athena-provider/src/AthenaProvider.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/AthenaProvider.js)
- [leedohyung-dba/athena-provider/src/util/queryGenerator.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/util/queryGenerator.js)

### ⑤ 【LOG GET】 実行履歴のデータを取得するためAPI GatewayにPOSTする
実行履歴の閲覧するところでAPI Gatewayのget-logメソッドに検索などのデータを載せてPOSTします。
※ profixでappcodeを設定したのはロギング先のアプリケーションを区分するためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108364/29f12033-98f0-2d66-9d3c-cea1fa0267ab.png)

### ⑥ 【LOG GET】 Athenaに実行履歴データを取得するためのQueryを実行する
API Gatewayのget-logメソッドと統合リクエストになってるLambda get-logの関数で、実行履歴データを取得するためのQuery Stringを生成及び実行します。
※ 参考コード
- [leedohyung-dba/athena-provider/get.js](https://github.com/leedohyung-dba/athena-provider/blob/master/get.js)
- [leedohyung-dba/athena-provider/src/AthenaProvider.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/AthenaProvider.js)
- [leedohyung-dba/athena-provider/src/util/queryGenerator.js](https://github.com/leedohyung-dba/athena-provider/blob/master/src/util/queryGenerator.js)

# まとめ
AWS S3に格納するのは安くて簡単なので色んなデータを格納してましたが、これからそのデータたちをAthenaやS3 Selectなどで分析して何か新しい価値に産み付けるものってとても楽しみですね。
最後まで読んでいただきまして、ありがとうございました。