DynamoDB で COUNT、SUM、集計を行う方法
DynamoDB には組み込みの集計がちょうど 1 つだけあります。Select=COUNT によるマッチした
アイテムのカウントです。ネイティブの SUM、AVG、MIN、MAX はありません。しかも
取得できる カウントですら、数えたすべてのアイテムを読み取り(そして課金し)ます。
このガイドでは、実際に何がサポートされているか、人々が頼りにする近似手段、そして必要な
ときに COUNT/SUM/AVG を本当にテーブルに対して実行する方法を扱います。
DynamoDB は SUM、COUNT、集計関数を実行できるか?
ほとんどできません。DynamoDB の唯一の組み込み集計は Select=COUNT で、マッチしたアイテムのカウントを返しますが、それでもすべてのアイテムを読み取り(そして課金し)ます。ネイティブの SUM、AVG、MIN、MAX はなく、PartiQL もそれらを追加しません。GROUP BY を伴う本物の集計には、アプリ内で畳み込むか、カウンターを維持するか、DynoTable の Workbench で SQL を実行します。
Select=COUNTはマッチしたアイテムの数を返しますが、DynamoDB はそれを算出するために すべてのアイテムを読み取ります — 安い「カウント」コストではなく、Scan/Queryの完全な 読み取りコストを支払います。- ネイティブの
SUM、AVG、MIN、MAXはない。 DynamoDB の読み取り操作はアイテムを 返すもので、それらを 1 つの数値に畳み込みはしません。PartiQL も集計を追加しません。 DescribeTable.ItemCountは無料ですが 近似値で、「およそ 6 時間ごとに」しか 更新されません — ダッシュボードのタイルには十分ですが、正確さが必要なものには誤りです。- 正確な
COUNT/SUM/AVG/MIN/MAX(GROUP BY付き)には、アプリ内で集計するか、 カウンターを維持するか、DynoTable の SQL Workbench(後述)で実行します。
アイテムを数える:Select=COUNT
Query と Scan はどちらも Select パラメータを受け付けます。それを COUNT に設定
すると、レスポンスはアイテムの代わりにカウントを運びます。
aws dynamodb scan \
--table-name Orders \
--select COUNT \
--filter-expression "#s = :open" \
--expression-attribute-names '{"#s":"status"}' \
--expression-attribute-values '{":open":{"S":"OPEN"}}'レスポンスは 2 つの数値を与えます(AWS: Counting the items in the results)。
Count— 「フィルタ式(もしあれば)が適用された 後 に残るアイテムの数」。ScannedCount— 「ScanFilterが適用される 前 に評価されたアイテムの数」。 フィルタがなければ、ScannedCountはCountと同じです。
だけを持っていて、その中の重複を数える必要があるなら、渡す
条件 + フィルタはまさに DynamoDB 式ビルダー が
生成するもの — 上記の FilterExpression と ExpressionAttributeNames/Values マップ、
そして Query で 1 つのパーティション内を数えるときの KeyConditionExpression —
であり、JSON を手でエスケープする必要はありません。
大きなテーブルを数える人々を悩ませる、もう 2 つの落とし穴。
- 1 MB のページ制限は依然として適用される。 「
Scanの結果セットのサイズが 1 MB を 超える場合、ScannedCountとCountは総アイテム数の部分的なカウントのみを表します」 (AWS Scan ドキュメント)。 各レスポンスのLastEvaluatedKeyを次のリクエストのExclusiveStartKeyとして渡し、 実際の数値を得るために累計を保持しながらページ送りする必要があります — これは DynamoDB のページネーション で扱うのと同じループです。 - 絞った
QueryはScanに勝る。Queryに対するSelect=COUNTは、テーブル全体では なく、対象パーティション内のアイテムだけを計測します。パーティションキーを固定できるなら (ベーステーブルでも GSI でも)、そこで数えましょう — カウントに適用された Query と Scan のコスト差です。
Select=COUNT vs ItemCount(そしてなぜそれが古いのか)
DescribeTable は ItemCount(と TableSizeBytes)を無料で、読み取りコストなしで返します。
落とし穴は
API リファレンスそのもの
にあります。「DynamoDB はこの値をおよそ 6 時間ごとに更新します。最近の変更はこの値に
反映されていない場合があります」。つまりテーブルの実際の状態からかなり遅れることがあります。
Select=COUNT | DescribeTable.ItemCount | |
|---|---|---|
| 正確さ | 正確(マッチしたセットについて) | 近似 |
| 鮮度 | ライブ | およそ 6 時間ごとに更新 |
| コスト | 数えた各アイテムを読み取り・課金 | 無料(メタデータ) |
| サブセットのフィルタ / カウント | 可(フィルタ式) | 不可 — テーブル全体のみ |
ItemCount は「このテーブルはどれくらい大きいか」というざっくりした感覚のチェックや
ダッシュボードのタイルに使いましょう。正確な、フィルタされた、または最新の数値が必要な
ときは Select=COUNT を使い、読み取りコストを受け入れましょう。本当にライブで無料の
ものが必要なら、自分でカウンターを追跡してください(下の 集計パターン を参照)。
なぜネイティブの SUM/AVG/MIN/MAX がないのか
DynamoDB の読み取り操作はアイテムを返します。結果セットをスカラーに畳み込むクエリ
プランナーがないため、SUM や AVG を計算する対象がそもそもありません。API が提供する
唯一の畳み込みはカウントで、Select=COUNT を介します。
PartiQL はこれを変えません。
PartiQL SELECT の文法
は SELECT {{expression}} [, …] FROM {{table}}[.{{index}}] [WHERE …] [ORDER BY {{key}} …]
で、ここで expression は「* ワイルドカード、または 1 つ以上の属性名やドキュメントパスの
射影リストから形成される射影」です。その文法には集計関数も GROUP BY 句もありません —
そして ORDER BY は {{key}} を取り、「返される結果を並べ替えるために使うハッシュキー
またはソートキー」と文書化されています。すべての PartiQL SELECT は依然として GetItem、
Query、Scan にコンパイルされるため、SELECT SUM(total) FROM "Orders" は単純に表現
できません。(PartiQL の上限については PartiQL vs SQL で詳述。)
集計パターン(カウンター、Streams、アプリ側)
DynamoDB が代わりに集計してくれないため、確立されたパターンは作業を別の場所に押しやります。
- 維持されるカウンターアイテム。 専用のアイテム(例:
PK = "STATS#orders")を保持し、 書き込みごとにUpdateItemで数値属性にADDします。集計の読み取りはそれから単一のGetItemになります — 正確で安いですが、インクリメントのロジック、その整合性、そして 1 つのカウンターが叩かれ続けたときの競合を自分で所有します。 - がアグリゲーターに供給する。 ストリームを有効にし、
アイテムが変わるたびに実行中の合計(カウント、sum)を更新する Lambda に配線します。
AWS Streams ドキュメント
によれば、ストリームの
StreamViewTypeを設定して各レコードがNEW_AND_OLD_IMAGES— 「アイテムの新旧両方のイメージ」 — を運ぶようにでき、これは再スキャンなしでSUM型の 集計を最新に保つのに十分です。ストリームレコードは 24 時間の寿命の対象なので(「シャード 内のストリームレコードは 24 時間後に自動的に削除されます」)、コンシューマーは追いつく 必要があります。 - アプリ側での畳み込み。 マッチしたアイテムをページ送りし、自分のコードで
SUM/AVG/MIN/MAXを累積します。正しいですが、毎回すべてのアイテムを読み取り (そして課金し)ます —Select=COUNTと同じコストプロファイルに、データ転送が加わります。 - 分析にオフロードする。 重いまたはアドホックな分析集計には、テーブルを S3 に エクスポートして Athena でクエリするか、ウェアハウスにストリームします。 AWS の S3 エクスポートドキュメント によれば、エクスポートは「読み取りキャパシティユニットを消費せず」、「Athena などの AWS サービスを使って分析や複雑なクエリを実行」できます — リクエストごとの集計から卒業したら、 AWS が推奨するパスです。
それぞれが、書き込み時の帳簿付け(カウンター、Streams)または読み取り時のコスト(アプリ側
スキャン)と引き換えに、シンプルさを得ています。DynamoDB 自身に SUM を無料で計算させる
パターンはありません。このトレードオフのグルーピング版 — テーブル全体ではなく キーごとに
集計する — は、それ自身のガイドです:DynamoDB GROUP BY。
DynoTable の SQL Workbench で COUNT/SUM/AVG を実行する
ページ送りするスキャンループや Lambda を書かずに、ただ答えが欲しいだけのとき —
「OPEN の注文はいくつあり、その合計はいくらか」 — DynoTable の SQL Workbench は本物の
集計を実行します。DynamoDB の実際の Query/Scan ランタイムを通してテーブルを
マテリアライズし、その上で単一の SELECT を実行します — 集計、GROUP BY、HAVING、
DISTINCT。DynamoDB のアクセスパターンのルールの中の SQL です。
-- Runs in the DynoTable Workbench (NOT in PartiQL):
SELECT status,
COUNT(*) AS orders,
SUM(total) AS revenue,
AVG(total) AS avg_order,
MIN(total) AS smallest,
MAX(total) AS largest
FROM orders
GROUP BY status
ORDER BY revenue DESCこれは計算された集計に対する COUNT、SUM、AVG、MIN、MAX、GROUP BY、ORDER BY
であり — そのいずれも DynamoDB や PartiQL は表現できません(PartiQL の ORDER BY はキー
属性に限られます) — それを 1 つのステートメントで。これは DynamoDB のための SQL
と同じ分析的なくさびです。完全なグルーピングの話は
DynamoDB GROUP BY を参照してください。
Workbench は Postgres のふりをするのではなく、その下のアクセスモデルについて正直です。
- 行は依然として DynamoDB の実際の Query/Scan を通して来ます。テーブル全体に対する
GROUP BYは、その下では依然としてScanです — Workbench はそのコストを隠すのではなく 表面化させます。これは同じ Query と Scan のトレードオフです。 - 集計は、行が着地した後、マテリアライズされたスカラー属性に対して実行されます。
よくある質問
スキャンせずに DynamoDB のアイテムを数えられますか?
厳密にはできません。正確で最新のカウントには、アイテムを読み取る必要があります —
Select=COUNT は依然として数えた各アイテムを計測します。スキャンなしの選択肢は、近似の
DescribeTable.ItemCount(およそ 6 時間ごとに更新)か、書き込みごとに自分で維持する
カウンターアイテムだけです。
GSI でアイテムを数えるには?
インデックスに対して Select=COUNT を付けて Query(または Scan)を実行します。絞った
GSI パーティションを介したカウントは、そのインデックスパーティション内のアイテムだけを
読むため、ベーステーブルをスキャンするよりはるかに安価です — 必要なカウントに合わせて
インデックスをモデル化しましょう。
DescribeTable.ItemCount は正確ですか?
近似です。
API リファレンス
は、DynamoDB が ItemCount と TableSizeBytes を「およそ 6 時間ごとに」更新し、「最近の
変更はこの値に反映されていない場合がある」と述べています。正確または最新の数値が重要な
場所では使わないでください。
DynamoDB は SUM や AVG を実行できますか?
ネイティブにはできず、PartiQL でもできません —
PartiQL SELECT の文法
には集計関数がありません。アプリケーション内で集計するか、カウンターを維持するか
(任意で DynamoDB Streams 経由で)、DynoTable の SQL Workbench で SUM/AVG を実行します。
Count と ScannedCount の違いは?
ScannedCount はフィルタ前に DynamoDB が評価したアイテム数で、Count はその後に残る数です。
フィルタ式がなければ両者は等しくなります。両者の大きな差は、非効率なカウントを意味します。
スキャンループを書かずに DynamoDB のデータを合計、平均、グループ化する必要がありますか? DynoTable をダウンロード して Workbench タブで実行しましょう。まずクライアントを 比較しますか?プレーンな DynamoDB GUI との位置づけをご覧ください。