[超初心者向け] AWS DynamoDBマネジメントコンソールでアイテムを検索する時に気を付けること(スキャンとクエリの使い分け)

# はじめに

お世話になっております。ノベルワークスのファービー（[@furbydayonn](https://x.com/furbydayonn)）です。
3日後に控えた健康診断に向けて過酷な減酒中です。

さて今回は、

- DynamoDBを操作するようなコードは普段書かない、DynamoDBのことはよくわかってない、そもそもエンジニアじゃない
- けど、何かしらの目的でDynamoDBに直接アクセスして保管されているデータ(アイテム)を探し出すことがある

そんな方に向けて、テーブル内を検索する時の注意点やケースに応じてどうすべきかをまとめました。

※本記事の内容は2025年1月20日時点の画面構成・仕様に準じます。

## 本記事では扱わないこと

- DynamoDBの詳細な仕様
- テーブル設計
    - 本来スキャンを利用するケースが無いように設計すべきですが、そのあたりには触れません
- コンソール画面の詳細な操作方法

## 本記事で一番伝えたいこと

スキャンとクエリはケースに応じて適切に使い分けよう！

#  スキャンとクエリ

DynamoDBのアイテムを検索する際の方法として、 **スキャンとクエリ** の2つがあります。
マネジメントコンソールでテーブルにアクセスした時、デフォルトではスキャンが選択されています。

![スクリーンショット 2025-01-21 111123.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/118bbc3d-75c2-7318-3d49-d5f2da279257.png)

クエリの存在を知らない場合、このままなんとなく「あ、フィルターってあるな、これで絞り込めばいいんだな」となり、スキャンを使ってしまう傾向が多いように感じています（少なくとも私の観測範囲では）。
ですが、**スキャンは極力使用すべきではありません。**

取得したいデータにもよりますが、 **基本的にはまずクエリで取得できないかを考えて** 、クエリでは取得が難しい場合にやむを得ずスキャンを使用します。

# 何故スキャンを使用すべきではない？

以下は、テーブルサイズが約1GBあるテーブルに対し、クエリでパーティションキーを指定した場合と、スキャンでフィルタによってパーティションキーを絞り込んだ際の消費キャパシティです。
キャパシティについては「どれぐらい負荷がかかったか」を表しています。

クエリ

![スクリーンショット 2025-01-21 130818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/3da1edd7-0fe9-36d0-cae3-689b2f80b958.png)

スキャン

![スクリーンショット 2025-01-21 130852.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/29ecb287-338f-9931-b43e-0f1d15607b35.png)

注目すべきは右端の`RCUs consumed`です。
得られる結果（アイテム）は同じですが、スキャンを使うかクエリを使うかによって、負荷に約16倍の差が生まれています。
なんならスキャンの方はエラーが出ています。これはテーブルに設定している処理上限を超えてしまったことによるエラーです。

キャパシティの詳細については以下を参照してください。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/capacity-mode.html

## `RCUs consumed` (消費キャパシティ) が高いと何がまずい？

テーブルの設定により影響が変わります。
DynamoDBの課金形態として

1. 使った分だけ課金（従量課金）
2. 予め決めた使用量に対する課金

の2つがあります。
2の方が単価は安くなりますが、予め使用量を計算・予測する必要があります。
要件やコストを鑑みてどちらにするかを選択します。

### オンデマンドキャパシティモードの場合 ☞ お金がかかる💸＆ユーザーにも迷惑かけるかも🥹

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/on-demand-capacity-mode.html

1の課金形態を`オンデマンドキャパシティモード`といいます。
この場合、単純に **課金量が増えます。**

また、あまりにも消費キャパシティが大きい場合は処理上限超過によるエラー(`ProvisionedThroughputExceededException`)が発生します。
**エラーが発生しているテーブルを参照しているアプリケーションに影響を及ぼす、つまりユーザーに迷惑をかける可能性があります。**

オンデマンドキャパシティモードにおける処理上限（最大スループット）については以下を参照してください。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/on-demand-capacity-mode-max-throughput.html

### プロビジョンドキャパシティモードの場合 ☞ ユーザーに迷惑かけるかも🥹

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/provisioned-capacity-mode.html

2の課金形態をプロビジョンドキャパシティモードといいます。
事前に最大これぐらい使いそう、と予測して設定した閾値に応じて課金されます。
なのでどれだけ負荷をかけようが料金は変わりません。
ただし閾値を超える負荷をかけた場合、先述の処理上限超過によるエラー(`ProvisionedThroughputExceededException`)が発生します。

---

いかがでしょうか。
 **スキャンをやみくもに使うと何か痛い目に合いそう** 、というのがなんとなく理解いただけたのではないかと思います。

なおキャパシティモードの確認は、テーブルの詳細情報の「追加の設定」タブから確認できます。

![スクリーンショット 2025-01-21 133654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/d4e3aa0c-9dbc-30db-9eed-7251b89f0554.png)

# 用語の説明

この後、ケースに応じた検索方法（スキャンとクエリの使い分け）を紹介していきますが、その前に用語について簡単に説明します。
なんとなくわかっている方は読み飛ばしてもらって大丈夫です。

#### パーティションキーとソートキー

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html

パーティションキーとソートキーの組み合わせは、テーブル内のアイテムを一意に特定するために使用されます。

- パーティションキー：データの保存場所を決定する主要な識別子
- ソートキー：同じパーティションキー内でのデータの並び順を決定。指定しないことも可能。

**パーティションキーのみ設定している場合:** パーティションキーは重複することができません。
**パーティションキーとソートキーを設定している場合:** パーティションキーは重複することができますが、パーティションキー＋ソートキーの組み合わせは重複することができません。

#### インデックス

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SecondaryIndexes.html

テーブルに対して、パーティションキーとは異なる属性でも効率的に検索できるように、インデックスを作成することができます。

例えば以下のようなテーブルがあるとします。

| パーティションキー: company_code | ソートキー: user_email |
| -------------------------------- | ---------------------- | 
| company_a                        | 1@a.com                |
| company_a                        | 2@a.com                |
| company_b                        | 1@b.jp                 |

ソートキーである`user_email`だけを指定して検索したい場合、このままでは`スキャン＋フィルタ`による高負荷な検索しかできません。
しかしこのユースケースは頻繁に発生するので、負荷はかけたくありません。
こういった場合に`user_email`をパーティションキーとしたインデックスを作成することで、負荷を減らすことができます。
ただしインデックスを作成することで費用は増加します。

#### フィルター

スキャンやクエリで得た結果から、更に絞り込みを行います。
注意すべき点としては、フィルタによって絞り込みを行っても、 **処理にかかる負荷（消費キャパシティ）は変わりません**


# ケース別💡スキャンとクエリの使い分け

以下、ケースに応じた取得方法を記載します。
なお本記事のサンプルテーブルではインデックスは存在していない想定ですが、
実際のテーブルにおいてインデックスが用意されている場合には適切に利用してください。

## サンプルテーブル

パーティションキーとソートキーがどちらも設定されているテーブルまたはインデックスとなります。

以下のような、会社別の担当者を管理するテーブルがあるとします。
※説明のしやすさ優先の設計となっています。予めご了承ください。

| パーティションキー: company_code | ソートキー: user_email | position | 
| -------------------------------- | ---------------------- | -------- | 
| company_a                        | 1@a.com                | Manager  | 
| company_a                        | 2@a.com                | Staff    | 
| company_b                        | 1@b.jp                 | Staff    | 

### ケース1: 取得したいデータのパーティションキーの値のみわかっている

**クエリを使用** してください。

例: **特定の会社`compnay_a`の担当者を一覧で取得したい**

![スクリーンショット 2025-01-21 120201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/ba2e0250-165f-5e49-5f4a-faec686d17ec.png)

### ケース2: 取得したいデータのソートキーの値のみわかっている

ソートキーのみで検索することはできない為、**スキャンを使用してください。**

例: 特定のメールアドレス（担当者）`2@a.com`がすでに存在しているか確認したい

![スクリーンショット 2025-01-21 123631.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/fb98208a-4187-47f5-9238-2f2988ffcf53.png)

### ケース3: 取得したいデータのパーティションキーとソートキーの両方の値がわかっている

クエリを使用してください。

例: `company_a`の`2@a.com`の情報を取得したい

![スクリーンショット 2025-01-21 124215.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/5e2d31bb-b0f4-71f9-1832-415a8056cf95.png)

### ケース4: 取得したいデータのパーティションキーとソートキーの両方の値がわからない

スキャンを使用してください。

例: `position`が`Manager`な人の一覧を取得したい

![スクリーンショット 2025-01-21 124631.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/9a9e3471-9127-d343-d696-74d7fe62d9f5.png)

例: とにかくテーブルの全情報が欲しい

![スクリーンショット 2025-01-21 124739.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/65b722eb-46f9-7ad1-8ecb-67e3e25c7f1f.png)

# Tips

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

スキャンまたはクエリを一度実行した際に取得できるアイテム数は、ページサイズで設定されている件数に準じます。（最大300件）
取得した結果ページサイズ以上のアイテムが存在している場合は、`Retrieve next page`というボタンが表示されるので、クリックすることで残りのアイテムを取得することができます。

![スクリーンショット-2025-01-21-130457-01-21-2025_01_05_PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356430/a38f89c2-b00e-2e9d-33ff-8108cc05869c.png)

## ソートしたい

テーブルの属性名をクリックすることで、任意の属性値でソートすることが可能です。
ソートの対象は、 **取得済みのアイテム** になります。
なので、一部データのみ取得した状態でソートした場合、期待した結果とならない可能性があります。
**すべてのデータを取得した上でソートする** ようにしましょう。

### 参考）取得した時点でのソート順は？

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html#HowItWorks.Partitions.CompositeKey

> デフォルトでは、項目は保存されている順序 (つまり、ソートキーによって昇順でソート) で返されます

## SQLを使いたい

PartiQLを利用することでSQLに似た構文でアイテムを検索することができます。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ql-gettingstarted.html