はじめに
こんにちは。アプリケーションサービス部の保田(ほだ)です。
最近 Future Funk なる音楽ジャンルにハマっており、仕事中 BGM としてずっと流しています。
というわけで今回は DynamoDB のストレージ容量を自力で算出することを試みます。
注意: 私の推測を多分に含むため、無理やり概算したければこういう方法もありかもしれない、と参考程度に見ていただければと思います。
要約
フラットなテーブル構造であれば 「キー1」「値1」「キー2」「値2」…
とツメツメに並べたデータ量がどうやらストレージ容量に一致するっぽいぞ
モチベーション
ストレージ容量 TableSizeBytes
は
項目数 ItemCount
と合わせて DescribeTable API から得られる値になります。
マネジメントコンソール上でも確認できまして、それぞれ以下から確認することができます。
- 古いコンソール
- 新しいコンソール
さて、この二つの値ですが注意すべき仕様があります。 CLI のリファレンス に端的な表現がありますので、こちらの表現を引用しますと、
TableSizeBytes -> (long)
The total size of the specified table, in bytes. DynamoDB updates this value approximately every six hours. Recent changes might not be reflected in this value.
訳:「指定されたテーブルの全体のバイト数。 DynamoDB はこの近似的な値を6時間ごとに更新する。直近の変更はこの値に反映されない場合がある。」
ItemCount -> (long)
The number of items in the specified table. DynamoDB updates this value approximately every six hours. Recent changes might not be reflected in this value.
訳:「指定されたテーブルのアイテム数。 DynamoDB はこの近似的な値を6時間ごとに更新する。直近の変更はこの値に反映されない場合がある。」
はい。リアルタイムの値が得られるわけではないということですね。(上で貼ったコンソールのスクショ内でもちゃんと説明が書かれています。)
「近似的な値」という点は、だいたいの運用において一番大きな桁の数字さえ分かれば後はそこまで重要ではない場面が多いと考えられますのでまぁ良いでしょう。実質容量は無限(※1)ですし。
※1
テーブルのサイズには実用的な制限はありません。テーブルは項目数やバイト数について制限がありません。
ですが、もし今スグに容量が知りたい!となった場合はどうしたら良いでしょうか。
今日はもっともナイーブな解決策として「 Scan して全部ファイルに書き出してそれを数える」方法を模索してみます。
後でわかった別のアプローチ
DynamoDB Local を使用すれば、 DescribeTable API から得られる TableSizeBytes
と ItemCount
の値は即座に更新されるようです。
Docker で手軽に動かせますし、 API の向き先の URL を変えるだけでローカルと実際のテーブルを簡単に切り替えられますので、このやり方で済む場合は以降の行は読まなくても大丈夫です。
本題
項目数
ご存じの方もいらっしゃるかもしれませんが、実は項目数だけであればコンソール上でリアルタイムの値が得られます。
それぞれ以下から得られます。
- 旧コンソールでは ライブ項目数の管理
- 新コンソールでは ライブ項目数を取得
これは Scan API を使用してテーブルをスキャンして現在の項目数を取得します。
画面上でも警告(?)が出るように、キャパシティーユニットを消費しますので、本番環境で非常に大きなテーブルや頻繁に読み書きが行われるテーブルに対して何も考えずに実行すると、本番のワークロードに影響が及ぶ可能性があります。
ですが、これで一応アイテム数は取れます。
ですので、各アイテムのおおよそのデータ量が分かれば、それにこのアイテム数を掛け算してやればおおよそのストレージ容量は算出できそうです。
1アイテムの容量
ここからは完全に推測の世界になります。
では、1アイテム(リレーショナルデータベースのアナロジーで以降は便宜上「レコード」と呼びます)の容量はどうやれば算出できるのでしょうか。
あれこれ検証してみた結果おそらくこうだろう、というものをご紹介します。
今回は簡単のためフラットな Key-Value 形式で書けるデータの場合について説明します。
Value にリストやオブジェクトを含んだフラットでない形式についてもいくつか検証してみましたが、まだ納得のいく公式が見いだせていないため今回は割愛させていただきます。
例えば次のようなテーブルを考えます。
Id | Key1 | Key2 |
---|---|---|
1 | ラーメン | hoge |
2 | 寿司 | fuga |
3 | カレー | piyo |
これの1レコードあたりの容量は以下で算出できそうだということに気付きました。
Id1Key1ラーメンkey2hoge
そして全レコードについては以下のようにして算出できそうです。
Id1Key1ラーメンkey2hogeId2Key1寿司key2fugaI32Key1カレーkey2piyo
要するに冒頭に書いた 「キー1」「値1」「キー2」「値2」…
の形です。これを何かしらプレーンなテキストファイルに書き出し、 そのバイト数を調べると DescribeTable API から得られる値とだいたい一致します。
ストレージ容量
上記をベースにして、 Scan API で取得した結果を 「キー1」「値1」「キー2」「値2」…
の形に落とし込み、そのバイト数をカウントするスクリプトをサクッと書いてみます。
まず適当な DynamoDB のテーブル SampleTb を作成します。
これに以下のスクリプトでデータを登録します。
import boto3 dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('SampleTb') items = [ {'Id': '1', 'Key1': 'ラーメン', 'Key2': 'hoge'}, {'Id': '2', 'Key1': '寿司', 'Key2': 'fuga'}, {'Id': '3', 'Key1': 'カレー', 'Key2': 'piyo'} ] with table.batch_writer() as batch: for item in items: batch.put_item(Item=item)
そしてアイテム一覧を取得するスクリプトを作成します。
名前は何でもよいですが get_storage.py
とします。
import boto3 dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('SampleTb') items = [] kwargs = {'Limit': 1} # 適当な値に設定します while True: response = table.scan(**kwargs) items += response.get('Items') last_evaluated_key = response.get('LastEvaluatedKey') # ページネーション対応 if last_evaluated_key: kwargs['ExclusiveStartKey'] = last_evaluated_key else: break # リストから取り出してつなげていく result = '' for i in items: for k,v in i.items(): result += f'{k}{v}' print(result)
これを実行することで欲しい形(の末尾に改行が一つくっついたもの)が標準出力に出ます。
Key1寿司Id2Key2fugaKey1ラーメンId1Key2hogeKey1カレーId3Key2piyo
したがって、適当なテキストファイルにリダイレクトしてやれば良いことになります。
$ python get_storage.py > result.txt $ du -b result.txt # バイト数を確認 73 result.txt
末尾に改行コードが一つくっついていますので、その分の 1 バイトを差し引いてやれば 72 バイトという結果になります。
気になる方はぜひ試していただきたいですが、この値はマネジメントコンソール上で得られる値、すなわち DescribeTable API で得られる値と一致します。
本当にこれで合っているかは不明ですが、そもそも DescribeTable API で得られる値も近似的なものらしいので、実用上はこれで良いのではないかな~~~と思います。
補足
Global Secondry Index (GSI)はカウントしなくていいの?と思われた慧眼なる読者様もいらっしゃるかと思います。
実は GSI については完全に別のテーブルとしてストレージが確保されているようです。
これは、マネジメントコンソールを見て頂くと、それぞれの GSI について サイズ と 項目数 が表示されていることからも推測することが出来ます。
気になる方は先ほどのサンプルコード中で以下の箇所(7行目)を
kwargs = {'Limit': 1}
このように変えて実行していただくことで確かめられるかと思います。
kwargs = {'Limit': 1, 'IndexName': '何かしらのインデックス名'}`
おわりに
しつこいようですが あくまで推測ですのでご利用は自己責任でお願いします!