DynamoDBデータのグラフ画像作成(Lambda)


# はじめに
LambdaとDynamoDBを使用すればIOT用のグラフページを作成できるのではと思ったのでその備忘録。
この記事では図中の「グラフ画像生成」部分を実施。
前回：https://qiita.com/aikawa_YO/items/0fb84aec97e28ea33136
関数URLとLambdaのロール設定は前回と同様。
（今回は読み込みだけなのでLambdaからDynamoDBへの権限はフルアクセスいらなかった。）

![Lambda-DB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195542/82933046-6bbe-6525-d7a2-d8d23116a3ec.png)

# DynamoDBからデータを取得する
データを取得するためにいくつかの方法がありました。
## get_item
条件に当てはまるデータ単体を取得する
## query
条件に当てはまるデータをすべて取得する
PartitionKeyとSortKeyがある
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195542/dcdf7d30-7cf0-90cc-c534-582bb2e6db46.png)
上記例ではPartitionKeyがidでSortKeyがtime

PartitionKeyのみ、またはPartitionKeyとSortKey2つを指定する事が可能。
Primary Key = Partition Key
Primary Key = Partition Key + Sort Key

今回のIOT機器の場合、idが機器固有の番号、timeがデータを計測した際の時間となりこの2つの値を合わせてプライマリーキーとなる。

queryではPartitionKeyにより絞り込みを行いSortKeyで整列させ出力する。

## scan
すべてのデータを取得

# query
下記サンプルプログラムではidが1番のデータをtime降順にて取得している。

```python
response = table.query(
    KeyConditionExpression=Key("id").eq(1),
    ScanIndexForward=False
)
```

# BytesIO(Python)
バイナリデータをメモリ(RAM)上で扱うための機能。
Lambdaではエフェメラルストレージが用意されているのでそちらを使用したほうがよかったかもしれない。

# matplotlib(Python)
グラフ画像の作成に使用する。
Lambdaにてmatplotlibを使用する方法は下記記事を参考にさせていただきました。

https://qiita.com/quryu/items/139ea4736f37fe0abcdd


# Lambdaの一般設定
グラフの作図や画像データ処理に処理にメモリが必要であるためある程度増やしておく。
処理に時間がかかるとタイムアウトするためタイムアウト時間も調整する必要がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195542/74bd8efb-e1fa-7950-069c-5aa8f3cc671a.png)


# プログラム全体

```python
import base64
import boto3
import matplotlib.pyplot as plt
from io import BytesIO
from datetime import datetime as dt
from boto3.dynamodb.conditions import Key


def lambda_handler(event, context):
    dynamodb = boto3.resource("dynamodb", region_name='ap-northeast-1')
    table = dynamodb.Table("iot_data")
    response = table.query(
        KeyConditionExpression=Key("id").eq(1),
        ScanIndexForward=False
    )
    
    items = response["Items"]
    
    temps = []
    times = []
    for item in items:
        temps.append(float(item['temperature']))
        times.append(dt.strptime(item['time'], '%Y-%m-%d %H:%M:%S %z'))
    
    fig = plt.figure()
    plt.plot(times, temps)
    
    byte_file = BytesIO()
    fig.savefig(byte_file, format="png")
    png_data = byte_file.getvalue()
    
    plt.close()

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "image/png"},
        "body": base64.b64encode(png_data).decode('utf-8'),
        "isBase64Encoded": True
    }
```

# 実行結果
関数URLへアクセスすると↓のように画像が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195542/6907ec7a-68bd-0b22-74b7-cd1f37da7874.png)


# BytesIOとエフェメラルストレージどちらが早い？
今回は画像の保存部分にBytesIOを使用しました。（通常であれば画像はS3に保存したりすると思います）

```python
byte_file = BytesIO()
fig.savefig(byte_file, format="png")
png_data = byte_file.getvalue()
```
よくよく考えるとBytesIOを使用した場合メモリ(RAM)上に保存すると思うのでLambdaの場合はふさわしくないのでは？
エフェメラルストレージに保存したほうがいいかもしれないと思いました。

```python
fig.savefig("/tmp/img.png")
file = open('/tmp/img.png', 'rb')
png_data = file.read()
```

ただ、実行結果は下記のようにそんなに大差はなかったです。
ふつうにBytesIO使っておけば良さそう

> BytesIO
１回目：Duration: 1125.19 ms Billed Duration: 1126 ms Memory Size: 512 MB Max Memory Used: 159 MB Init Duration: 2847.22 ms
２回目：Duration: 422.46 ms Billed Duration: 423 ms Memory Size: 512 MB Max Memory Used: 160 MB 

> エフェメラルストレージ
１回目：Duration: 1140.78 ms Billed Duration: 1141 ms Memory Size: 512 MB Max Memory Used: 159 MB Init Duration: 2433.66 ms 
２回目：Duration: 443.96 ms	Billed Duration: 444 ms	Memory Size: 512 MB	Max Memory Used: 161 MB