1. はじめに
本記事では、 RDB(リレーショナルデータベース)を理解している方向けに、DynamoDBの基本概念、設計のポイント、ベストプラクティスについて解説します。
「RDBとどう違うの?」「テーブル設計の考え方は?」といった疑問を解消し、 RDBとは異なる設計アプローチを学べる 記事を目指します。
具体的な課金モデルなどの商業的な内容については本記事の範囲外とします。
注意
本記事はChatGPTとの対話学習の記録を自分用にまとめたものです。
もし誤りや改善点があれば、ご指摘いただけると幸いです。
2. DynamoDBとは?
2.1 概要
Amazon DynamoDBは、AWSが提供する フルマネージド型のNoSQLデータベース です。高速な読み書きが要求される大規模アプリケーション向けに設計されており、スキーマレスで自動スケーリング可能 な特性を持ちます。
RDBとDynamoDBの主な違い
項目 | RDB | DynamoDB |
---|---|---|
データ構造 | 行と列のテーブル | キーバリュー / JSONドキュメント |
スキーマ | 事前定義が必要(固定カラム) | スキーマレス(柔軟なデータ構造) |
スケール方法 | 垂直スケール(スケールアップ) | 水平スケール(スケールアウト) |
クエリ | SQL(JOINや柔軟な検索が可能) | 主キー or インデックス検索のみ |
トランザクション | 完全なACIDトランザクション | 制限付きでサポート |
ユースケース | 一般的な業務システム | 高トラフィックなWeb/モバイルアプリ |
2.2 DynamoDBの基本概念
DynamoDBのデータ構造は、 RDBの「行と列」ではなく、「キーとアイテムの集合」 です。
ここで「キーとアイテムの集合」とは、各アイテムが一意に識別されるキーを持ち、それに対応するデータの属性が格納されるという構造を指します。
- テーブル :データの格納単位
- アイテム :1つのデータ(RDBの「レコード」に相当)
- 属性 :アイテムの持つ情報(RDBの「カラム」に相当)
RDBでは、各データは行(レコード)として格納され、各列(カラム)に特定の属性を持ちますが、DynamoDBでは、アイテム(レコード)が必ずしも同じ属性を持つ必要はなく、可変的なデータ構造を採用できます。
3. NoSQLとは?
DynamoDBはNoSQLデータベースの一種ですが、そもそも NoSQLとは何か? を理解することが重要です。
NoSQLとは、"Not Only SQL" (SQLだけではない) の略で、従来のRDBとは異なるデータ管理の仕組みを指します。
3.1 NoSQLの基本概念
NoSQLとは、"Not Only SQL" (SQLだけではない) の略で、従来の RDBとは異なるデータ管理の仕組み を指します。以下のような特徴を持ちます。
- スキーマレス(固定のテーブル構造が不要)
- 水平スケールが容易(サーバーを増やしてスケールアウトしやすい)
- 高速なデータアクセス(用途に特化した設計)
- 柔軟なデータ構造(JSONやキー・バリュー形式など)
3.2 NoSQLの種類とデータモデル
NoSQLの種類 | データの持ち方 | 代表的なDB | 用途の例 |
---|---|---|---|
キーバリュー型 | キーと値のシンプルな組み合わせ | Redis, DynamoDB | キャッシュ、セッション管理 |
ドキュメント型 | JSON/BSONのような階層構造 | MongoDB, CouchDB | Webアプリ、ログ管理 |
カラム指向型 | RDBに近いがカラム単位で最適化 | Cassandra, HBase | 大規模分析、ログ管理 |
グラフ型 | ノード(点)とエッジ(線)で関係を管理 | Neo4j, ArangoDB | SNS、推薦システム |
3.3 NoSQLのデータ取得方法
RDBと異なり、NoSQLでは以下のような方法でデータを取得します。
1 . キーによる直接アクセス(Key-Value検索)
SQLでは、次のように WHERE
句で検索します。
SELECT * FROM users WHERE id = 1;
しかし、キーバリュー型のNoSQLではキー(ID)を直接指定してデータを取得します。
例:DynamoDB, Redis
{
"TableName": "users",
"Key": { "user_id": "123" }
}
✅ user_id = 123
のデータをO(1)の速度で取得!
2 . ドキュメント指向のデータ取得(JSONベース)
RDBではテーブルを分割し、JOINで結合しますが、ドキュメントDB(MongoDBなど)はJSONのようなネスト構造をそのまま保存可能 です。
例:MongoDB
{
"name": "Alice",
"age": 25,
"address": {
"city": "Tokyo",
"zip": "100-0001"
}
}
✅ JOINなしでネストデータをそのまま検索可能!
3 . カラム指向のストレージ構造(HBase, Cassandra)
通常のRDBは「行単位」でデータを保存しますが、カラム指向DBは「カラム単位」で保存するため特定カラムの検索・集計が高速になります。
例:Cassandraのデータ構造
user_id | name | age | country
--------+--------+-----+---------
123 | Alice | 25 | Japan
456 | Bob | 30 | USA
✅ カラム単位で圧縮&保存 → 大規模データ分析向き!
4 . グラフデータベースの関係検索(Neo4j)
SNSの「友達関係」など、RDBのJOINでは処理が複雑になりがちなデータも、グラフDBならシンプルに検索できます。
例:Neo4j
MATCH (a:User {name: "Alice"})-[:FRIEND]->(b)
RETURN b.name;
✅ Aliceの友達を一発検索!(SNSや推薦システム向き)
まとめ
- NoSQLは「SQLに縛られない」データ管理手法
- キー検索・JSON・カラム指向・グラフDBなど、多様なデータモデルがある
- DynamoDBはNoSQLの「キーバリュー型」に分類される
- 用途に応じて適切なNoSQLを選択することが重要!
4. DynamoDBの最適な設計
DynamoDBはスケーラブルで高速なデータベースですが、RDBと同じ設計アプローチを適用すると、コストが増大したりパフォーマンスが低下するリスクがあります。そのため、適切な設計アプローチを理解し、DynamoDBの特性を最大限に活かすことが重要です。
本記事では、まずRDBと同じ設計をした場合の問題点を説明し、その後DynamoDBに適した設計方法を解説します。
4.1 RDBとDynamoDBの設計の違いと問題点
4.1.1 RDBの設計とその特徴
RDBでは、正規化によってデータの冗長性を排除し、データの整合性を確保します。例えば、ユーザー情報と注文情報を別々のテーブルに分け、JOIN
を使って関連データを取得します。
RDBの設計例(正規化)
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id),
total_price DECIMAL
);
このように、RDBは関連するデータを別々のテーブルに格納し、データ取得時には JOIN
を使って、ユーザーと注文情報を結合します。
SELECT users.name, orders.order_id, orders.total_price
FROM users
JOIN orders ON users.user_id = orders.user_id
WHERE users.user_id = 123;
✅ RDBで正規化を行うメリット
- データの一貫性を確保できる(更新が一括で反映される)
- ストレージコストが削減できる(データの重複を防ぐ)
-
WHERE
を活用した柔軟な検索が可能
4.1.2 DynamoDBでRDBと同じ設計をした場合の問題点
RDBの設計をDynamoDBに適用すると、以下のような問題が発生します。
1. JOINが使えず、複数のクエリが必要になる
DynamoDBはRDBのような JOIN
をサポートしていないため、関連するデータを取得するのに複数回のクエリが必要になります。
- ユーザー情報の取得
- 注文情報の取得(
user_id
で検索)
この結果、リクエスト数が増えてコストが高騰し、レスポンス速度も低下します。
2. WHERE検索がSCANになり、コスト増大と低速化
DynamoDBでは、WHERE
句を使った自由な検索ができません。パーティションキーを指定しない検索はSCANになり、全データを走査するためコストが高く、速度も遅いです。
{
"TableName": "orders",
"FilterExpression": "total_price > :price",
"ExpressionAttributeValues": { ":price": 100 }
}
SCANは全パーティションを対象にするため、リードキャパシティユニット(RCU)の消費が激しく、コストが増加します。
4.2 DynamoDBの適切な設計方法
DynamoDBを適切に設計するためには、以下の2つのアプローチが必要です。
- データを非正規化し、アクセスを最適化する
- GSIを活用し、SCANを回避した検索を行う
4.2.1 データを非正規化し、アクセスを最適化する
DynamoDBでは、データを非正規化して保存することが一般的なアプローチです。アクセスパターンに合わせて必要なデータを事前にまとめて保存することで、1回のクエリで必要な情報を取得できます。
DynamoDBに適した設計例(非正規化)
{
"user_id": "123",
"name": "Alice",
"email": "alice@example.com",
"orders": [
{"order_id": "A001", "total_price": 100},
{"order_id": "A002", "total_price": 200}
]
}
✅ この設計のメリット
- 1回のクエリでユーザー情報と注文データを取得できる(JOIN不要)
- データ取得のためのリクエスト数が減り、コストを削減できる
- スループットの最適化により、パフォーマンス向上
4.1.2 GSIを活用し、SCANを回避した検索を行う
DynamoDBでは、WHERE検索の代わりにGSI(グローバルセカンダリインデックス)を活用し、特定の属性で効率的にデータを検索します。
🔹 GSI(グローバルセカンダリインデックス)とは?
- 目的:DynamoDBテーブルの主キー以外の属性を基に、効率的にデータを検索するためのインデックス。
-
主キーとの違い:
- 主キーは1つのテーブルにつき1つしか定義できないが、GSIは1つのテーブルに複数作成可能。
- 主キーはハッシュキーとレンジキーから構成されるが、GSIは任意の属性をハッシュキーとして使用可能。
GSIを設定することで、主キー以外の属性で高速に検索できるようになります。
GSIの作成例(注文データをtotal_price
で検索)
{
"TableName": "orders",
"AttributeDefinitions": [
{"AttributeName": "user_id", "AttributeType": "S"},
{"AttributeName": "total_price", "AttributeType": "N"}
],
"GlobalSecondaryIndexes": [
{
"IndexName": "total_price_index",
"KeySchema": [
{"AttributeName": "total_price", "KeyType": "HASH"}
],
"Projection": {"ProjectionType": "ALL"}
}
]
}
上記のようにGSIを設定することで、total_price
で効率的に検索を行えるようになります。
例えば、total_price
が100以上の注文を取得するクエリは以下のように記述できます。
{
"TableName": "orders",
"IndexName": "total_price_index",
"KeyConditionExpression": "total_price > :price",
"ExpressionAttributeValues": { ":price": 100 }
}
✅ この設計のメリット
- SCANを回避し、高速な検索を実現
- 必要なデータに直接アクセスできるため、レスポンスが速い
- コストの削減(RCUの消費を抑える)
4.3 設計のポイントとまとめ
DynamoDBでは、「どのようにデータを保存するか?」 ではなく 「どのようにデータを取得するか?」 を最優先に考える必要があります。アクセスパターンに基づいて、非正規化やGSIを活用することで、パフォーマンスを最大化し、コストを抑えることができます。
✍️ DynamoDB設計のポイント
- 非正規化により、1回のクエリで必要なデータを取得する
- GSIを活用し、SCANを回避する設計を行う
適切な設計を行うことで、DynamoDBの高速な読み書きを活かしつつ、コストを最適化することができます。
5. その他の注意点
その他に利用に際して注意すべき点をいくつか挙げておきます。これらのポイントを理解することで、より効率的かつトラブルの少ない運用が可能になります。
5.1 NULLの扱いとデータ欠落に関する注意
DynamoDBにはNULLという概念がない
RDB(リレーショナルデータベース)では、NULL
はデータが未設定であることを意味します。しかし、DynamoDBではNULL
という概念が存在しません。代わりに、属性が未設定であれば、その属性自体が「存在しない」ものとして扱われます。つまり、NULL
ではなく、「値が設定されていない」という状態でデータが管理されます。
❌ 問題点
- RDBでは
NULL
を使ってデータが未設定であることを簡単に識別できますが、DynamoDBでは「属性がない」という状態だけで、値が本当に未設定なのか、それとも何かの理由で削除されたのかを判断しにくいという問題があります。 - そのため、
NULL
チェックを行うロジックをそのままDynamoDBに適用することはできません。
✅ 対策
-
デフォルト値を使う
DynamoDBでは、NULL
の代わりに空文字列(""
)やゼロ(0
)、あるいはフラグ値(例えばfalse
)などをデフォルト値として設定することで、意図的に「未設定」の状態を表現できます。この方法を使うことで、後からデータが欠落しているかどうかを判断しやすくなります。
5.2 データ型の柔軟性とデータ不整合のリスク
DynamoDBではデータ型に柔軟性がある
DynamoDBでは、同じ属性に異なるデータ型を格納することができます。例えば、age
という属性に、あるアイテムでは数値(25
)が、別のアイテムでは文字列("twenty-five"
)が格納されていることも可能です。これは非常に柔軟で強力な機能ですが、一方で問題を引き起こす可能性もあります。
❌ 問題点
-
データ不整合の発生
例えば、age
という属性に数値(25
)を格納していたのに、誤って文字列("twenty-five"
)を格納してしまうと、後でそのデータを使って計算や検索を行う際にエラーが発生することになります。こうした不整合が発生すると、アプリケーションが正常に動作しなくなる可能性があります。 -
アプリケーション側での型管理が必須
DynamoDBはデータ型を強制しないため、データの整合性を保つためには、アプリケーション側で型を厳密に管理する必要があります。 -
インデックスの問題
グローバルセカンダリインデックス(GSI)を使う場合、属性のデータ型が異なると、インデックスが正しく動作しないことがあります。
✅ 対策
-
データ型のバリデーションを行う
データをDynamoDBに格納する前に、is_number()
やis_string()
を使ってデータ型をチェックすることが推奨されます。これにより、意図しないデータ型が格納されることを防げます。 -
外部データソースからのインポート時に型変換を実施する
例えば、CSVファイルやJSONファイルなど外部のデータソースからDynamoDBにデータをインポートする際、型の不一致が発生しないように事前に型変換を行うことが重要です。 -
DynamoDB Streamsを活用する
DynamoDB Streamsを活用して、誤ったデータが書き込まれた場合にリアルタイムでアラートを発したり、データの修正を行うことができます。これにより、データ不整合を早期に検出して対応できます。
5.3 複雑なクエリや集計処理の制限
WHERE
やIN
演算子が使用できない
RDB(リレーショナルデータベース)では、WHERE
句やIN
演算子を使って、簡単に複数の条件に基づいた検索ができますが、DynamoDBではこれが制限されています。
❌ 問題点
-
IN
が使えない
DynamoDBでは、IN
演算子を使って一度に複数のアイテムを取得することができません。そのため、個別に複数回query
やscan
を実行する必要があります。 -
パーティションキーがない検索は
SCAN
になる
パーティションキーを指定せずに検索を行うと、SCAN
が実行され、これが非常にコストが高くなります。SCAN
は全データを走査するため、検索時間と費用が大きくなります。
✅ 対策
-
GSI(グローバルセカンダリインデックス)を使って効率的な検索を実現する
特定の属性で高速検索を行いたい場合は、GSIを利用してインデックスを作成し、効率的にデータを検索できるようにします。 -
フィルタリングはアプリケーション側で行う
複雑な検索やフィルタリングをアプリケーション側で行うことで、無駄なSCAN
を避け、コストを削減することができます。 -
BatchGetItem
を使う
複数のアイテムを一度に取得する場合は、BatchGetItem
を使って複数のアイテムを一括で取得することが可能です。
5.4 データ順序と並び替えの制約
ORDER BYが使えない
DynamoDBでは、ORDER BY
をサポートしていません。そのため、データを特定の順序で取得するためには、ソートキー(Range Key) を適切に設計する必要があります。
✅ 対策
-
ソートキーを適切に設計し、データ順序を管理する
データを特定の順序で取得したい場合、ソートキーを使って並び替えができるように設計します。 -
ScanIndexForward
オプションで並び替え
ソート順序を変更したい場合、ScanIndexForward
オプションを使って昇順または降順でデータを取得できます。
5.5 データの削除と更新に関する注意
データの削除=物理削除ではない
DynamoDBでは、データを削除しても即座には物理的に解放されません。このため、削除したデータがストレージに残り続ける可能性があります。
✅ 対策
-
論理削除を利用する
is_deleted
というフラグを立てて論理削除を管理することで、削除されたデータを後から追跡できます。 -
TTL(Time to Live)を設定する
TTL(Time to Live)を設定することで、一定期間後に不要なデータを自動で削除することができます。これにより、ストレージの無駄な増加を防げます。
5.6 データ整合性の問題
最終的な一貫性 vs. 強い一貫性
DynamoDBでは、最終的な一貫性(Eventually Consistent Reads) がデフォルトで使用されます。これにより、書き込み後すぐに読み取ったデータが最新であるとは限りません。
❌ 問題点
- 直後にデータを取得すると、書き込みが反映されていない場合があり、その結果古いデータが返されることがあります。
✅ 対策
-
ConsistentRead: true
を指定する強い一貫性が必要な場合は、
ConsistentRead: true
を設定して、最新のデータを確実に取得することができます。ただし、これにはコストがかかります。
5.7 注意点のまとめ
課題 | RDB(リレーショナルDB) | DynamoDB |
---|---|---|
NULLの扱い |
NULL あり |
属性の欠落 |
データ型の管理 | 厳格な型制約 | 混在可能(要バリデーション) |
複雑な検索 |
WHERE ・IN 使用可能 |
SCAN回避の設計が必要 |
並び替え |
ORDER BY 可能 |
ソートキーが必須 |
DynamoDBを効果的に活用するためには、その設計思想を理解し、制約を避けるための工夫を行うことが重要です。
6. DynamoDBのメリットと選定基準
ここまでの内容では、リレーショナルデータベース(RDB)が優れているように見えるかもしれませんが、DynamoDBを適切に活用することで、特に高スケーラビリティや運用の効率化を実現できます。DynamoDBがどのようなシーンで優れた選択肢となり得るのか、さらに詳しく説明します。
6.1 DynamoDBのメリット
-
運用が簡単
DynamoDBはフルマネージドサービスで、インフラの管理が不要です。AWSがバックアップやスケーリングを自動で行ってくれるため、開発者はアプリケーション開発に集中できます。 -
スケーラブルで高可用性
トラフィックやデータ量に応じて自動でスケールし、サービスがダウンするリスクを最小限に抑えます。複数の場所にデータが保存されるため、高可用性が保証されます。 -
高速なデータアクセス
適切な設計を行うことで、シンプルなデータモデルにより迅速なデータの読み書きが可能です。大量のリクエストにも対応でき、パフォーマンスを維持します。
6.2 DynamoDBを選ぶべきケース
-
アクセス方法が決まっている場合
アクセスパターン(データの読み書き方法)が最初に決まっている場合、DynamoDBはそのパフォーマンスを最大限に活かせます。アクセス方法が頻繁に変わる場合は、リレーショナルデータベースの方が適していることもあります。 -
大量のトラフィックが予想される場合
トラフィックの増加に対応するために、自動でスケールアウトするDynamoDBは、大量のアクセスを効率的に処理します。予測しきれないトラフィックの増加にも対応できる「オンデマンドモード」も便利です。 -
サーバーレスアーキテクチャとの相性が良い
DynamoDBはAWS Lambdaと組み合わせて、サーバーレスアーキテクチャを簡単に構築できます。インフラ管理から解放され、リソースを効率的に活用できます
7. まとめ
AWSのフルマネージドNoSQLデータベースであるDynamoDBについて個人的に学んだことを整理しました。
DynamoDBはそのスケーラビリティやパフォーマンスの面で非常に優れた特性を持っていますが、最適なパフォーマンスとコスト効率を引き出すためには、アクセスパターンを正確に理解し、適切なデータモデル設計を行うことが不可欠です。
しかし、使いこなせば強力なツールとなるので、今後もさらに実践的な活用方法を探求していきたいと思います!