発展LESSON 20

この教材で学ぶこと RDS・Aurora・DynamoDB・ElastiCacheを要件から選びます。

AWS データベースサービス — RDS・Aurora・DynamoDB・ElastiCache

前提知識: データベースの基本。本教材ではクラウドDBの選択・設計・運用を扱います。


1. どれを選ぶか(意思決定ツリー)

トランザクション・複雑な結合が必要?
  YES → RDS / Aurora(リレーショナル)
    高可用性・大規模?
      YES → Aurora(MySQL/PostgreSQL 互換)
      NO  → RDS(シンプルなワークロード)

  NO → 超高速・無制限スケール・スキーマレス?
    YES → DynamoDB(キー・バリュー / ドキュメント)

セッション・キャッシュ・リーダーボード?
  → ElastiCache(Redis / Memcached)

全文検索?
  → OpenSearch Service

2. RDS — 基本設計

# Multi-AZ(高可用性)の仕組み
Primary DB (ap-northeast-1a) ←→ Standby DB (ap-northeast-1c)
         ↑同期レプリケーション
フェイルオーバー時間: 60〜120秒
エンドポイントは変わらない(DNS が切り替わる)

# Read Replica(読み取りスケール)
Primary → Read Replica 1 (同リージョン)
        → Read Replica 2 (別リージョン / DR 用)
非同期レプリケーション → ラグが発生することがある
# 接続ポーリング(RDS Proxy 経由)
# Lambda から直接 RDS に接続すると接続数を使い果たす問題
# RDS Proxy がプーリングを担当

import os
import psycopg2

# Lambda からは RDS Proxy エンドポイントに接続
conn = psycopg2.connect(
    host=os.environ["RDS_PROXY_ENDPOINT"],
    database="myapp",
    user="myapp_user",
    password=os.environ["DB_PASSWORD"],  # Secrets Manager から取得
    connect_timeout=5
)

3. Aurora — RDS との違い

Aurora の特徴:
  ストレージ: 自動拡張(最大128TB)、6コピー(3AZ × 2)
  読み取り: 最大15台の Read Replica(Writer と共有ストレージ)
  フェイルオーバー: 30秒以内(RDS は 60-120秒)

Aurora Serverless v2:
  最小 0.5 ACU(Aurora Capacity Unit)から自動スケール
  → 開発環境・アクセスが不規則なワークロードに最適
  → コールドスタートに注意(数秒かかることがある)

コスト: RDS より高い(ただし Read Replica は共有ストレージなので追加コスト小)

4. DynamoDB — キー設計が全て

DynamoDB のアーキテクチャ:
  テーブル = パーティション集合
  パーティションキー(PK)でデータの置き場所(パーティション)が決まる
  
  同じ PK を持つアイテムは同じパーティションに入る
  → PK が偏ると「ホットパーティション」になりスロットリング発生

パーティションキーの選び方:
  ❌ ステータス(active/inactive)→ 偏る
  ❌ 日付(2024-01)→ 偏る
  ✅ ユーザーID → 分散しやすい
  ✅ UUID → ランダムに分散

アクセスパターン駆動設計(DynamoDB はこれが全て)

import boto3
from boto3.dynamodb.conditions import Key, Attr

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

# 設計前に「どんな問い合わせをするか」を列挙する
# アクセスパターン例:
#   1. ユーザー XXX の注文一覧を取得(日時降順)
#   2. 注文 ID で1件取得
#   3. ユーザー XXX のステータス「shipped」の注文一覧

# テーブル設計:
#   PK: USER#userId
#   SK: ORDER#timestamp#orderId
#   GSI: status-index (PK=status, SK=createdAt)

# 1. ユーザーの注文一覧
response = table.query(
    KeyConditionExpression=Key("PK").eq("USER#user001") &
                           Key("SK").begins_with("ORDER#"),
    ScanIndexForward=False  # 降順
)

# 2. 注文 ID で取得
response = table.get_item(
    Key={"PK": "USER#user001", "SK": "ORDER#2024-01-15T10:00:00#order001"}
)

# 3. ステータスでフィルタ(GSI 使用)
response = table.query(
    IndexName="status-index",
    KeyConditionExpression=Key("status").eq("shipped") &
                           Key("createdAt").begins_with("2024-01")
)

5. DynamoDB — 本番設計のポイント

# トランザクション(2テーブルをまとめて更新)
dynamodb.meta.client.transact_write(
    TransactItems=[
        {
            "Update": {
                "TableName": "Orders",
                "Key": {"PK": "ORDER#001"},
                "UpdateExpression": "SET #status = :status",
                "ExpressionAttributeNames": {"#status": "status"},
                "ExpressionAttributeValues": {":status": "shipped"}
            }
        },
        {
            "Update": {
                "TableName": "Inventory",
                "Key": {"PK": "PRODUCT#001"},
                "UpdateExpression": "SET stock = stock - :qty",
                "ConditionExpression": "stock >= :qty",  # 在庫チェック
                "ExpressionAttributeValues": {":qty": 1}
            }
        }
    ]
)

# DynamoDB Streams → Lambda でのイベント駆動
# テーブルの変更をリアルタイムにキャプチャ → Lambda で処理
# 用途: 集計テーブル更新・Elasticsearch 同期・監査ログ

6. ElastiCache Redis — キャッシュパターン

import redis
import json
import hashlib

r = redis.Redis(host=os.environ["REDIS_HOST"], port=6379, decode_responses=True)

# Cache-Aside パターン(最も一般的)
def get_user(user_id: str) -> dict:
    cache_key = f"user:{user_id}"
    
    # 1. キャッシュから取得
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 2. DB から取得
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    
    # 3. キャッシュに書き込み(TTL: 300秒)
    r.setex(cache_key, 300, json.dumps(user))
    return user

# セッション管理
def create_session(user_id: str, token: str) -> None:
    r.setex(f"session:{token}", 3600, user_id)

def get_session(token: str) -> str | None:
    return r.get(f"session:{token}")

# レート制限(スライディングウィンドウ)
def check_rate_limit(user_id: str, limit: int = 100) -> bool:
    key = f"rate:{user_id}:{int(time.time()) // 60}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, 120)
    return count <= limit

7. コスト最適化まとめ

RDS/Aurora:
  ✅ Reserved Instances(1年・3年契約)で最大60%削減
  ✅ 開発環境は Aurora Serverless v2(使わない時間は課金なし)
  ✅ Storage Autoscaling ON(過剰プロビジョニングを避ける)

DynamoDB:
  ✅ オンデマンド vs プロビジョニング済みの選択
     → アクセス予測可能 → プロビジョニング済み(安い)
     → 予測不能・スパイク → オンデマンド(安全)
  ✅ DynamoDB Standard-IA → アクセス頻度が低いデータを自動移行

ElastiCache:
  ✅ Reserved Nodes で最大55%削減
  ✅ キャッシュヒット率を CloudWatch で監視(低い → キャッシュ設計を見直す)

🔗 次のステップ