C# で DynamoDB から時系列データを GSI を使って範囲指定して検索する

C# で Amazon DynamoDB に保存されている時系列データを GSI（グローバルセカンダリインデックス）を使って範囲指定して検索する方法です。

# [事前準備１] データ
下記の構造のデータを保存できるよう、

```C#
[DynamoDBTable("SampleTable")]
public class Sample
{
    /// <summary>「SampleTable」テーブルのハッシュキー</summary>
    [DynamoDBHashKey]
    public string Id { get; set; }

    /// <summary>「SampleTable」テーブルのレンジキー</summary>
    [DynamoDBRangeKey]
    public string DateAndTime { get; set; }

    /// <summary>「Date-Time-index」GSI のハッシュキー</summary>
    [DynamoDBGlobalSecondaryIndexHashKey]
    public string Date { get; set; }

    /// <summary>「Date-Time-index」GSI のレンジキー</summary>
    [DynamoDBGlobalSecondaryIndexRangeKey]
    public string Time { get; set; }

    [DynamoDBProperty]
    public string Message { get; set; }

    public override string ToString()
    {
        return $"{Id} / {DateAndTime} / {Message}";
    }
}
```

AWS Console から `Id`、`DateAndTime` をキーにした `SampleTable` テーブルを用意します。
![table.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14838/af4c0a01-54ac-a8e7-6913-d8ab1b4b639e.png)

次に、下記のように `SaveAsync` メソッドを使って 10件のデータを作成します。

```C#
// DynamoDB にアクセスするためのオブジェクトを用意
using var client = new AmazonDynamoDBClient(RegionEndpoint.APNortheast1);
using var context = new DynamoDBContext(client);

// 10件のデータを作成
for (var i = 0; i < 10; i++)
{
    var now = DateTime.Now;

    await context.SaveAsync(new Sample
    {
        Id = $"{i % 3 + 1:D3}",
        DateAndTime = $"{now:yyyy-MM-dd HH:mm:ss.fff}",
        Date = $"{now:yyyy-MM-dd}",
        Time = $"{now:HH:mm:ss}",
        Message = $"メッセージ - {i + 1}"
    });

    // 保存される時間をずらしたいので 500 msec 待機
    await Task.Delay(500);
}
```

下記のように `ScanAsync` メソッドを使って全データを取得します。

```C#
// 全データを取得
var listAll = await context.ScanAsync<Sample>(null).GetRemainingAsync();

Console.WriteLine("--- listAll");
listAll.OrderBy(n => n.DateAndTime).ToList().ForEach(Console.WriteLine);
```

そうすると、下記のようにデータを取得できます。

```console
--- listAll
001 / 2020-08-11 11:40:40.236 / メッセージ - 1
002 / 2020-08-11 11:40:41.224 / メッセージ - 2
003 / 2020-08-11 11:40:41.756 / メッセージ - 3
001 / 2020-08-11 11:40:42.293 / メッセージ - 4
002 / 2020-08-11 11:40:42.824 / メッセージ - 5
003 / 2020-08-11 11:40:43.355 / メッセージ - 6
001 / 2020-08-11 11:40:43.877 / メッセージ - 7
002 / 2020-08-11 11:40:44.412 / メッセージ - 8
003 / 2020-08-11 11:40:44.936 / メッセージ - 9
001 / 2020-08-11 11:40:45.458 / メッセージ - 10
```

# [事前準備２] GSI
AWS Console からは下記のようにデータが保存されていることを確認できます。
![aws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14838/62cdccfd-6d87-4a9e-0fb4-0173681c525c.png)

事前準備の最後に、日時を範囲指定して検索できるよう、AWS Console から `Date`、`Time` をキーにした `Date-Time-index` インデックス（GSI）を用意します。
![gsi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14838/447517a8-3152-66b4-a847-1c7460c790e5.png)

これで準備は完了です。

# [シナリオ１] Id = 001 のデータを取得したい
下記のようにハッシュキーに `001` を指定して `QueryAsync()` メソッドを使ってデータを取得します。

```C#
var list01 = await context.QueryAsync<Sample>("001").GetRemainingAsync();

Console.WriteLine("--- list01");
list01.OrderBy(n => n.DateAndTime).ToList().ForEach(Console.WriteLine);
```

そうすると、下記のようにデータを取得できます。

```console
--- list01
001 / 2020-08-11 11:40:40.236 / メッセージ - 1
001 / 2020-08-11 11:40:42.293 / メッセージ - 4
001 / 2020-08-11 11:40:43.877 / メッセージ - 7
001 / 2020-08-11 11:40:45.458 / メッセージ - 10
```

Id が 001 のデータのみを取得できています。

# [シナリオ２] 2020-08-11 の 11:40:42～11:40:43 のデータを取得したい
下記のように GSI の名称 `Date-Time-index` を指定し、ハッシュキーに `2020-08-11` を指定して、レンジキーには `QueryOperator.Between` と `11:40:42 - 11:40:43` を指定して `QueryAsync()` メソッドを使ってデータを取得します。

```C#
var list02 = await context.QueryAsync<Sample>(
    "2020-08-11",
    QueryOperator.Between,
    new List<object>
    {
        "11:40:42",
        "11:40:43"
    },
    new DynamoDBOperationConfig
    {
        IndexName = "Date-Time-index" // 利用する GSI を指定
    }).GetRemainingAsync();

Console.WriteLine("--- list02");
list02.OrderBy(n => n.DateAndTime).ToList().ForEach(Console.WriteLine);
```

そうすると、下記のようにデータを取得できます。

```console
--- list02
001 / 2020-08-11 11:40:42.293 / メッセージ - 4
002 / 2020-08-11 11:40:42.824 / メッセージ - 5
003 / 2020-08-11 11:40:43.355 / メッセージ - 6
001 / 2020-08-11 11:40:43.877 / メッセージ - 7
```

`SampleTable` テーブルのハッシュキーである `Id` をまたいで `11:40:42 - 11:40:43` のデータのみを取得できています。

# さいごに
GSI を使って日時を範囲指定して検索することはできました。
ただ、コスト面の考慮や NoSQL らしい設計についてはできているかというと、、

より良い方法があったら教えてください m(_ _)m