対象読者: AWSをこれから触る人。 AWS勉強シリーズの9本目で、今回はDynamoDBです。全体の地図は索引記事にあります。
前回のRDSに続いてデータベースの2つ目です。DynamoDBを調べると必ず出てくる、こういう疑問に引っかかった人向けに書きます。
- 「NoSQLって、SQLが無いなら何で探すの?」
- 「RDSがあるのに、なんでもう1個データベースが要るの?」
どちらも「鍵で一発で取るロッカーか、何でも聞ける図書館か」の違いで答えが出ます。

DynamoDBをコインロッカーに例えた図(筆者作成)。左が正しい使い方(鍵で一発)、右がやってはいけない使い方(全部開けて探す)です。
DynamoDBとは何か
Amazon DynamoDBは、鍵(キー)を指定して出し入れする方式のデータベースです。NoSQLと呼ばれる型のひとつで、サーバーの用意もOSの管理もありません(Lambdaと同じ「管理レス」型)。テーブルを作るときに決めるのは実質鍵の形だけで、中に入れるデータの列はバラバラでも構いません。
上の図の通り、コインロッカーだと思うのが一番近い。番号札(キー)を持っていれば、何億件入っていても一発で取れる。逆に「番号札を持っていない探し物」は全部の扉を開けて回る(scan)ことになり、遅くて高い。ここがRDS(何でも聞ける図書館)との根本の違いです。
今AWSの地図のどこにいるか
データベース編の2/2です。前回のRDSと今回のDynamoDBで、AWSの「データを置く場所」の二大選択肢が揃います。
何に使うか
- 鍵が決まっている出し入れ: ユーザーIDでプロフィールを取る、注文IDで注文を取る。Webサービスの大半のアクセスはこれ
- 書き込みが大量に来るもの: IoTのセンサーデータ、ゲームのスコア、アクセスログ。テーブルの結合が要らないデータはDynamoDB向き
- Lambdaと組む: どちらも管理レスなので、「イベント→Lambda→DynamoDBに記録」はサーバー0台で組める定番の形
- セッション情報: ログイン状態のような「キーで取れて、期限が来たら消えていい」データ(TTLという自動削除の仕組みがある)
今はこれだけ分かればOK
| 必須の3語 | 意味 |
|---|---|
| テーブル | ロッカーの棚全体。作るときに鍵の形だけ決める |
| パーティションキー | 鍵の前半。「誰の」(user_id など)。必須 |
| ソートキー | 鍵の後半。「その中のどれ」(order_id など)。任意だが、付けると「同じ人の分をまとめて取る」ができる |
インデックス(GSI/LSI)、キャパシティユニット、DynamoDB Streamsは後回しで大丈夫です。この記事にも出てきません。
仕組みの要点
鍵は「パーティションキー+ソートキー」の2段です。コインロッカーで言うと「taroさんの棚の、注文001の箱」。この2段があるおかげで、取り方が2種類になります。
-
get_item: 2段とも指定して1件を一発で取る -
query: 前半だけ指定して同じ人の分をまとめて取る
そして大事な制約が1つ。テーブル同士の結合(JOIN)はできません。 RDSのように「注文テーブルとユーザーテーブルを繋いで一覧を出す」はDynamoDBの仕事ではない。結合が要る設計ならRDSを選びます。
このコードを動かす前提
この記事のコードはPython(boto3)です。動かす方法は2通りあります。
A. AWSアカウント無しで試す(おすすめ): motoというAWSのそっくりさんを使うと、課金もサインアップもなしでPCの中だけで動きます。
python3 -m venv venv
./venv/bin/pip install boto3 moto
from moto import mock_aws
@mock_aws # この中のboto3呼び出しは全部ローカルの偽AWSに行く
def main() -> None:
... # 以下の記事のコードをここに入れる
main()
B. 本物のAWSで動かす: 先にIAMユーザーとアクセスキーの設定が要ります。手順はIAM記事の「最初の1回だけやる手順」にあります。
使い方
やることは4手順です。
上の4コマがそのままコードの順番になります。
import boto3
ddb = boto3.client("dynamodb", region_name="ap-northeast-1")
# ① ロッカーを作る。決めるのは鍵の形だけ
ddb.create_table(
TableName="orders",
KeySchema=[{"AttributeName": "user_id", "KeyType": "HASH"}, # パーティションキー
{"AttributeName": "order_id", "KeyType": "RANGE"}], # ソートキー
AttributeDefinitions=[{"AttributeName": "user_id", "AttributeType": "S"},
{"AttributeName": "order_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST") # 使った分だけ課金するモード
# ② 入れる。中身の列は毎回違っていていい(2件目はprice無しでmemoがある)
ddb.put_item(TableName="orders", Item={"user_id": {"S": "taro"}, "order_id": {"S": "001"},
"item": {"S": "りんご"}, "price": {"N": "150"}})
ddb.put_item(TableName="orders", Item={"user_id": {"S": "taro"}, "order_id": {"S": "002"},
"item": {"S": "みかん"}, "memo": {"S": "急ぎ"}})
ddb.put_item(TableName="orders", Item={"user_id": {"S": "hana"}, "order_id": {"S": "001"},
"item": {"S": "バナナ"}})
# ③ 鍵で一発で取る
got = ddb.get_item(TableName="orders", Key={"user_id": {"S": "taro"}, "order_id": {"S": "001"}})
print(got["Item"]["item"]["S"]) # -> りんご
# ④ 同じ人の分をまとめて取る(前半の鍵だけ指定)
q = ddb.query(TableName="orders",
KeyConditionExpression="user_id = :u",
ExpressionAttributeValues={":u": {"S": "taro"}})
print(q["Count"]) # -> 2
print([i["order_id"]["S"] for i in q["Items"]]) # -> ['001', '002']
値の書き方が {"S": "taro"} と一段深いのは、DynamoDBが型を明示させる仕様だからです(Sが文字列、Nが数値)。毎回書くのが面倒な場合は boto3.resource("dynamodb") を使うと普通の辞書で書けますが、最初はこの生の形で「型を必ず持っている」ことを見ておくのが後で効きます。
今あなたが作ったもの
- 容量の宣言なしで、何件でも入るテーブルが1つ(motoでは即ACTIVE。実AWSでも十数秒で、RDSの数分とは桁が違う)
- 列がバラバラの3件(priceがある注文、memoがある注文、どちらも無い注文)が同居している
- taroの注文2件が
query一発でまとめて返ってくる
RDSでは「テーブル定義に無い列は入れられない」でしたが、DynamoDBは鍵さえ合っていれば中身は自由。ここが一番の性格の違いです。
実際に叩くと止まるところ
間違えたときに何が返るか、実際に叩きました。
| やったこと | 返ってきたエラー | 意味 |
|---|---|---|
鍵の片方(user_idだけ)で get_item
|
ValidationException |
get_itemは2段とも必須。前半だけで取りたいならquery |
鍵以外の列(item)で query
|
ValidationException: Query condition missed key schema element: user_id |
queryは鍵でしか探せない。コインロッカーは「中身がりんごの箱」では開かない |
無いテーブルに put_item
|
ResourceNotFoundException |
テーブル名のタイプミスか、別リージョンを見ている |
| 同名テーブルを再作成 | ResourceInUseException: Table already exists |
作り直しは削除してから |
無いキーで get_item
|
エラーにならず、Item が無い応答が返る |
「無い」は正常応答。if "Item" in got: で確認する癖を付ける |
2つ目が一番大事です。「商品名で検索したい」と思った瞬間にDynamoDBの設計は行き詰まります。先に「どの鍵で取るか」を決めてからテーブルを作るのがDynamoDBの作法で、後から検索条件を足せるRDSとは順番が逆です。
似たサービスとの使い分け
| 迷うところ | 答え |
|---|---|
| RDSかDynamoDBか | 結合や集計や複雑な検索が要るならRDS。鍵で取るだけの大量アクセスならDynamoDB |
| 「両方の性質が要る」 | 実務ではよくある。注文の本体はRDS、セッションやカウンタはDynamoDB、のように併用する |
| scanで全件取りたくなった | 件数が少ない管理画面ならあり。ユーザー向けの機能でscanが要る設計は、鍵の設計かサービス選定を間違えている |
| S3との違い | S3はファイル(写真・動画・大きな塊)、DynamoDBはデータ1件ずつ(400KB上限)。「番号札で取る」発想は同じ |
料金の考え方
BillingMode="PAY_PER_REQUEST"(オンデマンド)なら、読み書きの回数だけの課金です。東京リージョンで書き込み100万回 約0.71 USD、読み取り100万回 約0.14 USD(2024年11月の値下げ後の単価。2026年8月に料金表で確認)、保存は1GBあたり月0.285 USD。無料枠として25GBの保存が常設です(読み書きのオンデマンド課金は無料枠が無いので、大量アクセスの負荷試験だけ注意)。
RDSとの一番の違いは、使っていない時間の料金がゼロなこと。RDSは止め忘れると待機料金がかかり続けますが(前回の記事の「止めたのに請求が来る」)、DynamoDBのオンデマンドは触らなければ保存料だけです。学習用に置きっぱなしにできる数少ないサービスです。
よくある注意点
- scanを日常の道具にしない。 全件を開けて回るので、件数に比例して遅く・高くなる。上の図の右側の人にならない
- 1件400KBまで。 写真や動画そのものは入らない。S3に置いて、DynamoDBにはS3のキーを入れる
- 後から鍵は変えられない。 パーティションキーとソートキーはテーブル作成時に固定。変えたくなったら新テーブルに移す。だから「どの鍵で取るか」を最初に考える
- JOINは無い。 複数テーブルを繋ぐ発想を持ち込まない。繋ぎたくなったらRDSに戻る
まとめ
冒頭の2つの疑問に戻ります。
- 「SQLが無いなら何で探すの?」→ 鍵で探します。get_item(2段の鍵で1件)とquery(前半の鍵でまとめて)の2つが基本で、鍵以外では探さないのが作法です
- 「RDSがあるのになんで要るの?」→ 図書館(RDS)は何でも聞けるが司書の席(サーバー)が要る。ロッカー(DynamoDB)は鍵で取るだけだが、席が無いので待機料金ゼロで無限に並べられる。用途が違います
データベース編はこれで2/2です。次はCloudWatch(監視)を予定しています。
参考
- Amazon DynamoDB 公式ドキュメント
- 前回: 【図解】AWS RDSとは
- シリーズ索引: AWSとは結局何なのか

