DynamoDBの設計と料金見積もり

業務でDynamoDBを利用したDBを作成することになり、最適な設計と料金試算を行う方法について調べたので、まとめました。

具体的なユースケースとしては社内AIチャット履歴を保存するための設計と見積もりになります。

DynamoDBのデータ構造の設計、コストの試算、トランザクションの要否について解説します。

今回始めてDynamoを設計し、今までRDBしか触ってきておりませんので、ご指摘があればご意見いただけると幸いです🙇‍♀️

参考文献

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/best-practices.html

---

## 1. なぜDynamoDBなのか？

DynamoDBは、データの増減に応じて柔軟に対応できるキーバリュー型のデータベースです。

リレーショナルデータベース（RDB）とは異なり、データの関連性を考慮せずに保存するため、設計の考え方が大きく変わります。

### AWSのRDS（リレーショナルデータベース）との違い

| 比較項目 | DynamoDB | RDS（MySQL, PostgreSQL など） |
|---------|----------|-------------------------------|
| **データ構造** | キーと値のシンプルなデータ構造 | テーブル間の関係を持つ|
| **スケーラビリティ** | 自動で拡張・縮小 | サーバーのスケールアップが必要|
| **データ取得** | 単一テーブルからの取得が基本 | SQLの`JOIN`で複数テーブルを結合可能 |
| **運用管理** | フルマネージド | サーバーの管理・設定・最適化が必要 |
| **コスト** | 従量課金制 | インスタンス単位の固定費がかかる |
| **用途** | 高速なキー検索、スケールが必要なデータ | データの整合性が重要なシステム（金融、ECなど） |

---
### DynamoDBを選んだ理由（RDSではなくDynamoDBを採用した理由）

今回のユースケース（AIチャット履歴の保存）では、以下の点でDynamoDBが適していました。


- **アクセス頻度が比較的低く、コストを最小限に抑えられる**  
- **リレーショナルなデータ結合が不要**（セッション単位でデータを管理するため）  
- **データの自動削除（TTL機能）を活用できる**  


DynamoDBは**管理コストがかからず、料金も使った分だけ**なので、チャット履歴のような大量のデータを効率的に保存・取得するのに適しています。

これがRDSだと月**2000円**はかかってきます。
(ストレージ料金最低20G、インスタンス料金で、最低スペック）

これがDynamoDBだと月**1〜数百円**程度です。

このような理由から、DynamoDBを採用しました。


##  設計と料金見積もりの考え方

### どこにお金がかかりやすいのか？

DynamoDBの課金は以下の3つが主な要因となります。

#### 1. ストレージ料金（保存データ量）
- **1GBあたり 約$0.25/月**（データ量に比例）
- 不要なデータをTTLで削除しないと、どんどんコストが増える

#### 2. 書き込みリクエスト
- **100万回あたり $1.25**
- 頻繁にデータを更新・追加する設計にするとコストが増える

#### 3. 読み取りリクエスト
- **100万回あたり $0.25**
- 不要なクエリを減らすために、取得しやすい形で保存するのが重要

### お金を安くするには...？

基本的にDynamoDBは従量課金制なので、クエリをできるだけ減らして、ストレージを極力減らすという考え方でコストを抑えていきます。

- **アクセスパターンを事前に考える**：データの取得時に余計な読み取りを発生させない設計にする  
- **データを事前にまとめて保存**：RDBのようにテーブルを分割しすぎず、1回の取得で必要な情報をすべて取得できるようにする  
- **TTL（Time-To-Live）を活用**：不要なデータを自動削除し、ストレージコストを抑える   

---

### RDBとの設計の違いを具体例を用いて

DynamoDBでは、RDBのようにデータの正規化（テーブルを分けること）を意識しすぎるとパフォーマンスやコストが悪化します。

なぜなら、DynamoDBでは複数のテーブルを結合するようなクエリができないため、データを取得するたびに複数回のリクエストが必要になるからです。

またRDB（リレーショナルデータベース）では、どの列でもWHERE句を使って検索できますが、DynamoDBではPK（パーティションキー）を指定しないと効率的に検索できないという制約があります。

そのため、アクセス時のデータ取得を考え、あらかじめ必要なカラム一緒に保存しておくことが重要になります。

そのため、DynamoDBでは**アクセスパターンを考え、必要なデータを1つのテーブルにまとめて保存**するのが基本的な設計の考え方です。  

以下、**チャット履歴と「いいね」の管理を例に、RDBとDynamoDBの設計の違い**を説明します。

---

#### RDB（正規化する設計）
RDBでは、**データの一貫性を保つためにテーブルを分け、`JOIN` を使って結合**します。

**テーブル: `chats`（チャット履歴）**
| **PK (チャットID)** | **セッションID** | **ユーザーID** | **メッセージ** |
|----------------|------------|------------|----------|
| chat_001 | session_001 | user_123 | こんにちは |
| chat_002 | session_001 | user_123 | 今日の天気は？ |
| chat_003 | session_001 | AI | 今日の東京の天気は1日曇りです |

**テーブル: `likes`（いいね一覧）**
| **PK (いいねID)** | **チャットID** | **ユーザーID** | **評価** |
|------------|----------|----------|------|
| like_001 | chat_003 | user_123 | 👍 |

**ここで問題なのですが、DynamoDBでは `JOIN` ができません。**

なので同じ設計をすると非効率になってしまいます。

例えば、「チャット履歴」と「いいね一覧」を取得するために、リクエストを2回発行する必要があります。
そうなると、結果として、読み取り回数が増え、コストが高くなってしまいます。


#### DynamoDB（非正規化する設計）
DynamoDBでは、**チャット履歴といいね情報を1つのテーブルにまとめることで、1回のリクエストで取得可能にします**。

**テーブル: `chat_sessions`（チャット履歴 + いいね情報）**
| **PK (セッションID)** | **SK (メッセージindex)** | **ユーザーID** | **メッセージ** | **いいね** | **期限 (TTL)** |
|----------------|----------------|--------------|------------|---------------|--------------|
| session_001 | 1 | user_123 | こんにちは | null | 1715370000 |
| session_001 | 2 | user_123 | 今日の天気は？ | null | 1715370500 |
| session_001 | 3 | AI | 今日の東京の天気は1日曇りです | ✅ | 1715371000 |

**テーブル: `chat_feedback`（セッション全体の評価）**
| **PK (セッションID)** | **SK (データ種別)** | **ユーザーID** | **評価** | **期限 (TTL)** |
|---------------|--------------|--------|------|----------|
| session_001 | feedback | user_123 | 👍 | 1715370000 |

**DynamoDBの設計のポイントで、**「いいね」をチャット履歴に直接持たせています**  。
   → `liked`（いいね）の情報を各メッセージに保存することで、**追加のクエリが不要**


**DynamoDBのメリット**
- **クエリ1回で、チャット履歴と「いいね」の情報をまとめて取得可能**
- **リクエスト回数が減り、コストが最適化される**
- **データの自動削除（TTL）を活用し、不要なデータを削減**

---

### なぜDynamoDBは結合が苦手なのか？

DynamoDBは **キーと値の組み合わせでデータを保存する** シンプルな構造のため、RDBのような `JOIN` 操作ができません。

#### DynamoDBが結合を苦手とする理由

- **リレーショナルデータの結合ができない** → SQLの `JOIN` のような機能がない  
- **複数のテーブルをまたいでデータ取得するとコストが増加**（複数回のクエリが必要）  
- **一貫性のある結合データを取得するにはアプリ側でマージする必要がある**  

そのため、**事前に必要なデータを1つのテーブルにまとめておく** 設計が重要になります。

---

###  トランザクション書き込み・読み込みとは？ なぜ不要なのか？

DynamoDBでは **複数のデータ操作を1つのまとまった処理として実行し、すべて成功するか、すべて失敗するかを保証** する仕組みがあります。
DynamoDBのトランザクション書込読込は、整合性を担保するため、2回分のクエリを実行します。

#### 具体例と不要な理由

##### ❌ 不要なケース：チャット履歴の保存
- 各メッセージは独立しており、順番に書き込めば良い  
- 部分的に失敗しても影響が小さい  


##### ✅ 必要なケース：銀行口座の振込処理
- 送金元の口座の残高を減らし、受取口座の残高を増やす処理  
- どちらかが失敗するとデータの整合性が崩れる  
- **必ず「すべて成功 or すべて失敗」にする必要がある**  

今回はチャット履歴のようなケースで厳格なトランザクションではないため、不要としました！

##  実際のコスト試算
DynamoDBの料金は、以下の要素で決まります。

| コスト項目 | 単位 | 東京リージョンの料金 |
|----------|---------|------------------|
| **ストレージ** | 1GBあたり/月 | **$0.25/GB** |
| **書き込みリクエスト** | 100万リクエストあたり | **$1.25/百万リクエスト** |
| **読み取りリクエスト** | 100万リクエストあたり | **$0.25/百万リクエスト** |

### **1:データサイズの試算**
#### **メッセージ1件あたりのサイズ**
- **ユーザーのメッセージ** = **50文字 × 2B = 100バイト**
- **AIのメッセージ** = **300文字 × 2B = 600バイト**
- **平均サイズ** = **(100 + 600) ÷ 2 = 350バイト**

#### **1ヶ月のメッセージデータ量**
- **ユーザー1人あたり**
  - `1回6メッセージ × 4週間 = 24メッセージ`
  - `24メッセージ × 350バイト = **8.4KB**`
- **200人の場合**
  - `8.4KB × 200人 = **1.68MB**`

#### **セッションの「いいね or 悪い」データ量**
- **1件 100バイト × 800件/月 = **80KB**

#### **いいねしたAIの回答**
- **1件 600バイト × 160件 = **96KB**

### **2:3ヶ月分のストレージコスト**
| 項目 | 1ヶ月のデータ量 | 3ヶ月分 |
|------|-------------|----------|
| **メッセージ履歴** | 1.68MB | **5.04MB** |
| **いいね or 悪い** | 80KB | **240KB** |
| **いいねしたAIの回答** | 96KB | **288KB** |
| **合計** | **1.85MB** | **5.57MB** |

ストレージ料金:
- **5.57MB ≒ 0.006GB**
- **料金 = 0.006GB × $0.25 ≒ $0.0015 (ほぼ無料)**

### **3:書き込み・読み取りコスト**
#### **書き込みリクエスト**
- **書き込み回数**
  - メッセージ履歴: **4,800回**
  - いいね or 悪い: **800回**
  - いいねしたAIの回答: **160回**
  - **合計: 5,760回**

**書き込み料金**
- `5,760 ÷ 1,000,000 × $1.25 = **$0.007**`

#### **読み取りリクエスト**
- **読み取り回数**
  - 履歴取得: **1000回**
  - **合計 = 1000リクエスト**

**読み取り料金**
- `1,000 ÷ 1,000,000 × $0.25 = **$0.00025**`

---

### **総コスト**



| 項目 | 1ヶ月の費用 |
|------|------------|
| **ストレージ** | **$0.0015** |
| **書き込みリクエスト** | **$0.007** |
| **読み取りリクエスト** | **$0.00025** |
| **合計** | **$0.00875 (約1円/月)** |

---

## 感想

RDBと比べて、安すぎてびっくりしました！

はじめは要件をGPTにいれこんで試算しましたが安すぎて焦ったので、公式サイトと非公式サイトで計算し直しましたがほぼ同じくらいでした。

非公式

https://aws-rough.cc/dynamodb/

公式

https://calculator.aws/#/createCalculator/DynamoDB


ただRDBと比べてリレーションが弱いということは拡張性が低いということを感じました。

例えば今回の例で説明したチャットいいね機能は後付しようと思うと、チャットに対するいいねを保存する別テーブルを用意する形になります。
それは、DynamoDBのクエリをできるだけ別テーブルへ発行しない考え方に反することになります。

そして料金が高くなります。

なので、PoCや小規模プロダクト・短期プロダクトなどや、ログやユーザー情報などキーが明確で構造が単純なものにはすごく力を発揮すると思いました。

逆に前職のような、売上などの複数テーブルをジョインして演算する大規模システムや基幹システムの場合は向いてないと思いました。

## まとめ

✅ DynamoDBはスケーラブルかつ低コストで運用できるため、チャット履歴の保存に適している
✅ JOINが苦手なので、アクセスパターンを考慮した設計が重要
✅ トランザクションは不要と判断し、通常の書き込みを採用することでコストを最適化  
✅ 200人規模では、ストレージ・リクエストコストともに約1円/月で運用可能。20,000人にスケールしても月$1未満

参考になれば幸いです🙇‍♀️