初級読了 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: Scanning tables

それが罠です。フィルタは WHERE 句に見えますが、結果セットを変えるだけで、コストは決して 変えません。Scan は、フィルタがあろうとなかろうと、同じ読み取りキャパシティを消費します。 (AWS: Scanning tables

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

DynamoDB は読み取りを 読み取りキャパシティユニット(RCU) で計測します。1 RCU は、最大 4 KB のアイテムの強い整合性のある読み取り1回を買います。結果整合性の読み取りはその半分です。大きな アイテムは次の 4 KB に切り上がります。(AWS: Read/write capacity mode

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

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

それが 2,000,000 件のイベントを保持し、各々約 1 KB で、すべて1つの忙しいテナントの 下にあるとします。今日のチェックアウトが欲しいとします。反射的な一手:

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 を計測し — 約 500 MB のデータをページ送りしました。

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

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

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

Scan と Query、計測して

Scan + フィルタキーイングされた Query
読み取りテーブル内の すべての アイテム1つのパーティション、SK で絞り込み
課金キャパシティフィルタ前のテーブル全体スライス内のアイテムだけ
私たちの例約 250,000 RCU(約 500 MB)数百 RCU(約 2 MB)
レイテンシテーブルサイズとともに増加テーブルが成長しても平坦
結果件数コストについて何も決めない支払うものと一致する

この表がエンコードする教訓:Scan では、結果件数と請求は無関係です。Query では、それらは 互いを追跡します。

Scan する前に決める

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

フロー図にした決定がこちらです。

はいいいえはいいいえアイテムを読む必要パーティションキーがわかる?Query 1つのパーティションGSI でキーイングできる?GSI を追加し、QueryScan 最後の手段

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

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

フィルタではなく、キーイングされたクエリを書く

キーを超えた条件が本当に必要なときは、すべてを FilterExpression に詰め込むのではなく、 意図的に構築しましょう。DynamoDB Expression BuilderKeyConditionExpression と属性プレースホルダーを生成するので、パーティションキーと ソートキーが絞り込みをします — DynamoDB が読み取りを計測する後ではなく、前に。

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

Scan が実際に問題ないとき

Scan は禁止ではありません — ただ間違ったデフォルトなだけです。本当に「すべてを読む」を 意味するときには、正しい道具です。

  • 手で実行する1回限りのエクスポート やバックフィル。
  • 小さな設定/参照テーブル — テーブル全体が数 KB のところ。
  • バックグラウンドジョブ — 意図的にテーブル全体をページ送りするもの。1本の長い順次 クロールではなく、Segment / TotalSegments でワーカー間に分割します — 並列スキャン です。(AWS: Scanning tables

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

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

次のステップ

キャパシティ計算機 でどちらのパターンがどれだけ かかるか見積もり、API レベルの対比は Query と Scan を読み、 DynoTable をダウンロード して自分のテーブルに対してこれらを実行し、消費した キャパシティを自分の目で見てください。

更新日