Partition Key
DynamoDBでは、データを識別するためにPrimary Keyを設定します。
最もシンプルなのがPartition Keyだけを使用する構成です。
UserId
────────
user001
user002
user003
Partition Key = UserId
とすると、UserIdによってItemを識別できます。
Partition Key + Sort Key
同じPartition Keyに複数のItemを保存したい場合は、Sort Keyを組み合わせます。
例えば注文履歴の場合、
Partition Key = UserId
Sort Key = OrderId
とできます。
UserId OrderId
──────── ────────
user001 order001
user001 order002
user001 order003
user002 order001
この場合、
UserId + OrderId
の組み合わせによってItemを一意に識別します。
同じユーザーの注文をまとめて取得したい場合にも便利です。
Query
特定のPartition Keyに属するデータを取得するときは、Queryを使用します。
UserId = user001
の注文だけ取得したい場合、
DynamoDB
↓
Query
↓
user001のデータ
とできます。
Sort Keyを使用して、
UserId = user001
かつ
OrderDate >= 2026-01-01
のように範囲を絞ることもできます。
Scan
Scanは、テーブルまたはIndexのデータを広く読み取ります。
DynamoDB Table
↓
Scan
↓
大量のItemを読み取り
Partition Keyを指定して効率的に検索するQueryと違い、Scanは大量のデータを読み取る可能性があります。
効率的に検索したい
→ Query
テーブル全体を確認したい
→ Scan
FilterExpression
ScanではFilterExpressionを使用して、条件に一致するItemだけを結果として返すこともできます。
ただし、FilterExpressionはデータを読み取った後に適用されます。
DynamoDB
↓
Scanで読み取り
↓
FilterExpression
↓
必要なItemだけ返す
そのため、FilterExpressionで結果を絞っても、Scanによって読み取られたデータに対するCapacity消費を減らすことはできません。
効率的に検索
→ Query / Index
Scan結果だけ絞り込み
→ FilterExpression
Parallel Scan
大容量のテーブルに対してScanを実行すると、読み取りに時間がかかる場合があります。
その場合、Parallel Scanを使用してScanを並列化できます。
DynamoDB Table
│
├─ Segment 0 → Worker A
├─ Segment 1 → Worker B
└─ Segment 2 → Worker C
TotalSegmentsで全体の分割数を指定し、それぞれの処理で異なるSegmentを指定します。
通常のScan
→ 順番にScan
Parallel Scan
→ 複数Segmentを並列にScan
ただし、並列化すると読み取りスループットを大量に消費する可能性があるため注意が必要です。
GSI(Global Secondary Index)
Primary Keyとは別の条件で検索したい場合に利用するのがGSIです。
元のテーブルが、
Partition Key = UserId
Sort Key = OrderId
だったとします。
Status = SHIPPED
の注文を検索したくなった場合、元のPartition Keyでは効率的に検索できません。
そこで、
GSI
Partition Key = Status
Sort Key = OrderDate
を作成します。
Original Table
UserId + OrderId
↓
GSI
Status + OrderDate
これによって、
SHIPPEDの注文を取得
といった別のアクセスパターンに対応できます。
Provisioned Capacity Modeでは、GSIに独自のRCU / WCUを設定します。
Table
RCU / WCU
│
↓
GSI
独自のRCU / WCU
GSIへの書き込みは元テーブルへの書き込みに伴って発生するため、GSI側の書き込みCapacityが不足すると、元テーブルへの書き込みもThrottlingされる可能性があります。
LSI(Local Secondary Index)
LSIもSecondary Indexですが、GSIとは制約が異なります。
LSIでは、元テーブルと同じPartition Keyを使用し、Sort Keyだけ変更します。
例えば、
Table
Partition Key = UserId
Sort Key = OrderId
に対して、
LSI
Partition Key = UserId
Sort Key = OrderDate
のように設定できます。
つまり、
同じPartition Key
別のSort Key
→ LSI
です。
Provisioned Capacity Modeでは、LSIは元テーブルとRCU / WCUを共有します。
Table
│
└─ LSI
RCU / WCUを共有
また、LSIを持つテーブルでは、同じPartition Keyに属するItemの集合であるItem Collectionに10GBの制限があります。
同じPartition Key
↓
Item Collection
↓
最大10GB
GSI / LSIまとめ
| 項目 | GSI | LSI |
|---|---|---|
| Partition Key | 元テーブルと別でもよい | 元テーブルと同じ |
| Sort Key | 任意 | 必須・元テーブルとは別の属性 |
| 後から追加 | 可能 | 不可 |
| Provisioned Capacity | 独自 | 元テーブルと共有 |
| Eventually Consistent Read | 可能 | 可能 |
| Strongly Consistent Read | 不可 | 選択可能 |
| Item Collection 10GB制限 | なし | あり |
GetItem
Primary Keyが分かっていて、1件のItemを取得したい場合はGetItemを使用します。
Primary Key
↓
GetItem
↓
1 Item
BatchGetItem
複数のItemをまとめて取得したい場合はBatchGetItemを使用できます。
Key A ─┐
Key B ─┼→ BatchGetItem → DynamoDB
Key C ─┘
1件ずつGetItemを繰り返すのではなく、複数のKeyをまとめてリクエストできます。
1回のBatchGetItemでは、最大100 Item、合計16MBまで取得できます。
ただし、すべてのItemが必ず1回で返ってくるとは限りません。
処理できなかったKeyは、
UnprocessedKeys
として返されます。
BatchGetItem
↓
UnprocessedKeys
↓
Exponential Backoff
↓
Retry
UnprocessedKeysを再試行するときは、Exponential Backoffを利用します。
RetryとExponential Backoff
DynamoDBではスロットリングなどによって、一時的にリクエストを処理できない場合があります。
その場合、すぐに大量のRetryを繰り返すのではなく、Exponential Backoffを利用します。
失敗
↓
少し待つ
↓
Retry
↓
さらに長く待つ
↓
Retry
Retry間隔を徐々に長くすることで、サービスへさらに負荷をかけることを防ぎます。
Conditional Write
同じデータを重複して登録したくない場合などは、Conditional Writeを利用できます。
例えば注文IDを初回だけ登録したい場合、
OrderId = order001
に対して、
attribute_not_exists(OrderId)
という条件を設定できます。
OrderIdなし
→ Write成功
OrderIdあり
→ Write失敗
これによって重複登録を防止できます。
Lambdaの冪等性を実装するときにも利用できます。
Lambda
↓
Conditional Write
↓
DynamoDB
Optimistic Locking
同じItemを複数の処理が更新する場合、更新の競合が発生する可能性があります。
例えば、
Version = 1
のItemを取得したあと、
Versionがまだ1なら更新
という条件を付けます。
実装では、Conditional WriteのConditionExpressionを利用できます。
ConditionExpression
Version = :expected
取得時
Version = 1
↓
Versionがまだ1?
├─ YES → Update → Version = 2
└─ NO → Update失敗
他の処理が先に更新してVersionが2になっていれば、条件に一致しないため更新を失敗させることができます。
これを Optimistic Lockingと呼びます。
DynamoDB TTL
一定期間が経過したItemを自動的に削除したい場合は**TTL(Time To Live)**を利用します。
Session
↓
ExpirationTime
↓
期限切れ
↓
TTLによる削除
ただし、TTLは期限になった瞬間に即座にItemを削除する仕組みではありません。
期限切れのItemはバックグラウンドで削除されるため、期限切れ後もしばらく読み取れる可能性があります。
厳密に有効期限を判定したい
→ Application側でもExpirationTimeを確認
TTLによる削除では、元のリージョンでWrite Capacityを消費しません。
また、TTLによって削除されたItemの削除イベントはDynamoDB Streamsにも記録されます。
TTLによる削除
↓
DynamoDB Streams
↓
Lambda
↓
後続処理
DynamoDB Streams
DynamoDBのデータ変更をイベントとして取得したい場合は、DynamoDB Streamsを利用します。
例えば、
DynamoDB
↓
INSERT / MODIFY / REMOVE
↓
DynamoDB Streams
↓
Event Source Mapping
↓
Lambda
という構成にできます。
例えば注文が登録されたら、
注文登録
↓
DynamoDB
↓
Streams
↓
Lambda
↓
メール送信
のようなイベント駆動処理を実装できます。
Streamsを有効にしただけではLambdaと自動的に接続されないため、Lambdaとの連携にはEvent Source Mappingを設定します。
DynamoDB StreamsのStream View Type
Streamsへどの情報を記録するか設定できます。
主な種類は以下です。
| Stream View Type | 内容 |
|---|---|
| KEYS_ONLY | Keyのみ |
| NEW_IMAGE | 更新後のItem |
| OLD_IMAGE | 更新前のItem |
| NEW_AND_OLD_IMAGES | 更新前と更新後 |
DAX
DynamoDBへの読み取りアクセスが非常に多い場合、DynamoDB Accelerator(DAX) を利用できます。
DAXはDynamoDB向けのインメモリキャッシュです。
Application
↓
DAX
↓
DynamoDB
頻繁に読み取られるデータをDAXから返すことで、低レイテンシでアクセスできます。
DAXのキャッシュは、Eventually Consistent Readを高速化する用途に向いています。
Strongly Consistent Readを要求した場合は、DAXのキャッシュを利用せずDynamoDBから読み取ります。
Eventually Consistent Read
→ DAX Cache
Strongly Consistent Read
→ DynamoDB
DynamoDBの読み取り整合性
Eventually Consistent Read
最新の書き込みがすぐに読み取りへ反映されない可能性があります。
Write
↓
少し時間差
↓
Read
Strongly Consistent Read
最新の書き込み結果を反映した読み取りを行います。
Write
↓
Read
↓
最新データ
常にStrongly Consistent Readが必要とは限らないため、アプリケーションの要件に応じて選択します。
GSIではStrongly Consistent Readは利用できず、Eventually Consistent Readのみです。
LSIではEventually Consistent Readに加えて、Strongly Consistent Readも選択できます。
なお、Global Tablesのリージョン間レプリケーションにStrong Consistencyが保証されるという意味ではありません。
RCU / WCU
Provisioned Capacityでは、読み取りと書き込みに使用するキャパシティを設定します。
重要なのは、Itemサイズをそのまま計算するのではなく、
Read
→ 4KB単位で切り上げ
Write
→ 1KB単位で切り上げ
てから計算することです。
RCU
RCU(Read Capacity Unit)は読み取り性能を表します。
4KBまでのItemを1秒間に1回読み取る場合、
Strongly Consistent Read
→ 1 RCU
Eventually Consistent Read
→ 0.5 RCU
Transactional Read
→ 2 RCU
となります。
Strongly Consistent Read
必要RCU
=
4KB単位に切り上げたサイズ
×
1秒あたりの読み取り回数
例えば8KBのItemを毎秒10回読み取る場合、
8KB ÷ 4KB
= 2
2 × 10
= 20 RCU
となります。
Eventually Consistent Read
Eventually Consistent Readでは、Strongly Consistent Readの半分のRCUで読み取れます。
例えば6KBのItemを毎秒10回読み取る場合、
6KB
↓
4KB単位で切り上げ
↓
8KB相当
8KB ÷ 4KB
= 2
2 × 10 ÷ 2
= 10 RCU
となります。
RCUの切り上げは、1回の読み取りごとに行う点に注意します。
複数のItemサイズを先に合計してから、一度だけ4KB単位へ切り上げるわけではありません。
Transactional Read
Transactional Readでは、通常のStrongly Consistent Readの2倍のRCUを消費します。
4KBまで
Strong Read
→ 1 RCU
Transactional Read
→ 2 RCU
WCU
WCU(Write Capacity Unit)は書き込み性能を表します。
1KBまでのItemを1秒間に1回書き込む場合、
Standard Write
→ 1 WCU
Transactional Write
→ 2 WCU
となります。
Standard Write
必要WCU
=
1KB単位に切り上げたサイズ
×
1秒あたりの書き込み回数
例えば2.5KBのItemを毎秒5回書き込む場合、
2.5KB
↓
1KB単位で切り上げ
↓
3KB
3 × 5
= 15 WCU
となります。
Transactional Write
Transactional Writeでは、Standard Writeの2倍のWCUを消費します。
1KBまで
Standard Write
→ 1 WCU
Transactional Write
→ 2 WCU
RCU / WCUの覚え方
Read
→ 4KB単位
Write
→ 1KB単位
Strong Read
→ ×1
Eventually Read
→ ×0.5
Transactional Read
→ ×2
Standard Write
→ ×1
Transactional Write
→ ×2
DynamoDBの400KB制限
DynamoDBでは、1つのItemの最大サイズは400KBです。
この400KBにはデータの値だけではなく、属性名などもItemサイズとして含まれます。
そのため、
画像
PDF
動画
巨大なJSON
大容量テキスト
などをDynamoDBへ直接保存する用途には向いていません。
このような場合は、大容量データ本体をAmazon S3へ保存し、DynamoDBにはS3オブジェクトを参照する情報やメタデータを保存します。
Application
│ │
│ │
▼ ▼
S3 DynamoDB
│ │
大容量データ S3 Object Key
メタデータ
つまり、
大容量データ本体
→ S3
検索・管理に必要な情報
→ DynamoDB
という役割分担にします。