【RDBとの違いが分かる！】DynamoDB入門

# **1. はじめに**

本記事では、 **RDB（リレーショナルデータベース）を理解している方向けに**、DynamoDBの基本概念、設計のポイント、ベストプラクティスについて解説します。

「RDBとどう違うの？」「テーブル設計の考え方は？」といった疑問を解消し、 **RDBとは異なる設計アプローチを学べる** 記事を目指します。

具体的な課金モデルなどの商業的な内容については本記事の範囲外とします。

:::note warn
注意
本記事は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` 句で検索します。

```sql
SELECT * FROM users WHERE id = 1;
```

しかし、キーバリュー型のNoSQLでは**キー（ID）を直接指定**してデータを取得します。

例：DynamoDB, Redis

```json
{
  "TableName": "users",
  "Key": { "user_id": "123" }
}
```

✅ **`user_id = 123` のデータをO(1)の速度で取得！**

---

2 . **ドキュメント指向のデータ取得（JSONベース）**

RDBでは**テーブルを分割し、JOINで結合**しますが、ドキュメントDB（MongoDBなど）は**JSONのようなネスト構造をそのまま保存可能** です。

例：MongoDB

```json
{
  "name": "Alice",
  "age": 25,
  "address": {
    "city": "Tokyo",
    "zip": "100-0001"
  }
}
```

✅ **JOINなしでネストデータをそのまま検索可能！**

---

3 . **カラム指向のストレージ構造（HBase, Cassandra）**

通常のRDBは「行単位」でデータを保存しますが、カラム指向DBは「カラム単位」で保存するため**特定カラムの検索・集計が高速**になります。

例：Cassandraのデータ構造

```other
user_id | name   | age | country
--------+--------+-----+---------
123     | Alice  | 25  | Japan
456     | Bob    | 30  | USA
```

✅ **カラム単位で圧縮＆保存 → 大規模データ分析向き！**

---
4 . **グラフデータベースの関係検索（Neo4j）**

SNSの「友達関係」など、RDBのJOINでは処理が複雑になりがちなデータも、グラフDBならシンプルに検索できます。

例：Neo4j

```other
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の設計例（正規化）**

```sql
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` を使って、ユーザーと注文情報を結合します。

```sql
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になり、全データを走査するためコストが高く、速度も遅いです。

```json
{
  "TableName": "orders",
  "FilterExpression": "total_price > :price",
  "ExpressionAttributeValues": { ":price": 100 }
}
```

SCANは全パーティションを対象にするため、リードキャパシティユニット（RCU）の消費が激しく、コストが増加します。

## **4.2 DynamoDBの適切な設計方法**

DynamoDBを適切に設計するためには、以下の2つのアプローチが必要です。

1. **データを非正規化し、アクセスを最適化する**
2. **GSIを活用し、SCANを回避した検索を行う**

### **4.2.1 データを非正規化し、アクセスを最適化する**

DynamoDBでは、**データを非正規化して保存する**ことが一般的なアプローチです。アクセスパターンに合わせて必要なデータを事前にまとめて保存することで、1回のクエリで必要な情報を取得できます。

**DynamoDBに適した設計例（非正規化）**

```other
{
  "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`で検索）**

```other
{
  "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以上の注文を取得するクエリは以下のように記述できます。

```other
{
  "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はそのスケーラビリティやパフォーマンスの面で非常に優れた特性を持っていますが、最適なパフォーマンスとコスト効率を引き出すためには、アクセスパターンを正確に理解し、適切なデータモデル設計を行うことが不可欠です。

しかし、使いこなせば強力なツールとなるので、今後もさらに実践的な活用方法を探求していきたいと思います！