発展LESSON 04

この教材で学ぶこと DynamoDB・RedisのモデルとRDBとの使い分けを学びます。

NoSQL — DynamoDB・Redis・使い分け

前提知識: RDBの基本。本教材ではNoSQL固有の考え方と設計パターンを扱います。


1. RDB vs NoSQL の選択基準

RDB(PostgreSQL/Aurora)を選ぶとき:
  ✅ 複雑な JOIN・集計が必要
  ✅ ACID トランザクションが必須(金融・在庫)
  ✅ スキーマが安定している
  ✅ チームに SQL 知識がある

DynamoDB を選ぶとき:
  ✅ アクセスパターンが明確(事前に決まっている)
  ✅ 超高スループット(1秒間に数十万リクエスト)
  ✅ グローバルテーブル(マルチリージョン)が必要
  ✅ サーバーレス構成(Lambda との相性抜群)

Redis を選ぶとき:
  ✅ セッション・キャッシュ(ミリ秒以下のレスポンスが必要)
  ✅ リーダーボード(ZSET)
  ✅ Pub/Sub(チャット・リアルタイム通知)
  ✅ 分散ロック

2. DynamoDB — シングルテーブル設計

RDB の思想: テーブルを正規化して JOIN で結合
DynamoDB の思想: 1テーブルに全エンティティを入れてアクセスパターンに最適化

シングルテーブル設計の利点:
  - 1リクエストで関連データを全部取得(JOIN 不要)
  - RCU/WCU のコントロールが楽
  - Hot Partition を避けやすい

設計手順:
  1. アクセスパターンを全て列挙する
  2. PK/SK の命名規則を設計する
  3. GSI で補助インデックスを追加する

設計例(ECサイト)

エンティティ: User・Order・OrderItem・Product

アクセスパターン:
  1. ユーザー情報を取得 (userId)
  2. ユーザーの注文一覧を取得(新しい順)
  3. 注文の詳細を取得(orderItems含む)
  4. ステータス別の注文一覧

テーブル設計:
  PK                SK                  Type
  USER#user001      #METADATA           User
  USER#user001      ORDER#2024-01-15    Order
  ORDER#order001    ITEM#item001        OrderItem
  PRODUCT#prod001   #METADATA           Product

GSI1 (ステータス別注文):
  GSI1PK: STATUS#shipped
  GSI1SK: 2024-01-15
import boto3
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("ECommerce")

# 1. ユーザー情報取得
user = table.get_item(
    Key={"PK": "USER#user001", "SK": "#METADATA"}
)["Item"]

# 2. ユーザーの注文一覧(新しい順)
orders = table.query(
    KeyConditionExpression=Key("PK").eq("USER#user001") &
                           Key("SK").begins_with("ORDER#"),
    ScanIndexForward=False
)["Items"]

# 3. 注文の詳細とアイテムを一括取得
# PK = ORDER#order001 のものを全て取得
result = table.query(
    KeyConditionExpression=Key("PK").eq("ORDER#order001")
)["Items"]
# → 注文自体 + 全アイテムが1クエリで取れる

# 4. ステータス別の注文(GSI 使用)
shipped = table.query(
    IndexName="GSI1",
    KeyConditionExpression=Key("GSI1PK").eq("STATUS#shipped")
)["Items"]

3. DynamoDB のキャパシティモード

プロビジョニング済みキャパシティ:
  RCU(読み取り)と WCU(書き込み)を事前に設定
  → アクセスパターンが予測可能な場合
  → Reserved Capacity で最大65%削減
  
  1 RCU = 強力な一貫性読み取り 1回(4KB以内)or 結果整合性読み取り 2回
  1 WCU = 書き込み 1回(1KB以内)

オンデマンド:
  自動でスケール、使った分だけ課金
  → 新規サービス・突発的なスパイク
  → プロビジョニング済みより3〜5倍高コスト
  
Auto Scaling(プロビジョニング済みに追加):
  CPU 使用率に応じてRCU/WCUを自動調整
  → プロビジョニング + 柔軟性の両立

4. Redis — データ構造別の活用

import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)

# ===== String =====
# セッション・API レスポンスキャッシュ
r.setex("session:token123", 3600, "user001")  # TTL 1時間
r.get("session:token123")

# カウンター(原子的操作)
r.incr("page_views:home")

# ===== Hash =====
# ユーザーデータのキャッシュ(フィールド別にアクセス可能)
r.hset("user:001", mapping={
    "name": "田中太郎",
    "email": "tanaka@example.com",
    "role": "admin"
})
r.hget("user:001", "name")    # 特定フィールドのみ取得
r.hgetall("user:001")          # 全フィールド取得

# ===== Sorted Set =====
# リーダーボード(スコア付きで自動ソート)
r.zadd("leaderboard:2024-01", {"user001": 1500, "user002": 2300, "user003": 800})
r.zrevrange("leaderboard:2024-01", 0, 9, withscores=True)  # TOP10

# ===== List =====
# タスクキュー(簡易版; 本番は SQS/Celery を推奨)
r.lpush("task_queue", json.dumps({"type": "send_email", "to": "user@example.com"}))
task = r.brpop("task_queue", timeout=30)  # ブロッキング取得

# ===== Pub/Sub =====
# リアルタイム通知(チャット・WebSocket 連携)
pubsub = r.pubsub()
pubsub.subscribe("notifications:user001")
for message in pubsub.listen():
    if message["type"] == "message":
        print(message["data"])

5. Redis の永続化とレプリケーション

永続化の選択:
  RDB(スナップショット):
    定期的にダンプファイルを作成
    リカバリが遅い・データ損失の可能性あり
    
  AOF(Append Only File):
    全書き込みをログに記録
    リカバリが確実・ファイルが大きくなる
    
  推奨: AOF + everysec(1秒ごとにフラッシュ)
  ElastiCache Redis: AOF は無効(フェイルオーバーでレプリカが昇格)

ElastiCache の構成:
  Cluster Mode OFF: Primary 1 + Read Replica 最大5
  Cluster Mode ON:  シャーディング(データを複数ノードに分散)
                    スケールアウトが必要な大規模向け

6. DynamoDB vs Redis の棲み分け

同じ「高速な読み書き」でも違う:

DynamoDB:
  ✅ 永続化(データが消えない)
  ✅ 無制限のサイズ
  ✅ サーバーレス(管理不要)
  ❌ ミリ秒単位のレスポンス(数ms〜10ms程度)

Redis:
  ✅ サブミリ秒のレスポンス
  ✅ 豊富なデータ構造(Sorted Set 等)
  ✅ Pub/Sub
  ❌ メモリに全データを載せる(コスト大)
  ❌ 揮発性(設定によって消える)

組み合わせパターン:
  DynamoDB(永続化)+ Redis(キャッシュ)
  DynamoDB → Write-Through で Redis に書く
  → 読み取りは Redis から(速い)
  → データは DynamoDB に永続化(安全)

🔗 次のステップ