DynamoDBからログデータを出力

DynamoDBのデータをクエリしてローカルで何かしら解析用途に使いたい場合のメモです。

## aws-cliのセットアップ
まずは利用しているAWSアカウントでIAMの認証情報を取得します。
具体的には以下コマンドでそのアカウントで利用する認証情報をセットします。

```:bash
$ aws configure --profile hoge
AWS Access Key ID [None]:
AWS Secret Access Key [None]: 
Default region name [None]: 
Default output format [None]:
```

この設定が正しくできれば、aws-cliが使えるようになっているかと思います。
（※AWSアカウントを複数利用している場合は `--profile {アカウント名エイリアス}` としてアカウント切り替えを行う必要がありますが、単一アカウントの場合は単純に `aws help` 等、エイリアス指定は省略できます。）

```:bash
$ aws --profile hoge help
AWS()                                                                   

NAME
       aws -
（略）
```

## DynamoDBの操作確認
テーブル一覧が取得できます。
（※それぞれ"HogeLog"、"FugaLog"、"PiyoLog"という3つのテーブルが登録されている前提）

```:bash　テーブル一覧取得
$ aws --profile hoge dynamodb list-tables
{
    "TableNames": [
        "HogeLog",
        "FugaLog",
        "PiyoLog"
    ]
}
```

```:bash　"HogeLog"テーブル詳細取得
$ aws --profile hoge dynamodb describe-table --table-name "HogeLog"
{
    "Table": {
        "AttributeDefinitions": [
            {
                "AttributeName": "hoge_id",
                "AttributeType": "S"
            },
            {
                "AttributeName": "timestamp",
                "AttributeType": "S"
            }
        ],
        "TableName": "HogeLog",
        "KeySchema": [
            {
                "AttributeName": "hoge_id",
                "KeyType": "HASH"
            },
            {
                "AttributeName": "timestamp",
                "KeyType": "RANGE"
            }
        ],
        "TableStatus": "ACTIVE",
        "CreationDateTime": "2020-09-07T15:39:18.471000+09:00",
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 0,
            "WriteCapacityUnits": 0
        },
        "TableSizeBytes": 1685307527,
        "ItemCount": 4056344,
        "TableArn": "arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/HogeLOg",
        "TableId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST",
            "LastUpdateToPayPerRequestDateTime": "2021-10-07T10:45:35.678000+09:00"
        }
    }
}
```

## DynamoDBテーブルから実際にデータをクエリ実行する


```bash:
$ aws --profile hoge dynamodb query \
 --table-name HogeLog \
 --key-condition-expression 'hoge_id = :hoge_id and timestamp >= :timestamp' \
 --expression-attribute-values '{ ":hoge_id": { "S" : "SS10000" }, ":timestamp": { "S": "2021-10-08T10:30:00.000" } }'
```

#### コマンドの補足
- `dynamodb query` と `dynamodb scan`があるが、インデックスを使って効率よく低コストでデータ取得を行う場合は前者を用いる（という理解;）
- `--table-name テーブル名` は特に問題ないかと。
- `--key-condition-expression`はパーティションキーとソートキーを「キー名 = :キー値のプレースホルダ名」のような形式で指定する。プレースホルダ名はフルコロン「:」を接頭辞として付ける必要あり。ソートキー側は等号「=」である必要はなく不等号に変更可能。
- `--expression-attribute-values`は上記キー名で指定したプレースホルダ名に対応する値を指定する。形式は`":プレースホルダ名" { "データ型" : "データ値" }`となる。代表的なデータ型としては"N"（数値型）、"S"（文字列型）、"M"（Map型）等。

ただし上記コマンドを発行した際に以下エラーと遭遇します。

```shell-session:
An error occurred (ValidationException) when calling the Query operation:
Invalid KeyConditionExpression: Attribute name is a reserved keyword; reserved keyword: timestamp
```

エラー部分 `Attribute name is a reserved keyword; reserved keyword: timestamp` を見ると、`--key-condition-expression`で指定している属性名 `timestamp` aws-cliの予約語だからだということだそうです。

これを回避するためには、キー名を予約語ではない別のキー名に置き換える必要があります。

以下では`--expression-attribute-names '{ "#timestamp": "timestamp" }'`を追加してこのエラーを回避しています。

```bash:
$ aws --profile hoge dynamodb query \
 --table-name HogeLog \
 --key-condition-expression 'hoge_id = :hoge_id and #timestamp >= :timestamp' \
 --expression-attribute-names '{ "#timestamp": "timestamp" }' \
 --expression-attribute-values '{ ":hoge_id": { "S" : "SS10000" }, ":timestamp": { "S": "2021-10-08T10:30:00.000" } }'

{
    "Items": [
        {
            "payload": {
                "M": {
                    "hoge_id": {
                        "S": "SS10000"
                    },
                    "datetime": {
                        "S": "20211008013001"
                    },
                    "location": {
                        "M": {
                            "x": {
                                "N": "-3957314.620"
                            },
                            "y": {
                                "N": "3310254.137"
                            },
                            "z": {
                                "N": "3737540.043"
                            }
                        }
                    },
（略）
```

ここまででDynamoDBのクエリ操作自体は正常に完了するようになりました。

## DynamoDBのクエリ結果を見やすく整形する

### ステップ１　「一旦ローカルに落とす」
上記クエリコマンドの末尾に以下を追加してファイルに格納します。
`--output json > ~/SS10000_20211008013001.json`
（※ここで一度ファイルに出力してしまう背景には、整形の過程で何度もDynamoDBにクエリを投げると、その分Readキャパシティを消費し、コストが余計にかかってしまうことを防ぐ目的があります。

コマンド全体としては、

```bash
$ aws --profile hoge dynamodb query \
 --table-name HogeLog \
 --key-condition-expression 'hoge_id = :hoge_id and #timestamp >= :timestamp' \
 --expression-attribute-names '{ "#timestamp": "timestamp" }' \
 --expression-attribute-values '{ ":hoge_id": { "S" : "SS10000" }, ":timestamp": { "S": "2021-10-08T10:30:00.000" } } \
 --output json > ~/SS10000_20211008013001.json'
```

### ステップ２　「ローカルファイルを弄る」
出力したJSONファイルに対して整形コマンドを微調整していきます。

jqコマンドでjson文字列としてデータ操作できるようにする

```bash
$ cat ~/SS10000_20211008013001.json | jq .Items

[
  {
    "payload": {
      "M": {
        "hoge_id": {
          "S": "SS10000"
        },
（略）
```

データ配列の中身を操作するため、jqクエリをシングルクォーテーション「'」で囲み、小要素をドット「.」でチェーンする

```bash
$ cat ~/SS10000_20211008013001.json | jq '.Items[].payload.M'

{
  "hoge_id": {
    "S": "SS10000"
  },
（略）
```

JSONのネストした子要素もおしなべて同列に扱い、配列に突っ込んで表示するため、パイプで繋ぐ  `| []`

```bash
$ cat ~/SS10000_20211008013001.json | jq '.Items[].payload.M | [.datetime.S, .location.M.x.N, .location.M.y.N, .location.M.z.N,]'

[
  "20211006072947",
  "-3957314.620",
  "3310254.137",
  "3737540.043"
]
[
（略）
```

CSV形式で出力する `| @csv`

```bash
$ cat ~/SS10000_20211008013001.json | jq '.Items[].payload.M | [.datetime.S, .location.M.x.N, .location.M.y.N, .location.M.z.N,] | @csv'

"\"20211006072947\",\"-3957314.620\",\"3310254.137\",\"3737540.043\""
（略）
```

ダブルクォーテーション「"」の膜を剥いでエスケープ文字も無くすために`jq -r`とする

```bash
$ cat ~/SS10000_20211008013001.json | jq -r '.Items[].payload.M | [.datetime.S, .location.M.x.N, .location.M.y.N, .location.M.z.N,] | @csv'

"20211006072947","-3957314.620","3310254.137","3737540.043"
（略）
```

CSVヘッダー行を追加する `| jq '["datetime", "x", "y", "z"], () | @csv'`

```bash
$ cat ~/SS10000_20211008013001.json | jq '["datetime", "x", "y", "z"], (.Items[].payload.M | [.datetime.S, .location.M.x.N, .location.M.y.N, .location.M.z.N,]) | @csv'

"datetime","x","y","z"
"20211006072947","-3957314.620","3310254.137","3737540.043"
（略）
```

### ステップ３　「クエリから整形までを一気通貫で行う」

以上までのコマンドを組み合わせて、以下のように1コマンドでDynamoDBからローカルCSVにデータを吐き出せるようになりました。

```bash
$ aws --profile hoge dynamodb query \
 --table-name HogeLog \
 --key-condition-expression 'hoge_id = :hoge_id and #timestamp >= :timestamp' \
 --expression-attribute-names '{ "#timestamp": "timestamp" }' \
 --expression-attribute-values '{ ":hoge_id": { "S" : "SS10000" }, ":timestamp": { "S": "2021-10-08T10:30:00.000" } } \
 --output json | jq '["datetime", "x", "y", "z"], (.Items[].payload.M | [.datetime.S, .location.M.x.N, .location.M.y.N, .location.M.z.N,]) | @csv' > ~/SS10000_20211008013001.csv
```


データ数が増えた場合にDynamoDBレコード側のページング処理もしなければならない気もしています。
何か考慮漏れ等あればご教示ください。