中級読了 1 分

DynamoDB のコストモデル: なぜ SQL は請求を見えなくするのか

DynamoDB 上の SQL は、ネイティブにはそのどれも提供しないデータベースの上で、JOIN、 GROUP BY、任意の WHERE 句を与えてくれます。リレーショナルエンジンはそれらにプランナーと、あなたが作成したインデックスで答えますが、DynamoDB がインデックスを張るのは、あなたが設計したキーだけです。

まったく正当な SELECT が、テーブル内のすべてのアイテムを読み取り、それに課金するフルテーブル Scan にコンパイルされることがあります。

DynamoDB 上の SQL は、なぜ見た目より高くつくのか?

リレーショナルデータベースでは任意の列にインデックスを追加でき、プランナーはそれを使ってすべての行を読まずに WHERE status = 'active' に答えます。

DynamoDB が効率的に答えられるのはただ1つ、テーブル上、またはあなたが定義したセカンダリインデックス上のパーティションキーだけです (オプションでソートキーによって絞り込めます)。それ以外はすべて Scan です。

  • パーティションキーの等価条件は Query です。 DynamoDB はそのキー配下のアイテムに直接ジャンプし、それらだけを読み取ります。コストはそのパーティションのアイテムに比例します。
  • それ以外はすべて Scan + Filter です。 DynamoDB はテーブル内のすべてのアイテムを読み取り、その後で WHERE を FilterExpression として、読み取りの後に適用します。課金されるのは Scan したすべての分であって、返ってきたわずかな行の分ではありません。

WHERE 句は作業量を絞り込むように見えます。しかしキー以外の属性では、絞り込むのは出力だけで、読み取りコストはすでに費やされています。

どの SQL 構文が Scan にコンパイルされるのか?

  • パーティションキーの等価条件がない WHERE → 読み取り後にフィルターをかけるフル Scan。
  • JOIN → サーバーサイドの結合は存在しません。クライアントは結合される行ごとに 1回のキー参照を行い、それぞれがアイテムの読み取りとして課金されます。ベーステーブルのキーは BatchGetItem 1回あたり最大 100 件までまとめられ、GSI でキー付けされた側はキーごとに1回の Query です。頻繁に実行する結合では、両方のエンティティを1つのアイテムコレクション (同じパーティションキー、異なるソートキーのプレフィックス) に格納し、1回の Query で両方が返るようにしましょう。
  • COUNT、SUM、GROUP BY、集計 → サーバーサイドの集計はカウント (Select=COUNT) だけで、それでも Scan したすべてのアイテムを読み取って課金します。頻繁に必要な集計には、データが変わるたびに UpdateItem の ADD で更新する累計のアイテムを保持しましょう (AWS はこれを materialized aggregation と呼んでいます)。
  • キー以外の属性に対する ORDER BY や DISTINCT → 並べ替えと重複排除は、Scan された対象に対してクライアントサイドで行われます。テーブル全体の上位 N 件には、定数のパーティションキー (ランク付けされるすべてのアイテムに GSI1PK = "ALL") とその属性をソートキーに持つ GSI を追加し、ScanIndexForward=false と Limit で Query します。テナントごとや月ごとの上位 N 件なら、代わりにそれで GSI をキー付けします。定数のキーはインデックスへのすべての書き込みを受け止めます。1つのパーティションが処理できるのは最大で毎秒 1,000 書き込みユニットで、ソートキーが増え続ける場合、DynamoDB は負荷を分割して逃がせません。それを超えるなら、シャーディング (ALL#0 から ALL#9) して、各シャードの上位 N 件をクライアントサイドでマージします (ホットパーティションを参照)。

これらのどれも実行して間違いというわけではありません。Scan がまさに望むものであることもあります。肝心なのは、自分がいつ Scan を実行しているのかを知ることです。

フル Scan 1回のコスト

Scan は読み取る 4 KB ごとに課金され、結果整合性のある読み取りなら 4 KB あたり 0.5 読み取りユニットです。1 GB のテーブルは1回のフルパスあたりおよそ 131,000 読み取りユニット (強力な整合性なら 262,144) になり、us-east-1 の定価のオンデマンドで約 $0.016 です。

パスのたびにこれを支払い、FilterExpression はこの数値を下げません。ユニット単位の比較は Query vs Scan にあります。自分のデータでの数値は、アイテムサイズ計算ツールと料金計算ツールで算出してください。

下のカーブは、読み取りの多い Scan ワークロードを両方のキャパシティモードで料金計算したものです。平均使用率をスライドさせて、プロビジョニングしたスループットに支払うほうがリクエストごとに支払うより安くなるのはどこからかを確かめてください。

オンデマンド対プロビジョンド: コストと使用率
3,000 /s
100 /s
20 %

平均使用率 20% のとき: 月額でオンデマンド $328.50、プロビジョンド $474.50。

プロビジョンドピークの平均使用率が 28.9% を下回るとオンデマンドが安く、上回るとプロビジョンドが有利です。

使用率オンデマンドプロビジョンド割安
10%$164.25$474.50オンデマンド
25%$410.63$474.50オンデマンド
28.9%損益分岐点$474.50$474.50—
50%$821.25$474.50プロビジョンド
75%$1,231.88$474.50プロビジョンド
100%$1,642.50$474.50プロビジョンド

料金: 米国東部 (バージニア北部)、強い整合性のある読み取り、無料利用枠なし。リクエストコストのみ — ストレージはどちらのモードでも同じ料金で課金され、損益分岐点を動かすことはありません。 ピーク: 読み取り 3,000 回/秒、書き込み 100 回/秒、アイテムサイズ 4 KB。

SQL クエリをキーに乗せ続けるには?

  • アクセスパターンを中心にキーを設計しましょう。 最も安価なクエリは、キースキーマがすでに答えられるものです。シングルテーブル設計ツールと Query vs Scan ガイドで計画しましょう。
  • WHERE にパーティションキーの等価条件を入れて、Scan の代わりに Query にしましょう。
  • GSI を追加して、Scan してフィルターする代わりに2つ目のアクセスパターンに対応しましょう。
  • 実行する前にプランを読みましょう。 DynoTable の Workbench は SQL を DynamoDB のオペレーションにコンパイルし、エディターの下に表示します。Scan か Query か、使用するインデックス、そして見積もりがある場合は読み取られるアイテム数の見積もりです。テーブルをまたぐ JOIN には、行ごとの参照についての警告がインラインで表示されます。
  • 見積もりはヒントとして扱いましょう。 RCU の見積もりは、テーブルのメタデータが対応している場合にのみ表示されます。通常のパーティション Query では表示されません。Scan はなぜ遅く高コストなのかとオンデマンド vs プロビジョンドキャパシティも参照してください。

DynoTable の SQL はコスト問題を悪化させるのか?

DynamoDB 上の SQL は短い SELECT の背後に Scan を隠しうる、というのはもっともな批判です。 DynoTable は SQL を残したまま、実行する前にコンパイルされたプランを表示するので、Scan に支払う前にそれが Scan だと分かります。表示される数値は、請求額ではなく実行前のヒントとして扱ってください。

試してみる

DynoTable をダウンロードして、自分のテーブルに対して SQL Workbench を開いてみてください。エディターの下のプランが、実行する前に各クエリが Scan か Query か、そしてどのインデックスを使うかを示してくれます。

著者: Dmitriy · 更新日