Amazon DynamoDBは、RDSのようなインスタンスサイズによる課金モデルではなく、ストレージのデータ使用量とスループットを基にした課金モデルになっている。 インスタンスサイズによる課金モデルでないデータストア系サービスとして、他にはS3、Kinesisなどがある。 これらは、AWSの中でも、フルマネージドサービスと呼ばれる位置づけとなるサービスだ。 フルマネージドサービスは、ElastiCacheのようなそうでないものと比較し、AWSに最適化されていて、サービスとしてよくできていると感じている。
Mackerelの時系列データベースのスタックの一つとして、DynamoDBを採用している。時系列データベースという概念をクラウドの技で再構築する - ゆううきブログ 時系列データベースの開発は、コストとの戦いだったために、それなりにコスト知見が蓄積してきた。
(※ 以下は、2018年4月16日時点での情報を基にしている。)
DynamoDBのコスト構造
冒頭に書いたように、「ストレージのデータ使用量」と「スループット」を基にするため、インスタンスサイズモデルと比較して、よりアプリケーションコードがダイレクトに反映されるコスト構造になっている。
正確なコスト定義は、公式ドキュメントを参照してほしいが、これを初見で理解するのはなかなか難しい。データ使用量のコストモデルはGB単価なため把握しやすい。しかし、スループットに関するキャパシティユニットの概念が難しい。 以下では、各要素について、メンタルモデルを説明する。 AWS Calculatorに適当な値をいれてみながらコストを見てみるとだいたいの感覚をつかめると思う。
ストレージのデータ使用量
ストレージのGB単価は、$0.25/GBであり、S3のStandardストレージクラスと比較して、およそ10倍程度となる。 こう聞くと割高に聞こえるが、画像やブログのテキストデータなどを格納しなければ*1、それほど高いというわけではない。 実際、1TBのデータ使用に対して、$300/月程度のコストとなる。
DynamoDBはSSD上に構築されている*2ようなので、安定して低レイテンシという性能特性があるため、大容量データをひたすら保持するというよりは、エンドユーザに同期的に応答するようなユースケースに向いている。
スループット
スループットによるコストには、「秒間のread数/write数」と「対象のアイテムサイズ」が要素として含まれる。 S3のように月のAPIコール数の合計により課金されるわけではなく、予めどの程度のスループットとなるかを予測し、事前にキャパシティとして確保しておく必要がある。 したがって、見込んでいる最大のスループットにあわせてキャパシティ設定することになり、正味のリソース消費量よりも余分にコストがかかることに注意する。 ただし、後述するAuto Scalingにより、余分なコストを抑えられる可能性がある。
スループットによるコストは、大雑把には以下の特性をもつ。
- 「秒間read数/write数」に比例してコストが大きくなる
- アイテムサイズを1KBに固定したときの「秒間write数」の単価は「秒間read数」の約5~6倍程度となる。読み取り整合性を結果整合性のある読み込みにすると、秒間read数のコストは1/2となる
- readまたはwriteしたアイテムのサイズに比例してコストが大きくなる
- 「アイテムサイズ」のコスト単位はreadとwriteで異なる。readは4KB単位で比例し、writeは1KB単位で比例する。
- グローバルセカンダリインデックスおよびローカルセカンダリインデックスを利用する場合、writeする度に、内部的にインデックスの更新作業が走るため、writeコストが大きくなる。*3
2番目、4番目、5番目の特性がwriteスループットの大きいユースケースでDynamoDB利用が割高と言われる所以である。 同条件でのread(strong consitency)とwriteの差は、アイテムサイズが最小単位の場合では5~6倍程度となる。 さらに、アイテムサイズがより大きい場合では、readとwriteの差はより大きくなる。
複数アイテム操作
DynamoDBは、同時に複数のアイテムを操作するAPI(BatchGetItem、BatchWriteItem)があり、これらを用いてアプリケーションの性能を改善することができる。 しかし、基本的には、キャパシティユニットの消費を抑えられるわけではないため、これらのAPIを用いてコストが下がるわけではない。 ただし、Queryについては、単一の読み取りオペレーションとして扱われる。これについては最近まで見落としていた。 キャパシティーユニットの消費の詳細については、 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/CapacityUnitCalculations.html に書かれている。
フィルタ式
フィルタ式を用いると、クライアントとDynamoDB間のネットワーク通信料を削減できる。 しかし、DynamoDB内部の読み取りオペレーションの数が減るわけではないため、キャパシティユニットの消費を抑えることはできない
コスト試算表
以下の表では、いくつかのケースで、スループットコストを試算している。実際には、readとwriteのワークロードは両方発生するため、readとwriteのコストの合計値となる。コストはap-northeast-1のもの。
アイテムサイズ(KB) | read or write | 秒間オペレーション数 | 読み取り整合性 | コスト(USD) |
---|---|---|---|---|
1 | read | 1000 | strong | 100 |
1 | read | 5000 | strong | 540 |
4 | read | 5000 | strong | 540 |
40 | read | 5000 | strong | 5400 |
40 | read | 5000 | eventually | 2700 |
1 | write | 1000 | - | 500 |
4 | write | 1000 | - | 2000 |
4 | write | 5000 | - | 10800 |
表をみるとわかるように、writeのコストが大きくなりやすい。
ネットワーク
同一リージョンの他のAWSサービスとの間で転送されたデータは無料となる。S3と基本的に同じ。
ただし、プライベートサブネットにあるクライアントからDynamoDBへ転送されたデータには、VPC NAT Gateway での転送処理コストが発生する。 https://aws.amazon.com/jp/vpc/pricing/によると、TokyoリージョンではGB単価が$0.062であり、AZ間通信の$0.010/GBの6倍あるため、あなどれないコストになる。
コスト削減策
知る限りのDynamoDBのコスト削減策を書いてみる。ここにある方法以外の策がほしい場合は、そもそもDynamoDBに向いてないユースケースか、アーキテクチャレベルで再設計する必要があるかもしれない。
リザーブドキャパシティ
DynamoDBには、EC2のリザーブドインスタンスのように前払いにより、コストを下げるリザーブドキャパシティがある。 https://aws.amazon.com/jp/dynamodb/pricing/によると、前払いした分のキャパシティ消費が$0になるわけではなく、別途時間料金が設定されており難しい。 最大効率では、1年間前払いで、約50%程度の割引になり、3年間前払いでは、約75%程度の割引になるはず。
Auto Scaling
前述したように、DynamoDB Auto Scalingにより、固定的なキャパシティ割り当てから、実際に消費したキャパシティユニットにより近づけることで、コスト削減できる。 ただし、一日のキャパシティ削減回数には限りがあるため、インターバルの小さいキャパシティ増減には対応できない。
メモリキャッシュ & DAX
DynamoDBのシャーディング機構は、プライマリキーを内部ハッシュ関数への入力とし、内部的なノード配置を決定する。 したがって、特定プライマリキーにreadまたはwriteが集中すると、テーブルのキャパシティを増やしても、1つのノードの性能限界にあたってしまう。
readについては、前段にmemcachedのようなキャッシュを挟むことで対応できる。 もしくは、DynamoDBのインメモリキャッシュであるDynamoDB Acceleratorを使う。 DAXはDynamoDBのwrite throughキャッシュとして動作し、DynamoDBのストレージまで到達してから、DAX上のアイテムに書き込み、レスポンスを返す。 一方で、読み込みは、インメモリキャッシュのレスポンスを返すため、readが支配的なワークロードで効果を発揮する。 DAXの動作モデルについては、https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DAX.consistency.htmlが詳しい。 DAXは自分で使ったことがないので、DAXそのもののコストについては理解が浅いが、インスタンスタイプベースのコストモデルになっている。
テーブルデータ構造
前述したようにDynamoDBは、writeが支配的なワークロードで、コストが大きくなりやすい。 しかし、メインDBとなるMySQLやPostgreSQLはwriteスケールアウトしづらいため、DynamoDBを使いたいケースにはwriteが支配的であることは多いように思う。 スループットコストの特性からみて、基本的には、writeの回数を減らすか、アイテムサイズを小さくすることで対処する。
writeの回数を減らすには、一つのアイテムに詰め込んで書き込むことになる。 しかし、単純に詰め込んでも、アイテムサイズに比例して、一回あたりの書き込みコストが増加してしまう。 そこで、例えば、1KBが最小単位なため、1KB未満のデータを書き込んでいる場合は、1KBぎりぎりのサイズになるように、データを詰め込んで書き込む。
アイテムサイズを小さくするには、なんらかの手段で圧縮し、バイナリとして書き込むという手段がある。 バイナリとして書き込む場合は、アイテムの追記が難しい。追記するには、一旦アイテムのデータを読み出してから、データを連結して書き込む必要があり、読み出しコストが余分にかかる。リスト型やマップ型の要素としてバイナリ型を使って意味があるケースであれば、素直に追記できる。
数値はおおよそのサイズが「(属性名の長さ)+(有効桁数 2 あたり 1 バイト)+(1 バイト)」と書かれており、桁数ベースなので、バイナリとして扱うほうがサイズ効率はよい。 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/CapacityUnitCalculations.html
あとは、属性名の長さがアイテムサイズに含まれるので、長い属性名を付けている場合は、短くするとよい。
ネットワーク転送
NAT Gatewayのコストは、DynamoDB VPCエンドポイントにより回避できる。 S3とDynamoDBのVPCエンドポイントは、ゲートウェイVPCエンドポイントと呼ばれるタイプのエンドポイントで、プライベートなDNSエンドポイントが払い出されるわけではなく、VPCのルーティングテーブルを変更し、L3でルーティングする。 想像したものと違ったので、面食らったが、NAT Gatewayのコストは問題なく削減できる。
まとめ
DynamoDBのコスト構造と、自分が知るコスト削減手段を紹介した。 DynamoDBは、データモデルとコストモデルのための公式ドキュメントがもちろん揃っているのだが、計算式はそれなりに複雑になので、妥当な感覚を掴むまでに時間がかかった。 コスト見積もりし、サービスインしたのちに、実際の使用量を確認し、改善策を打つことで、徐々に理解が進んできた。 CPU、メモリなどのハードウェアベースのキャパシティプランニングとは異なり、アプリケーションロジックフレンドリーな計算モデルなため、アプリケーション開発者がコスト見積もりやスケーリング対応をしやすいサービスになっている。
ちなみにAWS Lambdaのコスト構造については、次のエントリ内で紹介している。コスト効率の悪いLambdaアプリケーションの性質に関する考察 - ゆううきブログ
*1:そもそも、DynamoDBはアイテムサイズが現在のところ400KBまでという制限がある https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html#limits-items
*2:https://aws.amazon.com/jp/dynamodb/
*3:プライマリキー単体と比較して、プライマリキー+ソートキーの複合キーの作成により、追加のwriteコストが発生することはないと認識しているが、ドキュメントを見つけられなかった。