AppSync の DynamoDB リゾルバの Scan で意図したデータが取得出来なくなった時の対処法

# はじめに

開発中盤で AppSync で開発している最中に追加したデータが、
突然 List Query で取得出来ない事態が発生しました... :scream_cat: 

調査したところ、
AppSync のデータソースで DynamoDB のデータソース生成した際に GraphQL を自動生成したのですが、
そこにヒントがあることを発見しました:bulb:

結局の所、**DynamoDB への理解不足**で片付く話なのですが、、
何となく AppSync を使っていると遭遇してしまう問題だと思います... :punch:  

そこで、同じ問題で悩む人を助けられればと思い、
記事に残しておくことにしました :writing_hand: 

# DynamoDB に登録されているはずのデータが AppSync の List Query で取得したデータに含まれない事象が発生することがある

AppSync で開発を開始した直後や、
登録データ数が少ない間は List Query の `filter` を用いてデータの絞り込みを行いつつ、
正常に意図したデータが取得出来るはずです :arrow_down: 
![4a96c5227343327499ec4c380f65b661.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/cc50b341-12e1-b24b-d11b-307f6135d5ce.png)
![8b41f67abe2fc998ca2d6e97925d87ec.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/da8f0a8a-a82a-a324-8efc-914ffefe987d.png)

しかし、ある日 DynamoDB にデータが登録されているのに List Query の `filter` で、
正常に意図したデータが取れない事象が特定の List Query で発生するようになってしまいました... :upside_down:

![20b80b348009f35572b930aeb5bbdeb2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/1be2e0df-14d8-1037-8fcf-61c34352c8da.png)
![5492af978c4e56f29e6f8234d999344d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/2ea75241-c368-a6de-0e16-b06323e4d0a6.png)


DynamoDB には `channel_id` が `42713673-eb21-43ef-bdbc-f460989fb505` でデータが 1件登録されていることは確認済みなのに AppSync の List Query では 1件も取得できないのは何故でしょうか。。？？:thinking:

## DynamoDB データソース追加時に自動生成されたリゾルバを確認してみる

DynamoDB のデータソース登録時に自動で GraphQL 生成にチェックを入れていれば、
AppSync の List Query は自動生成され DynamoDB リゾルバが登録されています :white_check_mark: 

原因調査のため AppSync の裏の DynamoDB リゾルバが何を行っているか中を覗いてみます :mag:

![9a7d6dcbfb03aab6abfaf21cdf91a20a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/7cd7cb3a-d6e7-0a20-eba5-3d009afd758c.png)
![3a91f0e79dcd05153f6e52543975dcda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/e1251499-31e6-0366-4938-8eba8aeb7b29.png)

:arrow_up: を見ると、`Scan` を用いて `filter` で検索条件を絞って `limit` で指定した件数のデータが取得出来るようになっているように見受けられます :mag:

# DynamoDB の `Scan` の仕様を確認する

次に [DynamoDB の公式ドキュメント](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html) を見て `Scan` の仕様について確認していきます。
早速公式ドキュメントのトップに `Scan` についての説明が記載されていました :pencil: 
> Amazon DynamoDB での Scan オペレーションは、テーブルまたは セカンダリインデックス のすべての項目を読み込みます。デフォルトでは、Scan オペレーションはテーブルまたはインデックスのすべての項目のデータ属性を返します。

:arrow_up: を見ると `Scan` はデフォでレコードを全件取得するような挙動となるようです。

## 結果セットの項目数の制限

ドキュメントを読み進めていくと、
次に [結果セットの項目数の制限](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html#Scan.Limit) の項目が目に止まりました :eyes: 
> ここで、Scan にフィルタ式を追加するとします。この場合、DynamoDB は返される 6 つの項目にフィルタ式を適用し、一致しない項目を廃棄します。最終的な Scan 結果はフィルタリングされる項目の数に応じて、6 つ以下の項目を含みます。

:arrow_up: を見ると `Scan` の `filter` を使用した際の挙動として **一致しない項目を廃棄します** とあります :wastebasket: 
AppSync の DynamoDB リゾルバを確認した際、`Scan` の `limit` はデフォでは `20` でした。

つまり、正常にデータが取得出来なかった AppSync の List Query を元に、
実際に実行された DynamoDB の `Scan` の挙動を推測すると、
**データを 20件取得した中に該当する channel_id のデータが見つからなかったので空を返した**
という挙動になりそうです :pencil: 

RDBMS の SQL の Where 句のような、
**該当する channel_id のデータを 20件まで検索して取得する**
という挙動を想定していましたが、それがそもそもの誤りだったようです... :upside_down: 

## スキャンの読み込み整合性

更にドキュメントを読み進めていくと [スキャンの読み込み整合性](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html#Scan.ReadConsistency) という項目も発見しました :eyes: 

> Scan オペレーションは、結果的に整合性のある読み込みをデフォルトで行います。つまり、Scan 結果が、最近完了した PutItem または UpdateItem オペレーションによる変更を反映しない場合があります。詳細については、「Read Consistency」を参照してください。

> 強力な整合性のある読み込みが必要な場合は、Scan が開始する時に ConsistentRead パラメータを true リクエストで Scan に設定できます。これにより、Scan が開始する前に完了した書き込みオペレーションがすべて Scan 応答に含められます。

:arrow_up: を見ると RDBMS では保証されている一貫性は無いので、
**`Scan` を実行するタイミングによって取得可能なデータは変動する可能性がある**
ということを `Scan` を用いたデータ取得を行う際は考慮する必要がありそうです :memo:

# 対策方法

まず、AppSync の `limit` がデフォで `20` なので GraphQL クエリの `limit` を `100` 等の、
一度の Query で `filter` で絞り込むデータが十分に取得出来そうな数に設定しておきます[^1] :printer:

![8bbf374ac035a470852cad3f425466ab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/aa4ea623-8a99-d07c-fc8f-2741d19d808e.png)

また、Web アプリケーション等でよく使用される、画面最下部までスクロールした際にロードを行う処理の実装等を考慮した場合、`nextToken` を用いたページネーションの実装も考慮しておく必要があります :white_check_mark: 

![d3e7bd3b6c78b2c47f1693baa83fca03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/20c06143-130c-02ef-3d35-99f0daf01b2b.png)
![6e5f9b851e6d2af8b0e83b716f3a6cc3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/2633187b-8214-5503-2f37-c9bbfe794ffe.png)

:arrow_up: の流れで List Query を実行するようにしておけば、
画面最下部までスクロールした際のロード処理が実装可能です :thumbsup: 

# おわりに

結果 AppSync というよりも DynamoDB の仕様にフォーカスに当たった記事内容になりました :writing_hand:

AppSync の DynamoDB リゾルバを雰囲気で扱っていたため起きた問題で、
ステージング環境で実際にデータがある程度追加されてきてから問題が発覚したため、
個人でデバッグしているときには全く気づくことが出来ませんでした... :bomb: :boom: 

また保守運用の観点から予め DynamoDB の設計についても考慮しておいたほうが良いと思われます :raised_hand: 
公式ドキュメントに [DynamoDB の設計のベストプラクティス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/best-practices.html) についての解説ページがあるので読んでおいた方が良さそうです :book: 

以上 AppSync を用いる際に DynamoDB リゾルバを自動生成して利用する際は十分にご注意ください :bow: 

# 参考リンク

- [DynamoDB のリゾルバーのマッピングテンプレートリファレンス - AWS AppSync](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html#aws-appsync-resolver-mapping-template-reference-dynamodb-scan)
- [DynamoDB でのスキャンの使用](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html)
- [Amazon DynamoDBのNextTokenを使ったページネーションの挙動を調査した](https://www.playfulit.net/articles/2019/06/22/investigate-dynamodb-nexttoken-pagination/)
- [DynamoDB を使用した設計とアーキテクチャの設計に関するベストプラクティス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/best-practices.html)

[^1]: 大量のデータが登録されている状況であれば `Scan` の Query から `limit` は無くしてしまい、一度の Scan で最大の 1.0MBまでのデータを取得するようにして良いかも知れません