初級読了 3 分

DynamoDB の Scan が遅くて高コストな理由

Scanテーブル内のすべてのアイテムを読み取り、その後でしかフィルタリングしません。 それは SQL の筋肉記憶で手を伸ばしてしまう操作であり、そして、あなたが後にしてきた RDS ボックスよりも レイテンシーを悪化させながら、静かに請求額をつり上げる操作です。

私の DynamoDB の Scan はなぜ遅くて高コストなのか?

ScanFilterExpression が動く前にテーブル内のすべてのアイテムを読み取るため、返ってくる行が どれほど少なくてもテーブル全体を読み取る分を支払うことになり、テーブルが大きくなるほど遅くなります。 修正策はほぼ常にキー付きの Query です — アクセスパターンをキーを中心にモデリングして、DynamoDB が すべてではなく1つのパーティションに触れるようにします。

  • Scan は毎回テーブル全体を読み取る。 結果件数ではなくサイズが、支払う額と所要時間を決めます。
  • FilterExpression はコストについての嘘。 それは読み取りが計量された に動くので、12件のアイテムを 返すのに1200万件の読み取り分を請求されることがあります。
  • Scan は成長するほど遅くなる。 キー付きの Query は横ばいのままです — テーブルがどれだけ大きくなっても 1つのパーティションに触れるだけです。
  • 修正策はほぼ常にモデリングであって、チューニングではない。 日常的な質問に答えるために Scan するなら、 あなたはキーを見落としています。

Scan が実際にすること

SQL から来ると、SELECT * FROM events WHERE type = 'checkout' はタダに感じられます — エンジンにはインデックスがあるか、ないかのいずれかですが、どちらにしても行が返ってきます。 DynamoDB にはそれを決めてくれるクエリプランナーはありません。

Scan はテーブル全体を 1 MB ずつ順に歩き、各ページをあなたの FilterExpression に渡します。 フィルターが拒否したものも読み取られ、計量され、なおあなたの請求に乗ります。 (AWS: テーブルのスキャン

それが罠です。フィルターは WHERE 句のように見えますが、変えるのは結果セットであって、コストではありません。 Scan はフィルターの有無に関わらず同じ読み取りキャパシティを消費します。(AWS: テーブルのスキャン

読み取りユニットを数える

DynamoDB は読み取りを (RCU) で計量します。1 RCU は最大 4 KB の アイテムの 読み取り1回分に相当します。 読み取りは その半分のコストです。より大きなアイテムは次の 4 KB に切り上げられます。(AWS: 読み取り/書き込み キャパシティモード

分析用テーブル ProductEvents を取り上げます。各行は追跡された1つのイベントです。

PK  = "TENANT#acme"
SK  = "TS#2026-06-23T14:08:55Z#evt_9f3a"
attrs: eventType, sessionId, userId, payloadBytes

これが1つの多忙なテナントの下に、それぞれ約 1 KB の 2,000,000 件のイベントを保持しているとしましょう。 あなたは今日のチェックアウトを求めます。反射的な手は次のとおりです。

Scan ProductEvents
FilterExpression: eventType = "checkout"

そのフィルターは40行を返すかもしれません。しかし Scan はまず 2,000,000 件のアイテムをすべて読み取りました。 それぞれ約 1 KB(4 KB あたり 1 RCU、結果整合性で 4 KB あたり約 0.5 RCU)で、40件のアイテムを返すために およそ 250,000 RCU を計量し — 約 2 GB のデータをページングしました。

今度はアクセスパターンをキーとしてモデリングし、代わりに Query します。

Query ProductEvents
PK = "TENANT#acme"
AND SK begins_with "TS#2026-06-23"

これは1つのパーティションの、一致したスライスだけを読み取ります。それら40件のチェックアウト行に、その日の 他のイベントを加えて約 2 MB になるなら、支払うのは 2 GB ではなく約 2 MB の読み取り分です。同じ答え、コストは ごくわずか — そしてテーブルが成長してもレイテンシーは横ばいのままです。

Scan 対 Query、計量した比較

Scan + フィルターキー付き Query
読み取りテーブル内のすべてのアイテム1つのパーティション、SK で絞り込み
課金対象キャパシティフィルター前のテーブル全体スライス内のアイテムのみ
この例約 250,000 RCU(約 2 GB)数百 RCU(約 2 MB)
レイテンシーテーブルサイズとともに増大テーブルが成長しても横ばい
結果件数コストについて何も決めない支払う額と一致する

この表が刻む教訓はこうです。Scan では結果件数と請求額は無関係です。Query では、両者は互いに追随します。

Scan する前に決める

偶発的な Scan のほとんどは1つの問いから生まれます。必要なパーティションを名指しできるか? できるなら Query です。できないなら、修正策はより大きなフィルターではなくキーです。

以下がその判断をフロー図にしたものです。

はいいいえはいいいえアイテムを読みたいパーティションキーは分かる?Query 1つのパーティションGSI でキー化できる?GSI を追加して QueryScan 最後の手段

その道はほぼ常に Query で終わります。キー — 既存のものでも追加できるものでも — がアクセスパターンに 合わないときにだけ、Scan へと落ちていきます。

パターンが現実的で反復的なのにベーステーブルではキー付けできないなら、それは グローバルセカンダリインデックス を追加して、その質問を Query にするシグナルです。 アクセスパターンを中心にキーを前もってモデリングすることがすべてです — シングルテーブル設計 を参照してください。

フィルターではなく、キー付きのクエリを書く

キーを超える条件が本当に必要なときは、すべてを FilterExpression に投げ込むのではなく、意図的に組み立てましょう。 DynamoDB 式ビルダーKeyConditionExpression と属性の プレースホルダーを生成してくれるので、DynamoDB が読み取りを計量する に、パーティションキーとソートキーが 絞り込みを行います — 後ではなく。

KeyConditionExpression: PK = :tenant AND begins_with(SK, :day)

Scan が実際に妥当なとき

Scan は禁じられているわけではありません — ただ、デフォルトとして間違っているだけです。本当に 「すべてを読む」つもりなら、それが正しいツールです。

  • 1回限りのエクスポートや手作業のバックフィル。
  • テーブル全体が数 KB しかない、小さな設定/ルックアップテーブル
  • 意図的にテーブル全体をページングするバックグラウンドジョブ。それらは1つの長い逐次的なクロールではなく、 Segment / TotalSegments でワーカー間に分割しましょう — です。(AWS: テーブルのスキャン

そして PartiQL も救ってはくれないことに注意してください。キー述語のない SELECT * FROM ProductEvents WHERE eventType = 'checkout' はまっすぐ Scan にコンパイルされます。それは SQL の衣をまとった同じ落とし穴です。 (完全な内訳は Query 対 Scan を参照してください。)

DynamoDB が表現できない GROUP BYJOIN、集計といったアイテム横断の分析が本当に必要なときは、 DynoTable の SQL Workbench がそれらを、テーブルをフル Scan で叩くのではなく、境界のある結果セットに対して クライアント側で実行します。

次のステップ

料金計算ツール でどちらのパターンにいくらかかるかを見積もり、 API レベルの対比については Query 対 Scan を読み、そして DynoTable をダウンロード して、これらを自分自身のテーブルに対して実行し、各アプローチが 実際に何件のアイテムを読み取るかを確かめましょう。

更新日