中級読了 4 分

DynamoDB GROUP BY: GROUP BY 句なしで集計する方法

DynamoDB に GROUP BY はありません。COUNTSUMAVG もありません — ネイティブ API にも、 PartiQL にもです。DynamoDB は分析エンジンではなくキーバリュー / ドキュメントストアなので、集計は クエリプランナーが代わりにやってくれるものではなく、あなた が構築するものです。

DynamoDB で GROUP BY はできますか?

いいえ。DynamoDB には GROUP BYHAVING も、COUNTSUMAVG のような集計関数もありません — ネイティブ API にも、SELECTWHEREORDER BY しか受け付けない PartiQL にもです。集計は、データが変わるときに合計を事前計算する(アトミックカウンター、または Streams + Lambda のロールアップ)か、読み取った後にアプリ側でグループ化することで行います。

  • DynamoDB の PartiQL SELECT 文法は SELECT … FROM … [WHERE …] [ORDER BY …] — そしてそれがリストのすべてです。GROUP BY も、HAVING も、集計関数も、JOIN も ありません(AWS PartiQL SELECT リファレンス)。
  • DynamoDB は「項目をまたいだ SUMCOUNT のような集計操作をネイティブにサポートしない」ため、 AWS 自身の指針は、データが変わるときに集計を 事前計算 し、結果を通常の項目として保存することです (AWS: マテリアライズド集計)。
  • 代替案 — すべての項目を読んでからアプリで集計する — は動きますが、クエリのたびに テーブル全体を読む対価を払います。
  • 一度きりの探索には、DynoTable の SQL Workbench がライブテーブルに対して GROUP BY / COUNT / SUM / AVG を直接実行します — DynamoDB の PartiQL エンドポイントが拒否する SQL です。

なぜ DynamoDB では集計が難しいのか

DynamoDB にはスキャン時の集計エンジンがありません。QueryScan は項目を返します。 それらを畳み込みません。Scan はテーブル全体を一度に 1 MB ずつ読み、消費するキャパシティは、 保持する行ではなく読み取る項目に基づきます — FilterExpression はスキャンの、結果が返るに 適用されるので、請求を下げずに結果セットを絞りますAWS Scan API リファレンス: フィルターは 「追加の読み取りキャパシティユニットを消費しない」。キャパシティは返された項目ではなく スキャンされた項目サイズに基づく)。そもそも sum や count を引っ掛ける GROUP-BY フックがありません。

PartiQL もこれを変えません。PartiQL は同じエンジンの上の SQL互換の方言なので、同じ制限を 継承します — 新しい実行モデルではなく、構文の表面です。文書化された SELECT 文法には 単に GROUP BY トークンがありません。 PartiQL と本物の SQL の全ギャップはPartiQL vs SQLを参照してください。

なので問いは「GROUP BY をどう書くか」ではなく、「集計はどこに存在し、いつ計算されるか」です。 答えは3つあります。

パターン1: 書き込み時に集計(アトミックカウンター)

グループを事前に分かっている場合 — ステータスごとのカウント、顧客ごとの合計、月ごとの ダウンロード数 — カウンター項目を保ち、すべての書き込みで更新します。

ADD 更新式を使って、インクリメントをアトミックかつ並行性に安全にします。 ADD は数値とセットに対して動き、読み取り-変更-書き込みの競合を避けるので、同じカウンターを インクリメントする2つのライターが互いを潰すことはありません (AWS はアトミックな ADD が「読み取り-変更-書き込みの競合状態を避ける」と記しています)。

UpdateItem
Key                         { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression            "ADD orderCount :one"
ExpressionAttributeValues   { ":one": 1 }

これがあなたの SELECT COUNT(*) … GROUP BY status です — ただしカウントはすでに項目として そこにあり、1桁ミリ秒の GetItem で読めます。トレードオフ: グループ化キーを書き込み時に 知っている必要があり、カウンター更新を書き込みパスに結合します。書き込みのカウンター更新のに アプリがクラッシュすると、両者は同期からドリフトします — それこそ次のパターンが分離する障害モードです。

パターン2: DynamoDB Streams + Lambda のロールアップ

集計ロジックを書き込みパスに置きたくない場合 — または書き込みが簡単にラップできない素の PutItem の場合 — それを下流へ移します。これは AWS 自身の推奨パターン、マテリアライズド集計です (AWS: マテリアライズド集計クエリのための GSI の使用)。

  1. アプリが生の項目(注文、ダウンロード、イベント)を書きます。集計ロジックなし。
  2. DynamoDB Streams がその書き込みをストリームレコードとして捕捉します。
  3. ストリームにアタッチされた Lambda が新しい項目を読み、グループ(ステータス、月、 カテゴリ…)を導出し、アトミックな UpdateItem で一致する集計項目に ADD します — 多くの呼び出しが同じカウンターに触れるとき「読み取り-変更-書き込みの競合状態を避け」ます。
  4. 事前計算された集計をクエリします — しばしば、ロールアップ項目だけをインデックスする スパース GSI を通して、「今月のトップ10」は Limit 10 付きの1つの Query になります。

スパース GSI のトリック: 集計項目だけがインデックス属性(例 Month)を持つので、生のイベント行は 自動的にインデックスから除外されます — 「テーブルの全項目のごく一部」で、インデックスを安価に、 読み取りを速く保ちます。

これは集計を書き込みパスから分離し、書き込みを単純に保ちます。代償は 結果整合性 です — AWS は「ダウンロードが記録されてから集計が更新されるまで数秒の遅延」と記しています。ダッシュボード、 リーダーボード、トレンドカウンターにはこれで問題ありません。

同じ再試行の注意が適用されます: 再試行された Lambda 呼び出しは ADD を再実行するので、 「再試行はカウントを2回以上インクリメントする」ことになり、概算の値が残ります。正確なカウントには 冪等性を加えてください(例 ソース項目の id をキーにした条件式)。そうでなければ、分析や リーダーボードには小さなマージンで問題ありません。

パターン3: Scan/Query 後のアプリ側グループ化

力技の選択肢: 項目を読み、自分のコードでグループ化します。

groups = {}
for item in paginate(table.scan()):       # 1パーティションには query()
    key = item["status"]
    groups[key] = groups.get(key, 0) + 1

これは正しく、ときに正しい判断です — が、コストについて正直になりましょう。Scanテーブル内のすべての項目 を読み、読み取りキャパシティはフィルターするかどうかに関わらず同じです。 だからフル Scan に対するアプリ側グループ化は、集計のたびにテーブル全体を読む対価を払うことを 意味し、レイテンシーはテーブルとともに増えます。AWS は「読み取り時のスキャンとカウント」を 「レイテンシーが問題にならない非常に小さなデータセットにのみ適する」と挙げています (AWS: なぜ集計を事前計算するか)。

Query 経由で単一パーティションに絞れば(例 1顧客の注文を数える)、アプリ側グループ化は まったく合理的です — 1つの項目コレクションを読むだけです。両者のフルコスト差は Query vs Scanを参照してください。所定の集計スキャンが実行前に何を 読むかを見積もるには、項目サイズ計算機で代表的な項目を 測ってください — 読み取りキャパシティは 4 KB ごとに切り上げなので、 項目サイズが請求を左右します。

DynamoDB テーブルに対する真にアドホックな分析 SQL — 一度だけ実行する使い捨ての 「GROUP BY status、それらを数える」 — には、AWS の答えは別のエンジンを向けることです。 Amazon Athena DynamoDB コネクタ が、Lambda コネクタ経由で本物の SQL(GROUP BY、集計、 他のソースへの JOIN まで)でテーブルをクエリできます (AWS: Amazon Athena DynamoDB コネクタ)。 裏でテーブルをスキャンするので、ホットパスではなくレポーティング/BI ツールです。

どのパターンを使う?

必要なもの…使うもの
ホットな読み取りパスでの既知のグループ合計パターン1 — アトミックカウンター(ADD
書き込みパスに触れずに集計パターン2 — Streams + Lambda ロールアップ
1パーティションに絞ったカウントパターン3 — Query してアプリでグループ化
ドリフトなしの正確な合計パターン1/2 冪等性ガード付き
探索中の一度きりの GROUP BYDynoTable Workbench(下記)または Athena
SQL での反復的な BI/レポーティングAthena DynamoDB コネクタ

DynoTable の SQL Workbench で GROUP BY を直接実行する

上記のパターンは、本番で集計を提供する方法です。でもテーブルを探索しているとき — 「ステータスごとに注文は今いくつ?」 — Lambda をプロビジョンしたり Athena を立ち上げたりは したくありません。クエリを打ち込みたいのです。

それが DynoTable の SQL Workbench の用途です。本物の SQL — GROUP BYCOUNTSUMAVGHAVINGJOIN まで — を、ライブの DynamoDB テーブルに対して 直接実行し、読み取った行に対して集計をクライアント側で行います。DynamoDB の PartiQL エンドポイントが 拒否する SQL です。

SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESC

正直な位置づけ: 内部で DynoTable は API が許す方法(できるところは Query、必要なところは Scan)で項目を読み、マテリアライズし、グループ化を Workbench で行います — パターン3 と同じ 「読んでから集計」の仕組みで、ループがないだけ、そして DynamoDB のアクセスパターンのルール内 で。 探索とアドホック分析 のために作られており、ホットな読み取りパスで本番のロールアップを 置き換えるためではありません。そのためには事前計算(パターン1 / 2)です。

同じくさびの JOIN 側 — DynoTable は PartiQL もできないクロステーブル結合を実行します — は DynamoDB JOINを参照してください。まさにこの能力で GUI クライアントを 比較しますか?DynamoDB GUI 比較を参照してください。

FAQ

DynamoDB PartiQL は GROUP BY をサポートしますか? いいえ。DynamoDB の PartiQL SELECTWHEREORDER BY だけをサポートします — GROUP BYHAVING、集計関数、JOIN はありません。文法は 文書化されており、 SELECT … FROM … [WHERE …] [ORDER BY …] です。

DynamoDB テーブル全体に対して COUNT(*) できますか? 集計関数としては無理です — PartiQL にはありません。API は Scan/QuerySelect=COUNT を提供し、これは一致した項目のカウントを返しますが、スキャンが触れる すべての項目を依然として読み(課金され)ます (AWS Scan API リファレンス: キャパシティは返された項目ではなく調べた項目に基づく)。頻繁に読む合計には、カウンター項目を 保ってください(パターン1)。

パーティションキーで GROUP BY できますか? DynamoDB でも PartiQL でも無理です。「パーティションキーごと」が既知のアクセスパターンなら、 キーごとに1つの集計項目をアトミックな ADD で維持するか(パターン1)、Streams + Lambda で ロールアップしてください(パターン2)。

グループごとの SUMAVG はどうすれば? SUM: グループごとの累計を保ち、書き込み時にそれに ADD します。AVG: 合計とカウントの両方を 保存し、読み取り時に割ります — ネイティブの平均はありません。一度きりの探索的な AVG には、 DynoTable の SQL Workbench または Athena DynamoDB コネクタ経由で実行してください。

partiql group by の回避策はありますか? PartiQL 側のものはありません。集計を事前計算(カウンター/Streams)してロールアップ項目を SELECT するか、GROUP BY を持つエンジンで実行してください — アドホックには DynoTable の Workbench、反復的なレポーティングには Athena です。


Lambda を書かずに自分のテーブルに対して GROUP BY を実行したいですか? DynoTable を試して、SQL Workbench をライブテーブルに向けてください。

更新日