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回のキー参照を行い、それぞれがアイテムの読み取りとして課金されます。ベーステーブルのキーはBatchGetItem1回あたり最大 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 ワークロードを両方のキャパシティモードで料金計算したものです。平均使用率をスライドさせて、プロビジョニングしたスループットに支払うほうがリクエストごとに支払うより安くなるのはどこからかを確かめてください。
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 か、そしてどのインデックスを使うかを示してくれます。