初級読了 3 分

DynamoDB のデータ型:全型を例付きで

DynamoDB のすべての属性は、ワイヤ形式で短い型コード(S、N、BOOL、SS、…)が付けられます。この型は値の格納方法とアイテムサイズへの数え方の両方を左右するため、どんな型があるかを知っておくことが重要です。

DynamoDB はどんなデータ型をサポートしていますか?

DynamoDB は3つのカテゴリにわたる10種類のデータ型をサポートしています。スカラーは String(S)、 Number(N)、Binary(B)、Boolean(BOOL)、Null(NULL)です。ドキュメントは Map(M)と List(L)で、他の型をネストします。

セットは String Set(SS)、Number Set(NS)、Binary Set(BS)で、順序なし・同種・空でない、という性質を持ちます。キーにできるのは S、N、B のみです。

コード型カテゴリJSON / JS 相当例(DynamoDB-JSON)
SStringスカラーstring{"S": "Ada"}
NNumberスカラーnumber{"N": "37"}
BBinaryスカラーUint8Array / base64{"B": "ZGF0YQ=="}
BOOLBooleanスカラーboolean{"BOOL": true}
NULLNullスカラーnull{"NULL": true}
MMapドキュメントobject{"M": {"k": {"S": "v"}}}
LListドキュメントarray{"L": [{"N": "1"}]}
SSString setセットJSON にはなし、JS は Set{"SS": ["a", "b"]}
NSNumber setセットJSON にはなし、JS は Set{"NS": ["1", "2"]}
BSBinary setセットJSON にはなし、JS は Set{"BS": ["ZA=="]}

DynoTable のアイテムエディタでは、どのアイテムもプレーン JSON と DynamoDB JSON を切り替えられるので、以下で説明する型タグを自分のデータで確認できます(手順)。

スカラーは1つの値を持つ

  • S: 文字列(UTF-8。文字数ではなくバイト長でサイズが決まる)。
  • N: 数値。精度を保つため文字列として送られ、最大38桁。
  • B: バイナリ。base64 エンコードで送られる。
  • BOOL: true / false。
  • NULL: 明示的な null マーカー。

マップとリストはほかの型を入れ子にする

  • M: マップ(オブジェクト)。ネストされた各属性は自身の型タグを保持する。
  • L: リスト。要素は型が混在してもよい。
{"profile": {"M": {"name": {"S": "Ada"}, "age": {"N": "37"}}}}

セットは型付きで、重複がなく、空にできない

  • SS(文字列セット)、NS(数値セット)、BS(バイナリセット)。

セットは順序なし・同種で、空にはできません。素の JSON にはセット型が存在しないため、配列はリスト(L)としてラウンドトリップし、SS/NS にはなりません。 DynamoDB-JSON コンバーターでその違いを確認できます。

アプリケーションコードでは、セットを明示的に指定します。JS SDK v3(marshall、またはドキュメントクライアント)は JavaScript の Set を DynamoDB のセットに、配列をリストに変換します。boto3 も Python の set で同じことをします。

import {marshall} from '@aws-sdk/util-dynamodb';

marshall({tags: new Set(['a', 'b']), scores: new Set([1, 2]), history: ['a', 'b']});
// {tags: {SS: ['a', 'b']}, scores: {NS: ['1', '2']}, history: {L: [{S: 'a'}, {S: 'b'}]}}

セットのつもりの配列は静かなバグです。重複が積み重なり、保存された属性が L なので ADD tags :newTags の更新は ValidationException で失敗します。

{"SS": []} は拒否されるため、「タグなし」は属性がない状態としてモデル化します。 DELETE tags :t を使う UpdateItem が最後の要素を削除すると属性そのものが削除されるので、読み取り時には属性がないことを空のセットとして扱いましょう。

セットはキーになれません。大規模にセットのメンバーシップでアイテムを検索するには、メンバーごとに1つのアイテム(隣接アイテム)を書き込んでそれをクエリします。そうでなければ、キー条件の後でセットに対してフィルタします。

どの型をキーにできますか?

キーとキーは、テーブル上でも任意のインデックス上でも、スカラー、つまり S、N、B のいずれかでなければなりません。boolean、set、map、list をキーにはできません。

複数の値をキーにするには、それらを1つの S に連結します。TENANT#acme#PROJECT#42 のような合成キーです(複合ソートキー)。

GSI では代わりにマルチ属性キーを宣言できます。パーティションキーとソートキーにそれぞれ最大4つの属性を、連結なしで使えます(AWS ドキュメント)。

知っておきたい制限

  • 1つのアイテムの上限は 400 KB です。ネストされたものも含め、すべての属性名と値が対象です。
  • 数値は最大38桁の精度を持ちます(正・負のいずれも)。
  • マップとリストは32レベルまで深くネストできます。
  • セットは空でなく同種です。空のセットも、S と N の混在もできません。

なぜ型がコストに影響するのか

アイテムサイズは属性名のバイト数と値のバイト数の合計で、各型のサイズの数え方は異なります。数値は圧縮され、boolean と null は1バイト、マップとリストは要素ごとのオーバーヘッドが加わります。

読み取りはそのサイズを 4 KB 単位で、書き込みは 1 KB 単位で課金します(キャパシティモード)。アイテムサイズ計算機で実際のアイテムを計測してください。

実際の単位でのサイズ例

最小限のユーザープロフィールを取り上げます。

{
  "pk": {"S": "USER#42"},
  "email": {"S": "a@example.com"},
  "plan": {"S": "pro"},
  "score": {"N": "1280"}
}

このアイテムをアイテムサイズ計算機に貼り付けると、 42 バイトと表示されます。4 KB の読み取りブロック1つに十分収まるので、GetItem は結果整合性で 0.5 RCU、強い整合性で 1 RCU、書き込みは 1 WCU です。

2 KB の bio 文字列を足すと、アイテムは 2,093 バイトになります。読み取りはまだ 4 KB のブロック1つに収まりますが、書き込みは 3 KB に切り上げられ、3 WCU になります。

ネストしたマップは要素ごとに課金されます。子属性の名前と型付きの値がそれぞれバイトを加えます。M の中に M が深くネストした木は、同じ情報を持つフラットな属性よりも速くアイテムを 400 KB の上限へ押し上げます。

型を意図して選ぶ

選んだ型は、キー、サイズ、そしてクエリを制約します。

必要なもの推奨避けるもの理由
プライマリキーやインデックスキーS、N、BBOOL、M、L、セットキーはスカラーの S/N/B でなければならない
正確な小数の金額文字列としての Nアプリコード内の JSON 数値ワイヤ上で38桁の精度
アイテム上の一意なタグSS文字列の Lセットは重複排除、リストは重複可
順序のある履歴L またはソートキーSSセットは順序なし
ネストしたプロフィールMフラット化した S の JSONマップは型付きの子要素を保持
バイナリのサムネイルBS に入れた Base64B がネイティブのバイナリ型

数値が文字列として運ばれる理由

DynamoDB は N の値を最大38桁で保存し、文字列として送ります。JavaScript の数値は IEEE-754 の倍精度浮動小数点で、正確なのは Number.MAX_SAFE_INTEGER (9,007,199,254,740,991)までです。

桁が失われるのは素の JSON です。JSON.parse は 9007199254740993 を何も言わずに 9007199254740992 に変えてしまいます。JS SDK v3 は入口ではより厳格で、marshall はその上限を超える JS の数値に対して例外を投げます。

出口では挙動がまちまちです。素の unmarshall は大きな整数を BigInt (9007199254740993n)として返し、12345678901234567890.5 のような大きな小数では例外を投げ、長い小数は黙って丸めます。3.14159265358979323846 は 3.141592653589793 として返ってきます。

すべての桁を保つには、unmarshall(item, {wrapNumbers: true})(ドキュメントクライアントの unmarshallOptions.wrapNumbers)で読み取ります。これは 1 も含めてすべての数値を、 .value が正確な文字列である NumberValue として返すので、算術演算や === での比較の前に変換してください。大きな値は BigInt か、@aws-sdk/lib-dynamodb の NumberValue として書き込みます。

式のプレースホルダも、数値を型タグ付きの文字列として運びます。ExpressionAttributeValues の中の {":n":{"N":"42"}} です。式ビルダーはそのマップを出力するので、タグのないリテラルが ConditionExpression に入り込むことはありません。

DynoTable でやってみる

DynoTable のアイテムエディターは、フォーマット切り替えでセットとリストの区別を明示します。

  • プレーン JSON: プリミティブは素のまま("age": 30)ですが、セットは型ラッパーを保持してラウンドトリップに耐えます: "tags": { "SS": ["a", "b"] }、"scores": { "NS": ["1.5", "2.5"] }。新しいセットも同じ形で入力します。
  • DynamoDB JSON: AWS 標準の形式で、すべての 値が型タグを持ちます: "age": { "N": "30" }、"name": { "S": "alice" }。

両者を切り替えると、各スカラー・ドキュメント・セット型がワイヤ上でどう見えるかが分かります。セットのつもりだったリストは DynamoDB JSON では L として現れるので、SS として書き直しましょう。フッターには、編集に合わせてアイテムの推定サイズが表示されます。

数値セットと文字列セットを持つアイテムを表示している DynoTable のアイテムエディターと、プレーン JSON / DynamoDB JSON の切り替え。
数値セットと文字列セットを持つアイテムを表示している DynoTable のアイテムエディターと、プレーン JSON / DynamoDB JSON の切り替え。

アプリなしでマーシャリング済みのブロブを変換するには、 DynamoDB-JSON コンバーターがブラウザ内で同じラウンドトリップを行います。

CLI で属性ディスクリプタを手書きする代わりに、型付きのコントロールでアイテムを編集するにはDynoTable をダウンロードしてください。

著者: Dmitriy · 更新日