CHAPTER 03 / D1 / D4
DynamoDB とデータストア
キー、インデックス、整合性、S3の使い方を学ぶ。
01 / 保存先はアクセスパターンで選ぶ
「データを保存できる」だけでは選べません。何をキーに検索するか、結合やトランザクションが必要か、オブジェクト全体を取得するかを考えます。
| サービス | 向いている例 | 判断の軸 |
|---|---|---|
| DynamoDB | 注文IDで取得、顧客ごとの注文一覧 | キーによるアクセス、低レイテンシ、スケール |
| S3 | 写真、CSV、ビルド成果物 | オブジェクトをキーで保存・取得 |
| RDS / Aurora | 複数テーブルのSQL・JOIN | 関係性とSQL、接続数、トランザクション |
| ElastiCache | セッション、汎用の高速キャッシュ | TTL、失効、キャッシュミス、メモリ容量 |
| DAX | DynamoDBの読み取りキャッシュ | DynamoDB互換のアクセスを高速化。強い整合性を要求する読み取りはキャッシュを使わない |
02 / DynamoDBのキーとインデックス
主キーはパーティションキーだけ、またはパーティションキー+ソートキーで構成します。複合キーの場合、組み合わせが項目を一意にします。同じパーティションキーの項目を、ソートキーの範囲で取得できます。
たとえば、主キーをPK=CUSTOMER#123、SK=ORDER#2026-11-08#001とすれば、顧客の注文を時系列に探す形を作れます。全注文を一つの固定値のパーティションキーに集中させると、ホットパーティションの原因になります。キーの種類の多さだけでなく、アクセス量の偏りも見ます。
| 比較 | GSI | LSI |
|---|---|---|
| パーティションキー | ベーステーブルと異なるキーを選べる | ベーステーブルと同じ |
| ソートキー | ベーステーブルと異なる構成にできる | 別のソートキー |
| 作成のタイミング | 作成時・作成後に追加可能 | テーブル作成時に定義 |
| 読み取り整合性 | 結果整合性のみ | 結果整合性 / 強い整合性 |
| 容量 | ベーステーブルと別に管理する | ベーステーブルの容量を共有 |
公式資料:セカンダリインデックス
03 / Query・Scan・整合性
Queryはパーティションキーの等価条件が必須です。ソートキーには範囲・前方一致などの条件を追加できます。任意の属性を自由にSQL検索する仕組みではないため、検索入口がなければGSIなどを検討します。
Scanはテーブルやインデックスを読み進めます。FilterExpressionは読み取り後に結果を絞るので、返却件数を減らしても読み取り容量の節約には直結しません。Queryのフィルターにも同じ注意が当てはまります。
from boto3.dynamodb.conditions import Key
params = {"KeyConditionExpression": Key("customerId").eq("C123")}
while True:
page = table.query(**params)
for item in page.get("Items", []):
print(item["orderId"])
last_key = page.get("LastEvaluatedKey")
if not last_key:
break
params["ExclusiveStartKey"] = last_keyQuery/Scanは一回の呼び出しで全件返るとは限りません。LastEvaluatedKeyがあれば続きを取得します。フィルターで返却件数が0でも続きがある場合があります。
公式資料:読み取り整合性
04 / 容量の計算を一度やってみる
オンデマンドはリクエストに応じた課金、プロビジョンドは設定した容量を軸とする課金です。予測しにくい負荷と、安定して予測できる負荷を区別します。どちらもキーへの偏りやサービスクォータを無視できるわけではありません。
| 通常の単一項目操作 | 1単位で扱う量 | 計算例 |
|---|---|---|
| 強い整合性の読み取り | 1 RCU = 4 KB以下を毎秒1回 | 6 KBを毎秒10回 → ceil(6/4) × 10 = 20 RCU |
| 結果整合性の読み取り | 強い整合性の半分の読み取り単位 | 6 KBを毎秒10回 → 2 × 10 ÷ 2 = 10 RCU |
| 通常の書き込み | 1 WCU = 1 KB以下を毎秒1回 | 1.5 KBを毎秒20回 → ceil(1.5/1) × 20 = 40 WCU |
サイズは境界ごとに切り上げます。トランザクション操作は通常の操作より容量を消費します。GSIへの書き込みや、読み取り対象と実際のサイズにも注意してください。
公式資料:RCU・WCU
05 / 条件付き更新・トランザクション・TTL
| 要件 | 機能 | 注意点 |
|---|---|---|
| 同じIDを二度作りたくない | ConditionExpression / attribute_not_exists | 存在確認と書き込みを別々にすると競合する |
| 同時更新で上書きを防ぐ | バージョン番号+条件式による楽観的ロック | 読み取ったversionと一致するときだけ更新 |
| 在庫更新と注文作成を両方成功させたい | TransactWriteItems | 同一アカウント・同一リージョン内。BatchWriteItemは全件原子的ではない |
| 期限切れデータを自動で掃除したい | TTL | 期限は秒単位のUnix時刻のNumber。削除は即時ではない |
| データ変更に応じて後続処理をしたい | DynamoDB Streams | 変更記録を処理。Lambda側は重複を考えて冪等にする |
table.put_item(
Item={"orderId": "O123", "status": "PENDING"},
ConditionExpression="attribute_not_exists(orderId)"
)
# 既に存在すれば ConditionalCheckFailedException。
# アプリ側で「既存結果を返す」などの業務上の扱いを決める。公式資料:TTL の削除タイミング
06 / S3・キャッシュで押さえること
S3はオブジェクトストレージです。一般的なPUT、DELETE後のGET・LISTには強い整合性があります。ただし、CloudFrontなどのキャッシュが古い内容を返す問題は別です。バージョニングは誤更新からの復旧、Lifecycleは保存クラスの移行や期限切れ削除に使います。
大きなファイルはマルチパートアップロードで分割して送れます。失敗した部分だけの再送が可能ですが、未完了パートの管理も必要です。S3のCORSはブラウザのアクセス可否を制御する設定で、IAMやバケットポリシーの代わりにはなりません。
| キャッシュ | よくある使いどころ | 気をつけること |
|---|---|---|
| CloudFront | 画像や静的コンテンツをエッジで配信 | キャッシュキー、TTL、無効化、ファイル名のバージョン化 |
| API Gateway REST APIキャッシュ | APIの応答を再利用 | 利用者で内容が違うならキーの分離。認可との組み合わせ |
| ElastiCache | 汎用データ、セッション | キャッシュミス時の正本、TTL、更新時の失効 |
| DAX | DynamoDBへの読み取り負荷を軽減 | 強い整合性の読み取りはDynamoDBへ。書き込み経路とキャッシュの鮮度 |
設計練習:一覧が遅い
全注文に対してScanし、status=PENDINGだけ返すAPIが遅い。キャッシュ以外で改善するなら?
考え方・解答例
statusを検索入口に持つGSIを検討し、Queryできる形にします。ただし、状態の種類が少なくアクセスが一つに集中するなら、キーの分散やアクセスパターンも見直します。フィルター追加だけでは読み取り自体は減りません。
07 / 確認問題
08 / この章の到達点
上のチェックは、このページでの振り返り用です。章単位の進捗は下のボタンで記録できます。
公式資料
内容確認日:2026年11月8日。仕様の詳細や更新情報は、各公式資料で確認できます。
理解を確認できたら、章の進捗を記録できます。