AWS Database本番運用Vol3|ElastiCache×DAX×MemoryDB 完全ガイド

目次

1. なぜDatabase Vol3 (Cache編) か — Database三部作完結 + キャッシュ戦略の現実解

1-1. 本記事のゴール

本記事は AWS のキャッシュサービス3本柱 — ElastiCache for RedisDAX (DynamoDB Accelerator)MemoryDB for Redis — を本番運用視点で横断網羅する。各サービスの技術的な違いを整理するだけでなく、「どのユースケースでどれを選ぶか」を決める選定マトリクスと、そのまま本番投入できる Terraform 完全実装例を提供する。

Database Vol1 で主データストア (RDS/Aurora/DynamoDB) を固め、Vol2 で DR・移行・バックアップを固めた。本記事 Vol3 でキャッシュ層まで揃えることで、AWS データ基盤の全層を本番品質で設計・実装できるエンジニアを目指す。

到達点は次の3つだ。

  1. ElastiCache for Redis を Cluster Mode × Multi-AZ × TLS × RBAC で本番構築できる
  2. DAX の Item Cache / Query Cache / Batch 動作を理解し、適切なテーブルを選定できる
  3. MemoryDB for Redis の永続化モデル (Multi-AZ AOF) を理解し、ElastiCache との使い分けができる

1-2. 読者像

本記事の主な読者像は次の通りだ。

  • RDS・Aurora・DynamoDB は Vol1 で構築済みで、次はキャッシュ層を本番品質にしたいエンジニア。「とりあえず ElastiCache」で動いているが、Cluster Mode や Multi-AZ の設定に自信がない。
  • DAX を DynamoDB の全テーブルに適用してコストが膨らんでいる、または逆に「DAX は使いどころが難しい」と感じていて適用範囲に迷っているエンジニア。
  • ElastiCache Redis と MemoryDB for Redis をどう使い分けるか判断できていないエンジニア。永続化・整合性・コストの3軸で迷いやすいポイントだ。
  • Cache Stampede やメモリ Eviction といったキャッシュ本番運用の落とし穴を一度経験したことがある、あるいは経験する前に体系的に押さえておきたいエンジニア。

1-3. Vol1/Vol2との架橋 + 三部作完結の意義

Database三部作完結の意義 — AWS データ基盤 全層網羅

  • Vol1 (RDS / Aurora / DynamoDB): 主データストア層 — 永続化・整合性・ACID特性を担保する基盤。マルチAZレプリカ、Aurora Global Database の基礎を構築した。
  • Vol2 (DMS / Aurora Global / Streams / Backup): 災対と移行層 — クロスリージョン DR・ゼロダウンタイム移行・ポイントインタイムリストアを確立した。
  • Vol3 (本記事 — ElastiCache / DAX / MemoryDB): キャッシュ層 — レイテンシ最小化・OriginDB 負荷軽減・コスト最適化を担う。三部作でデータ基盤の全層を網羅する。
  • 三部作を組み合わせることで「構築 → 災対 → 高速化」のサイクルが完結し、どんな規模のシステムでも AWS データ基盤を本番品質で運用できるロードマップが手に入る。
キャッシュ本番運用 痛点5選 — 現場でよくある失敗パターン

  • Cache Stampede (Dog-piling) によるOriginDB過負荷: キャッシュ TTL が同時刻に一斉失効すると、数百リクエストが同時に DB へ到達する。対策は Probabilistic Early Expiration (PER) による TTL ゆらぎ追加、または Mutex Lock でリクエストを1本に絞ること。高トラフィック環境では必須対策だが見落とされやすい。
  • TTL設計ミスによるデータ陳腐化または過剰更新: TTL を長くしすぎると古いデータがキャッシュに残り続け、ユーザーに誤情報を提供する。短くしすぎるとキャッシュヒット率が低下し OriginDB 負荷が上がる。エンティティの「更新頻度 × 許容遅延」を事前に整理し、TTL ポリシーをデータ種別ごとに設計することが重要だ。
  • Single-AZ ElastiCache でのフェイルオーバ失敗: 開発コスト優先で Single-AZ のまま本番投入すると、AZ 障害時にキャッシュが消失し全リクエストが DB に流れ込む。Multi-AZ + Auto-Failover は本番では必須設定。Failover 時間は約 30 秒で DNS TTL を 60 秒以下にする必要もある。
  • DAX 全テーブル適用による過剰コスト: DynamoDB の全テーブルに DAX を当てると、書き込み多数・Read 少数のテーブルや管理系テーブルでもコストが発生し続ける。DAX は「読み取り多数・同一キーへのリクエスト集中・ミリ秒以下レイテンシが必要」なテーブルにのみ適用するべきだ。
  • MemoryDB vs ElastiCache Redis の選定誤り: “Redis が使いたい” だけで MemoryDB を選ぶと、ElastiCache Redis の 2〜3 倍のコストが発生する。MemoryDB が必要なのは「キャッシュでなくプライマリデータストアとして Redis を使う」「AOF による全データ永続化が必須」「強整合性 (Multi-AZ Transactional Log) が要件」の場合だ。セッション管理やリアルタイムカウンタなら ElastiCache Redis で十分なことが多い。

1-4. キャッシュ層を後回しにするリスク

キャッシュ層の構築を後回しにすることには、見えにくいリスクが3軸で存在する。

レイテンシ軸: RDS や DynamoDB へのクエリはネットワークレイテンシ込みで数ミリ秒〜数十ミリ秒かかる。ElastiCache Redis のインメモリアクセスは 0.1〜1 ミリ秒台だ。ユーザー体感速度はページ表示時間に直結し、EC サイトでは 100ms の遅延で直帰率が数 % 上昇するという A/B テスト結果が複数報告されている。

コスト軸: DB クエリは RCU / CPU / IOPS コストを消費する。DAU 10 万のサービスでセッションデータや商品マスタを毎回 RDS から取得した場合、月間のクエリ課金は ElastiCache Reserved Node の数倍に達することがある。キャッシュは「追加コスト」でなく「DB コスト削減手段」として捉えるべきだ。

スケール軸: トラフィックが増加した際に DB だけスケールアップし続けるアーキテクチャは限界が早い。ElastiCache Cluster Mode のシャード追加はダウンタイムゼロで水平スケールが可能だ。キャッシュ層を設計に組み込むことで、DB のスケール限界を大幅に先延ばしできる。


fig01: キャッシュ全体アーキテクチャ — ElastiCache / DAX / MemoryDB 3層構成 (Application → Cache → Database, Multi-AZ)

2. ElastiCache for Redis 本番運用 — Cluster Mode × Multi-AZ × TLS × Backup

2-1. Cluster Mode設計 — シャーディング + ノード数計算

ElastiCache Redis には Cluster Mode EnabledCluster Mode Disabled (Replication Group) の2つのモードがある。本番ワークロードに合わせた選定基準を以下の表で整理する。

項目Cluster Mode EnabledCluster Mode Disabled
シャーディング◎ 複数シャードで水平分割✕ 単一シャード
スケールアウト◎ シャード追加でゼロダウンタイム拡張✕ 不可 (スケールアップのみ)
メモリ上限◎ シャード数 × ノードメモリ△ 単ノードのメモリ上限
Failover◎ シャード単位 Auto-Failover◎ Replication Group 単位
マルチキー操作△ 同一スロットのキーのみ MGET/MSET 可◎ 制限なし
Lua スクリプト△ クロススロット参照不可◎ 制限なし
推奨用途大規模/高スループット/水平スケール必要小〜中規模/Lua多用/マルチキー多用

シャード数計算式:

シャード数 = CEIL(総データ量 [GB] / ノードメモリ [GB] × 1.5)

係数 1.5 はメモリオーバーヘッド (Redis 内部データ構造・レプリケーションバッファ) を考慮した安全マージンだ。例えば総データ量 60 GB / ノードメモリ (cache.r7g.xlarge: 26.32 GB) の場合:

CEIL(60 / 26.32 × 1.5) = CEIL(3.42) = 4 シャード

最小本番構成例 (cache.r7g.large: 13.07 GB):

  • 3 シャード × Primary 1 + Replica 2 = 9 ノード合計
  • 各シャードが別 AZ (ap-northeast-1a / 1c / 1d) に Primary を配置
  • Replica は Primary と別 AZ に自動配置 (Auto-Failover 有効時)

Cluster Mode のシャーディングは CRC16 ハッシュスロットで行われる。キーの CRC16 値を 16384 で割った余りをスロット番号とし、各シャードにスロット範囲を割り当てる。クライアントライブラリ (redis-py-cluster / Jedis Cluster 等) がスロットマップを自動管理し、正しいノードにルーティングする。

fig02: ElastiCache Cluster Mode シャーディング構成 — 3シャード × (Primary+Replica×2) = 9ノード / Hash Slot 0-16383 分散

2-2. Replication Group — Multi-AZ × Auto-Failover

本番では Primary 1 台 + Replica 2 台の最小3ノード構成が基本だ。

ap-northeast-1a: Primary node
ap-northeast-1c: Replica-1
ap-northeast-1d: Replica-2

Failover 動作の注意点:

  • Primary 障害検知から DNS フリップ完了まで 約 30 秒。アプリ側には接続リトライ (指数バックオフ) を必ず実装する。
  • ElastiCache のエンドポイントは CNAME で提供される。DNS TTL = 60 秒のため、クライアントが古い IP をキャッシュしていると Failover 後も旧 Primary に接続し続ける恐れがある。JVM や Go の DNS キャッシュ設定を確認し、TTL = 5〜10 秒程度に短縮することを推奨する。

Read 負荷分散:

Cluster Mode Disabled の場合は Reader Endpoint が提供される。全 Replica にラウンドロビンでアクセスできるため、Read 重視のワークロードでは Primary の CPU 負荷を大幅に下げられる。Cluster Mode Enabled の場合は各シャードの Replica に接続するよう、クライアントの readonly コマンドを使用する。

Automatic Failover vs Manual Failover:

  • Automatic Failover: AZ 障害・ノード障害を ElastiCache が自動検知し Primary を昇格。Multi-AZ 有効時は常に ON にすること。
  • Manual Failover (TestFailover API): メンテナンス計画・DR 訓練で意図的にフェイルオーバをトリガーする際に使用。本番で月1回のフェイルオーバ訓練を推奨する。

2-3. TLS in-transit + RBAC認証

TLS 設定: transit_encryption_enabled = true を必須とする。ElastiCache は AWS 発行の TLS 証明書を使用し、クライアントは tls:// スキームで接続する。ElastiCache Serverless でも同設定が適用される。

RBAC (Role-Based Access Control) 設定手順:

  1. ElastiCache User を作成し、コマンド権限を設定する
  2. ElastiCache User Group を作成し、User を紐付ける
  3. Replication Group に User Group を関連付ける

権限設定例:
~* + +@all: 全キー・全コマンドへのフルアクセス (管理者用)
~readonly:* + +@read: readonly: プレフィックスのキーに Read のみ
~session:* + +@write +@read -@dangerous: セッション管理用ユーザー

IAM Auth Token: ElastiCache IAM 認証を使うと EC2 / Lambda の IAM ロールを使って認証できる。パスワード管理が不要になりセキュリティ向上につながる。対象は ElastiCache Serverless + ElastiCache for Redis 7.0 以降。

AWS CLI でのユーザー / ユーザーグループ作成コマンドは後述するが、Terraform での一括管理が保守性に優れる。

Terraform による TLS + RBAC 完全実装例:

resource "aws_elasticache_subnet_group" "redis" {
  name = "redis-subnet-group"
  subnet_ids = var.private_subnet_ids
}

resource "aws_elasticache_parameter_group" "redis7" {
  name= "redis7-production"
  family = "redis7"

  parameter {
 name  = "maxmemory-policy"
 value = "allkeys-lru"
  }

  parameter {
 name  = "lazyfree-lazy-eviction"
 value = "yes"
  }

  parameter {
 name  = "timeout"
 value = "300"
  }

  parameter {
 name  = "tcp-keepalive"
 value = "300"
  }
}

resource "aws_elasticache_user" "app_readonly" {
  user_id = "app-readonly"
  user_name  = "app-readonly"
  access_string = "on ~readonly:* +@read"
  engine  = "REDIS"
  passwords  = [var.redis_readonly_password]
}

resource "aws_elasticache_user" "app_readwrite" {
  user_id = "app-readwrite"
  user_name  = "app-readwrite"
  access_string = "on ~* +@all -@dangerous"
  engine  = "REDIS"
  passwords  = [var.redis_readwrite_password]
}

resource "aws_elasticache_user_group" "app" {
  engine  = "REDIS"
  user_group_id = "app-user-group"
  user_ids= [aws_elasticache_user.app_readonly.user_id, aws_elasticache_user.app_readwrite.user_id]
}

resource "aws_elasticache_replication_group" "redis" {
  replication_group_id = "production-redis"
  description = "Production Redis Cluster Mode Enabled"
  engine= "redis"
  engine_version = "7.0"
  node_type= "cache.r7g.large"
  parameter_group_name = aws_elasticache_parameter_group.redis7.name
  subnet_group_name = aws_elasticache_subnet_group.redis.name
  security_group_ids= [aws_security_group.redis.id]

  # Cluster Mode Enabled
  num_node_groups= 3
  replicas_per_node_group = 2

  # Multi-AZ + Auto-Failover
  multi_az_enabled  = true
  automatic_failover_enabled = true

  # TLS
  transit_encryption_enabled = true
  at_rest_encryption_enabled = true

  # RBAC
  user_group_ids = [aws_elasticache_user_group.app.user_group_id]

  # Backup
  snapshot_retention_limit = 7
  snapshot_window = "17:00-18:00"
  maintenance_window = "sun:18:00-sun:19:00"

  # CloudWatch Logs
  log_delivery_configuration {
 destination= aws_cloudwatch_log_group.redis_slow_log.name
 destination_type = "cloudwatch-logs"
 log_format = "json"
 log_type= "slow-log"
  }

  tags = {
 Environment = "production"
 Terraform= "true"
  }
}

2-4. Backup — AOF + RDB + S3エクスポート

自動スナップショット: snapshot_retention_limit = 7 が本番設定の基本だ。これで7日間のスナップショットが自動保持される。snapshot_window は低トラフィック時間帯 (例: 17:00-18:00 UTC = 深夜2時 JST) に設定する。

手動スナップショット: メンテナンス作業・エンジンバージョンアップ前には必ず手動スナップショットを取得する。自動スナップショットの保持期間に依存せず、任意の期間保持できる。

aws elasticache create-snapshot \
  --replication-group-id production-redis \
  --snapshot-name pre-maintenance-20260517

S3 エクスポート (クロスリージョン DR 用):

# Step 1: スナップショット作成
aws elasticache create-snapshot \
  --replication-group-id production-redis \
  --snapshot-name dr-export-20260517

# Step 2: S3 に RDB ファイルをエクスポート
aws elasticache copy-snapshot \
  --source-snapshot-name dr-export-20260517 \
  --target-snapshot-name dr-export-20260517-s3 \
  --target-bucket "s3://your-dr-bucket/redis-backups/"

エクスポートされた RDB ファイルは DR リージョンで aws elasticache restore-replication-group-from-s3 コマンドでリストア可能だ。

AOF (Append Only File) と RDB の違い:

方式説明ElastiCache での扱い
RDB定期的なポイントインタイムスナップショットElastiCache スナップショット = RDB ベース
AOF全書き込み操作のログElastiCache ではクラスター内レプリケーションに相当

ElastiCache のスナップショットは RDB 形式だ。AOF の永続化 (全操作の永続ログ) が必要なケースでは MemoryDB for Redis を選択する (§4 で解説)。

2-5. パラメータチューニング

maxmemory-policy 比較表:

ポリシー動作推奨シナリオ
allkeys-lru全キーからLRUで削除セッション・汎用キャッシュ (キーに TTL なし混在)
volatile-lruTTL 付きキーのみ LRU で削除TTL で期限管理し、永続キーを保持したい場合
allkeys-lfu全キーからアクセス頻度最小順で削除アクセス偏りが大きいホットキーが存在する場合
volatile-lfuTTL 付きキーのみ LFU で削除TTL 管理 + アクセス頻度重視の混在環境
noevictionメモリ満杯時にエラー返却データ消失が許容できないプライマリストア用途
allkeys-random全キーからランダム削除アクセスパターンがほぼ均一な場合

本番では allkeys-lru を基本とし、DynamoDB や RDS のキャッシュ層として使う場合は volatile-lru も検討する。

主要パラメータ:

lazyfree-lazy-eviction: yes # 非同期削除でメインスレッドのレイテンシ改善
lazyfree-lazy-expire:yes # TTL 失効キーの非同期削除
timeout: 300 # アイドル接続を 300 秒で切断
tcp-keepalive: 300 # Keep-Alive パケット間隔 (秒)

CloudWatch Metrics 監視設定とアラーム閾値:

メトリクス閾値 (本番基準)意味
Evictions> 0 で即 Alertキャッシュがメモリ不足でデータを追い出している
SwapUsage> 50 MB で Warning, > 100 MB で Alertスワップ発生はレイテンシ急増の前兆
CPUUtilization> 70% で WarningRedis はシングルスレッドのため上限が低い
CurrConnections異常増加で Warningコネクションリークの検知
ReplicationLag> 100 ms で Warningレプリカの遅延 (Failover 時のデータ欠損リスク)
DatabaseMemoryUsagePercentage> 80% で Warningメモリ使用率の早期警告

Evictions > 0 は「今すぐスケールアウトが必要」を意味するため、即時 Alert に設定する。

2-6. スケール戦略 — Out vs Up 判断フロー

スケールアウト (シャード追加):

Cluster Mode Enabled でのみ実行可能。シャード追加はオンラインで行われ、ダウンタイムなしでデータが再分散される。

トリガー: Evictions > 0 が持続 (メモリ不足)
操作: ElastiCache コンソール or Terraform で num_node_groups を増加
所要時間: 数分〜15分 (データ再分散)

スケールアップ (ノードタイプ昇格):

トリガー: CPUUtilization > 70% が持続 (CPU ボトルネック)
操作: node_type を変更 (例: cache.r7g.large → cache.r7g.xlarge)
所要時間: Rolling Update で約 30〜60 分 (Primary → Replica の順)

判断フロー:

① CPUUtilization > 70% 持続
→ コマンド処理量超過 → スケールアップ (ノードタイプ昇格)

② Evictions > 0 持続 + DatabaseMemoryUsagePercentage > 80%
→ メモリ不足 → スケールアウト (シャード追加) [Cluster Mode Enabled 前提]
→ Cluster Mode Disabled の場合は スケールアップ or Cluster Mode Enabled に移行

③ ReplicationLag > 100ms 持続
→ レプリカ書き込み追従不足 → レプリカノードタイプ昇格

④ CurrConnections 上限付近
→ アプリ側コネクションプール設定の見直し (ElastiCache 単体のスケールでは解決しない)

Data Tiering (cache.r6gd): SSD と DRAM を階層化するノードタイプだ。アクセス頻度の低いデータを自動的に SSD に移動し、メモリコストを削減できる。大規模キャッシュ (数 TB 規模) でコスト最適化が必要な場合に有効だ。ただしレイテンシは DRAM アクセスより増加するため、ホットデータ比率と許容レイテンシを事前に確認する。

AWS CLI — ElastiCache User / UserGroup 作成コマンド:

# ユーザー作成 (ReadWrite)
aws elasticache create-user \
  --user-id app-readwrite \
  --user-name app-readwrite \
  --engine REDIS \
  --access-string "on ~* +@all -@dangerous" \
  --passwords "YourStrongPassword123!"

# ユーザー作成 (ReadOnly)
aws elasticache create-user \
  --user-id app-readonly \
  --user-name app-readonly \
  --engine REDIS \
  --access-string "on ~readonly:* +@read" \
  --passwords "AnotherStrongPassword456!"

# ユーザーグループ作成
aws elasticache create-user-group \
  --user-group-id app-user-group \
  --engine REDIS \
  --user-ids app-readwrite app-readonly

# Replication Group に UserGroup 関連付け
aws elasticache modify-replication-group \
  --replication-group-id production-redis \
  --user-group-ids-to-add app-user-group \
  --apply-immediately
ElastiCache Redis 本番運用ベストプラクティス

  • Multi-AZ + Auto-Failover を本番デフォルト: Cluster Mode Enabled なら num_node_groups ≥ 3 / replicas_per_node_group ≥ 1 を最小構成とする。AZ をまたいだ Primary 配置で単一 AZ 障害を吸収する。
  • TLS + RBAC をセットで設定: transit_encryption_enabled = true と user_group_ids の両方を設定しないと、暗号化は通信レベルの保護のみになる。アプリケーション別にユーザーと権限を分離することで、万が一の認証情報漏洩時の影響範囲を最小化できる。
  • snapshot_retention_limit = 7 + Manual Snapshot 運用: 自動スナップショット7日分に加え、メンテナンス前後に手動スナップショットを取得する。S3 エクスポートで DR リージョンにバックアップを持ち出す手順を事前に確立しておく。
  • CloudWatch Alarm の必須設定: Evictions > 0 (即 Alert)・SwapUsage > 50 MB (Warning) の2つは本番必須。これらを見逃すとアプリケーション障害が表面化してから原因特定に時間がかかる。
  • スロークエリログを CloudWatch Logs に転送: log_delivery_configuration で slow-log を CloudWatch Logs に送り、O(N) コマンド (KEYS・HGETALL 等) の使用を早期に検出する。
ElastiCache コスト最適化

  • Reserved Node Pricing (1年/3年予約): On-Demand 比で 1 年予約は約 30% 削減、3 年予約は約 55% 削減になる。本番環境で常時稼働するクラスターは Reserved Node に切り替えることでランニングコストを大幅削減できる。Convertible Reserved は途中でノードタイプ変更が可能なため柔軟性が高い。
  • Data Tiering (cache.r6gd) でメモリ階層化: 総キャッシュデータ量が数 TB を超える場合、DRAM のみの r7g 系より cache.r6gd (DRAM + NVMe SSD) でコストを 30〜50% 削減できる。ホットデータは DRAM、コールドデータは SSD に自動階層化される。
  • 非本番環境のサイズダウン: 開発・ステージング環境は cache.t4g.micro〜small で十分なケースが多い。Reserved Node は本番のみに適用し、非本番は On-Demand + 小サイズで維持することが最もコスト効率が高い。
  • ElastiCache Serverless の活用: 開発環境や低トラフィックのバッチ処理用途では ElastiCache Serverless が有効。使用した ECU (ElastiCache Compute Units) と GB の分だけ課金されるため、アイドル時間の長いワークロードには On-Demand より安くなる。

3. ElastiCache for Memcached 本番運用 — Node Type × Auto Discovery × Redis比較

ElastiCache for Memcached はマルチスレッドアーキテクチャを活用した高スループットキャッシュサービスだ。Redis と比較してシンプルなデータモデルながら、水平スケールと並列処理能力に優れており、純粋なキャッシュ用途に最適化されている。

3-1. Node Type選定 — cache.r7g vs cache.m7g

Memcached ノードタイプ選定は「メモリ容量優先」か「コスト最適化」かで分岐する。

メモリ重視: cache.r7g シリーズ (Graviton3 Memory-Optimized)

ノードタイプメモリvCPU価格 (東京)推奨用途
cache.r7g.large6.38 GB2$0.192/hセッションキャッシュ、オブジェクトキャッシュ
cache.r7g.xlarge12.93 GB4$0.384/h中規模アプリケーション
cache.r7g.2xlarge26.04 GB8$0.768/h大規模オブジェクトキャッシュ
cache.r7g.4xlarge52.24 GB16$1.536/hエンタープライズ用途

バランス重視: cache.m7g シリーズ (Graviton3 General-Purpose)

ノードタイプメモリvCPU価格 (東京)推奨用途
cache.m7g.large3.09 GB2$0.133/h低コスト汎用キャッシュ
cache.m7g.xlarge6.38 GB4$0.266/h中規模汎用キャッシュ
cache.m7g.2xlarge12.93 GB8$0.532/h高スループット汎用

Graviton3採用によるコスト効率改善

Graviton3 (r7g/m7g) は従来の Intel/AMD ベース (r6g/m6g) と比較して約 20% のコスト効率改善を実現する。同一メモリ容量でスループットが向上するため、新規構築では Graviton3 系を選定すること。

ノード数計算式

# 必要ノード数の計算
# 必要ノード数 = ceil(総キャッシュデータ量 / ノードメモリ × 1.5)
# 安全係数 1.5: キーオーバーヘッド10% + 増加バッファ20% + 内部管理領域20%

例: 総キャッシュデータ量 30 GB、cache.r7g.large (6.38 GB) 使用時:
– 必要ノード数 = ceil(30 / 6.38 × 1.5) = ceil(7.05) = 8ノード

3-2. Auto Discovery — ElastiCache クラスターエンドポイント

Auto Discovery は ElastiCache for Memcached クラスターのノード情報をアプリケーション側で自動取得する機能だ。ノードの追加・削除が発生しても、アプリケーション側の設定変更なしに接続先を更新できる。

仕組み

Configuration Endpoint (mycluster.cfg.xxx.cache.amazonaws.com:11211) に接続すると、クラスター内の全ノード IP・ポートが返却される。Auto Discovery 対応クライアントはデフォルト 60 秒間隔でポーリングし、ノード追加・削除を自動検知して接続プールを更新する。

Auto Discovery 対応 SDK

言語SDK入手先
JavaAmazon ElastiCache Cluster Client for JavaAWS GitHub
PHPAmazon ElastiCache Cluster Client for PHPAWS GitHub
Pythonpymemcache + ElastiCache Discoverypip install
.NETEnyimMemcached + AutoDiscovery ExtensionNuGet

Python 設定例

from pymemcache.client.hash import HashClient

cluster_endpoint = "mycluster.cfg.xxx.cache.amazonaws.com"
cluster_port = 11211

client = HashClient(
 [(cluster_endpoint, cluster_port)],
 use_pooling=True,
 max_pool_size=10,
 timeout=0.5,
 connect_timeout=1.0,
)

client.set("session:user123", "session_data", expire=3600)
value = client.get("session:user123")

Auto Discovery 非対応言語での代替策

Node.js や Ruby など一部の言語・ライブラリは Auto Discovery に非対応だ。この場合は以下の代替策を検討する:

  1. 個別ノードエンドポイントをハードコード — スケール時に手動更新が必要 (運用負荷大)
  2. Redis への移行 — ElastiCache for Redis はクラスターモード対応でノード自動検出が容易
  3. サービスメッシュ経由 — ECS Service Connect でエンドポイント管理を委譲

一般的に Auto Discovery 非対応言語での Memcached 運用は推奨しない。Redis への移行を優先すること。

3-3. Redis vs Memcached 選定マトリクス

比較項目RedisMemcached
データ永続化RDB + AOF 対応なし (揮発性のみ)
Multi-AZ / Failoverあり (自動フェイルオーバー)なし
データ型豊富 (String/List/Set/Hash/Sorted Set/Stream)シンプル (文字列のみ)
スレッドモデルシングルスレッド (I/O多重化)マルチスレッド
最大スループット単一コア上限ありマルチコア活用可
Pub/Subありなし
Lua scriptingありなし
Sorted Set / Leaderboardありなし
Transactions (MULTI/EXEC)ありなし
Auto Discoveryクラスターモードで対応ElastiCache Cluster Client 必要

選定判断フロー

キャッシュ要件
├── データ永続化が必要? → YES → Redis
├── Multi-AZ フェイルオーバーが必要? → YES → Redis
├── Pub/Sub が必要? → YES → Redis
├── Sorted Set / Leaderboard が必要? → YES → Redis
├── Lua スクリプトが必要? → YES → Redis
├── Auto Discovery 非対応言語を使用? → YES → Redis
└── 上記すべて NO かつ高並列マルチスレッドが必要? → Memcached

現代のほとんどのユースケースでは Redis が推奨される。Memcached を選ぶ積極的な理由は「マルチスレッドによる高並列処理」かつ「データ永続化不要」の場合のみだ。特に数百万 QPS レベルの純粋なキー/バリューキャッシュや、小さなオブジェクト (128 bytes 以下) の大量キャッシュでは Memcached の優位性がある。

Redis vs Memcached 選定サマリ

  • Redisを選ぶ場合: Multi-AZ Failover / Pub/Sub / Sorted Set / Lua / データ永続化 / TLS+RBAC のいずれかが必要
  • Memcachedを選ぶ場合: 純粋なKey/Valueキャッシュのみ + マルチスレッドによる高並列処理が必要 + データ永続化不要
  • 迷ったらRedis: 機能の超過コストはほぼゼロ。将来の要件変化にも対応可能

3-4. ユースケース — セッションキャッシュ / フルページキャッシュ

PHP セッションキャッシュ設定例

# php.ini での ElastiCache Memcached セッション設定
# php-memcached 拡張 (libmemcached ベース) を使用
session.save_handler = memcached
session.save_path = "mycluster.cfg.xxx.cache.amazonaws.com:11211?persistent=1&weight=1&timeout=1&retry_interval=15"

php-memcache (古い拡張) ではなく php-memcached を使うこと。PECL install memcached でインストール後、extension=memcached.so を有効化する。

WordPress オブジェクトキャッシュ (W3 Total Cache)

// wp-content/object-cache.php での設定 (W3 Total Cache が自動生成)
define('W3TC_MEMCACHED_SERVERS', [
 'mycluster.cfg.xxx.cache.amazonaws.com:11211',
]);
define('W3TC_MEMCACHED_PERSISTENT', true);
define('W3TC_MEMCACHED_TIMEOUT', 30);

WordPress での設定手順:
1. W3 Total Cache インストール
2. Performance → Object Cache → Memcached を選択
3. Configuration Endpoint を入力
4. wp-content/object-cache.php が自動生成されることを確認

フルページキャッシュ: CloudFront + ElastiCache ハイブリッド構成

ユーザー
 ↓
CloudFront (CDN層キャッシュ: TTL 5-60分)
 ↓ キャッシュミス時
Application Load Balancer
 ↓
EC2 / ECS アプリケーション
 ↓ DB/API アクセス前にチェック
ElastiCache Memcached (アプリ層キャッシュ: TTL 1-5分)
 ↓ キャッシュミス時
RDS / DynamoDB

CloudFront: 静的コンテンツ・認証不要ページの長期キャッシュ
ElastiCache: 動的コンテンツ・ユーザー別データの短期キャッシュ

Terraform: aws_elasticache_cluster (Memcached) 完全実装例

resource "aws_elasticache_subnet_group" "memcached" {
  name = "memcached-subnet-group"
  subnet_ids = var.private_subnet_ids

  tags = {
 Name  = "memcached-subnet-group"
 Environment = var.environment
  }
}

resource "aws_elasticache_parameter_group" "memcached" {
  family = "memcached1.6"
  name= "memcached-prod-params"

  parameter {
 name  = "max_item_size"
 value = "10485760"
  }

  parameter {
 name  = "chunk_size_growth_factor"
 value = "1.25"
  }

  tags = {
 Name  = "memcached-prod-params"
 Environment = var.environment
  }
}

resource "aws_security_group" "memcached" {
  name  = "memcached-sg"
  description = "Security group for ElastiCache Memcached cluster"
  vpc_id= var.vpc_id

  ingress {
 description  = "Memcached from app tier"
 from_port = 11211
 to_port= 11211
 protocol  = "tcp"
 security_groups = [aws_security_group.app.id]
  }

  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
 Name  = "memcached-sg"
 Environment = var.environment
  }
}

resource "aws_elasticache_cluster" "memcached" {
  cluster_id  = "memcached-prod"
  engine= "memcached"
  engine_version = "1.6.22"
  node_type= "cache.r7g.large"
  num_cache_nodes= 3
  parameter_group_name = aws_elasticache_parameter_group.memcached.name
  subnet_group_name = aws_elasticache_subnet_group.memcached.name
  security_group_ids= [aws_security_group.memcached.id]
  port  = 11211

  maintenance_window= "sun:05:00-sun:06:00"
  snapshot_retention_limit = 0
  notification_topic_arn= var.sns_topic_arn
  az_mode  = "cross-az"

  preferred_availability_zones = [
 "${var.aws_region}a",
 "${var.aws_region}c",
 "${var.aws_region}d",
  ]

  apply_immediately = false

  tags = {
 Name  = "memcached-prod"
 Environment = var.environment
 Purpose  = "application-cache"
  }
}

resource "aws_cloudwatch_metric_alarm" "memcached_cpu" {
  alarm_name = "memcached-high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name= "CPUUtilization"
  namespace  = "AWS/ElastiCache"
  period  = 300
  statistic  = "Average"
  threshold  = 80
  alarm_description= "ElastiCache Memcached CPU utilization is high"
  alarm_actions = [var.sns_topic_arn]

  dimensions = {
 CacheClusterId = aws_elasticache_cluster.memcached.cluster_id
  }
}

resource "aws_cloudwatch_metric_alarm" "memcached_evictions" {
  alarm_name = "memcached-high-evictions"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name= "Evictions"
  namespace  = "AWS/ElastiCache"
  period  = 300
  statistic  = "Sum"
  threshold  = 1000
  alarm_description= "High evictions indicate memory pressure on Memcached nodes"
  alarm_actions = [var.sns_topic_arn]

  dimensions = {
 CacheClusterId = aws_elasticache_cluster.memcached.cluster_id
  }
}
Memcached 選定基準

  • データ永続化不要・揮発性キャッシュ専用 → Memcached (永続化・Failover が必要なら Redis)
  • マルチスレッドによる高並列処理が必要 (数百万 QPS) → Memcached (Redis はシングルスレッド)
  • Auto Discovery 対応 SDK が利用可能な言語 (Java/PHP/Python) → Memcached 運用可。非対応言語では Redis 推奨
  • コスト最優先の純粋キャッシュ用途 → cache.m7g.large が最小コスト構成

4. DAX (DynamoDB Accelerator) 本番運用 — Item Cache × Query Cache × Write-Through

DAX は DynamoDB と互換 API を持つマネージドインメモリキャッシュクラスタだ。DynamoDB SDK をほぼ無修正で DAX クライアントに切り替えるだけで、読み取りレイテンシをマイクロ秒単位に短縮できる。

graph LR
 subgraph AppLayer["Application Layer"]
  App["アプリケーション\nDAX Client"]
 end

 subgraph DAXCluster["DAX Cluster (3ノード構成)"]
  Primary["Primary Node\n書き込み・調整"]
  Replica1["Replica Node 1\n読み取り"]
  Replica2["Replica Node 2\n読み取り"]
  Primary --- Replica1
  Primary --- Replica2
 end

 subgraph CacheStore["Cache Stores"]
  IC["Item Cache\nGetItem / BatchGetItem\nTTL: 5分 (デフォルト)"]
  QC["Query Cache\nQuery / Scan\nTTL: 1分 (デフォルト)"]
 end

 subgraph Backend["Backend"]
  DDB[("DynamoDB Table")]
 end

 App -->|"1. 読み取りリクエスト"| Primary
 Primary -->|"2a. Cache HIT (μs)"| IC
 Primary -->|"2b. Cache HIT (μs)"| QC
 IC -->|"3. 即時レスポンス"| App
 QC -->|"3. 即時レスポンス"| App
 Primary -->|"4. Cache MISS → フォールバック"| DDB
 DDB -->|"5. データ返却 + Cache更新"| Primary
 Primary -->|"6. レスポンス (ms)"| App
 App -->|"Write-Through\n書き込み伝播"| Primary
 Primary -->|"書き込み反映"| DDB

4-1. Item Cache vs Query Cache の使い分け

DAX は 2 種類のキャッシュストアを内部に持つ。それぞれ異なる API 操作に対応している。

Item Cache

項目内容
対象 APIGetItem, BatchGetItem
TTL デフォルト5 分 (300,000 ms)
キャッシュキーテーブル名 + プライマリキー (Partition Key + Sort Key)
Hit 率計算ItemCacheHits / (ItemCacheHits + ItemCacheMisses)
最適ユースケース単一アイテムの頻繁な読み取り

Query Cache

項目内容
対象 APIQuery, Scan
TTL デフォルト1 分 (60,000 ms)
キャッシュキーテーブル名 + クエリパラメータ全体 (完全一致)
Hit 率計算QueryCacheHits / (QueryCacheHits + QueryCacheMisses)
最適ユースケース同一クエリ条件の繰り返し実行

ユースケース別 DAX 適用判断

テーブルの読み取りパターン分析
├── GetItem の RCU が全体の 80% 以上? → Item Cache 効果大 → DAX 推奨
├── 同一 Query パラメータが繰り返される? → Query Cache 効果大 → DAX 推奨
├── 読み取りに対して書き込みが多い (書き込み率 > 50%)? → Cache invalidation 頻発 → DAX 非推奨
└── Strongly Consistent Read が必須? → DAX 経由不可 → DAX 非推奨

CloudWatch での Hit 率監視:

import boto3

cloudwatch = boto3.client('cloudwatch', region_name='ap-northeast-1')

response = cloudwatch.get_metric_statistics(
 Namespace='AWS/DAX',
 MetricName='ItemCacheHits',
 Dimensions=[{'Name': 'ClusterName', 'Value': 'my-dax-cluster'}],
 StartTime='2026-05-17T00:00:00Z',
 EndTime='2026-05-17T23:59:00Z',
 Period=3600,
 Statistics=['Sum'],
)
print(response['Datapoints'])

4-2. Read-Through — DAXがDynamoDBへフォールバック

Read-Through は DAX の中核機能だ。アプリケーションは DAX クライアントに対して通常の DynamoDB API を発行するだけでよく、キャッシュヒット/ミスの制御は DAX が透過的に行う。

Cache Miss 時の自動フォールバック

1. アプリ → DAX: GetItem(TableName, Key)
2. DAX: Item Cache 確認 → MISS
3. DAX → DynamoDB: GetItem(TableName, Key) (自動フォールバック)
4. DynamoDB → DAX: Item データ返却
5. DAX: Item Cache に格納 (TTL = 5分)
6. DAX → アプリ: Item データ返却

TTL 経過後の最初のアクセスは DynamoDB への読み取りが発生する。高頻度アクセスのテーブルでは TTL を短く設定しすぎると DynamoDB RCU が増加するため注意が必要だ。

Eventually Consistent Read 前提

DAX は Eventually Consistent Read のみサポートする。DynamoDB では以下の API で ConsistentRead=True が指定可能だが、DAX 経由の場合はキャッシュが返されるため Strongly Consistent にならない。

APIConsistentRead=True 指定時DAX 経由の動作
GetItemStrongly ConsistentDAX キャッシュから返却 (Eventual のまま)
QueryStrongly ConsistentDAX キャッシュから返却 (Eventual のまま)

Strongly Consistent Read が必要な場合は DAX クライアントを使わず DynamoDB クライアントを直接使用する。

Python: DAX client vs DynamoDB client 使い分けコード例

import amazondax
import boto3
from typing import Optional

class DynamoDBRepository:
 def __init__(self, table_name: str, dax_endpoint: str, region: str = 'ap-northeast-1'):
  self.table_name = table_name

  self.dax = amazondax.AmazonDaxClient(
endpoints=[dax_endpoint],
region_name=region,
  )

  self.dynamodb = boto3.client('dynamodb', region_name=region)

 def get_item(self, key: dict, consistent_read: bool = False) -> Optional[dict]:
  if consistent_read:
resp = self.dynamodb.get_item(
 TableName=self.table_name,
 Key=key,
 ConsistentRead=True,
)
  else:
resp = self.dax.get_item(
 TableName=self.table_name,
 Key=key,
)
  return resp.get('Item')

 def transact_write(self, transact_items: list) -> dict:
  return self.dynamodb.transact_write_items(
TransactItems=transact_items,
  )

4-3. Write-Through vs Write-Around

DAX の書き込みモードは 2 種類ある。テーブルの読み書きパターンに応じて選択する。

Write-Through (DAX 経由で書き込み)

アプリ → DAX: PutItem / UpdateItem
DAX → DynamoDB: 書き込み伝播
DAX: Item Cache を最新値で更新 → キャッシュ整合性を維持

特徴:
– キャッシュが最新データを保持 → 次の読み取りで Cache Hit
– DAX クライアントから全ての書き込みを行う場合に有効
– 読み取り多・書き込み少のテーブルに最適

Write-Around (DynamoDB 直接書き込み)

アプリ → DynamoDB: PutItem / UpdateItem (DAX をバイパス)
DAX: Cache は古いデータのまま (TTL 切れまで残存)
次の読み取り: Cache Miss → DynamoDB から最新データ取得 → Cache 更新

特徴:
– 書き込みは DynamoDB のスループットをそのまま活用
– DAX キャッシュは TTL 後に自動更新
– 書き込み多・読み取り少のテーブルや一括バッチ処理に適用

選定ガイド

パターン推奨方式理由
読み取り多 + 書き込み少Write-Throughキャッシュ整合性を維持しつつ読み取りを高速化
書き込み多 + 読み取り少Write-Around または DAX 不要キャッシュ効果が薄いため DAX のコストが割高
バッチ一括投入後に高頻度読み取りWrite-Around + TTL 後に読み取り開始バッチ中は DynamoDB 直接書き込みで高スループット
リアルタイム更新 + 即時読み取りWrite-Through書き込み直後のキャッシュ整合性が重要

4-4. クラスタサイズ計算 + コスト最適化

ノードタイプ一覧 (東京リージョン)

ノードタイプメモリvCPU価格 (On-Demand)
dax.r5.large13.5 GB2$0.269/h
dax.r5.xlarge27.0 GB4$0.538/h
dax.r5.2xlarge54.0 GB8$1.076/h
dax.r5.4xlarge108.0 GB16$2.152/h
dax.r5.8xlarge216.0 GB32$4.304/h
dax.r5.16xlarge432.0 GB64$8.608/h

メモリ容量計算式

必要メモリ = キャッシュ対象データ量 × 1.3 (安全係数)
安全係数 1.3 の内訳: 内部管理オーバーヘッド10% + キャッシュキーメタデータ10% + 将来増加バッファ10%

例: キャッシュ対象データ量 30 GB → 必要メモリ = 30 × 1.3 = 39 GB → dax.r5.4xlarge (108 GB) が余裕を持った選択

コスト試算 (dax.r5.large, 3ノード構成)

料金区分計算月額コスト
On-Demand (3ノード)$0.269 × 3 × 720h約 $581/月
Reserved (1年, 3ノード)On-Demand × 64%約 $372/月
Reserved (3年, 3ノード)On-Demand × 45%約 $261/月

Reserved Instance 選定: 本番環境で 6 ヶ月以上継続使用なら 1年 Reserved (36% 削減)、24 ヶ月以上の長期運用なら 3年 Reserved (55% 削減)。

DynamoDB RCU 削減効果との比較

Cache Hit 率 90% 時の RCU 削減効果:
– 元の RCU 消費: 10,000 RCU/秒
– DAX 導入後: 1,000 RCU/秒 (90% 削減)
– RCU 削減による DynamoDB コスト削減: 月約 $200〜$500 (規模による)

DAX クラスタコスト ($372/月) vs RCU 削減効果 ($200〜$500/月) を比較して導入判断を行う。

DAX 導入判断基準

  • ROI計算式: DAXコスト (dax.r5.large×3: 約$194/月) < DynamoDB RCU削減額 が導入条件
  • 適用推奨: GetItem/BatchGetItem読み取りが月間1億回超のテーブル / レイテンシ要件がマイクロ秒単位
  • 適用非推奨: Strongly Consistent Read必須の金融取引 / 書き込み多・読み取り少のテーブル / バッチ処理専用テーブル
  • 段階的導入: まずTop-3高頻度テーブルのみDAX適用 → 効果計測後に拡大判断

4-5. DAX非対応操作 — TransactGetItems等の回避策

DAX がサポートしない操作は DynamoDB クライアントを直接使用する必要がある。

DAX 非対応操作一覧

非対応操作代替策
TransactGetItemsDynamoDB client 直接使用
TransactWriteItemsDynamoDB client 直接使用
PartiQL (ExecuteStatement 等)DynamoDB client 直接使用
DynamoDB StreamsDynamoDB client 直接使用
Parallel Scan (Segment 指定)一部制限あり、DynamoDB client 推奨
コントロールプレーン APIDynamoDB client 使用

実装パターン: DAX + DynamoDB client 両方保持

import amazondax
import boto3
from typing import Optional, List

class DynamoDBDAXRepository:
 """DAX クライアントと DynamoDB クライアントを使い分けるリポジトリ"""

 def __init__(
  self,
  table_name: str,
  dax_endpoint: str,
  region: str = 'ap-northeast-1',
 ):
  self.table_name = table_name

  self.dax = amazondax.AmazonDaxClient(
endpoints=[dax_endpoint],
region_name=region,
  )

  self.dynamodb = boto3.client('dynamodb', region_name=region)

 def get_item(self, key: dict, consistent_read: bool = False) -> Optional[dict]:
  """通常読み取り: DAX 経由 (Eventually Consistent)"""
  if consistent_read:
resp = self.dynamodb.get_item(
 TableName=self.table_name,
 Key=key,
 ConsistentRead=True,
)
  else:
resp = self.dax.get_item(
 TableName=self.table_name,
 Key=key,
)
  return resp.get('Item')

 def transact_write(self, transact_items: List[dict]) -> dict:
  """トランザクション書き込み: DAX 非対応 → DynamoDB 直接"""
  return self.dynamodb.transact_write_items(
TransactItems=transact_items,
  )

 def batch_get_items(self, keys: List[dict]) -> List[dict]:
  """バッチ読み取り: DAX の BatchGetItem を活用"""
  resp = self.dax.batch_get_item(
RequestItems={
 self.table_name: {'Keys': keys}
}
  )
  return resp['Responses'].get(self.table_name, [])

Terraform: aws_dax_cluster + parameter_group + subnet_group 完全実装例

resource "aws_iam_role" "dax" {
  name = "dax-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "dax.amazonaws.com" }
 }]
  })
}

resource "aws_iam_role_policy" "dax" {
  name = "dax-dynamodb-access"
  role = aws_iam_role.dax.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Action = [
  "dynamodb:GetItem",
  "dynamodb:BatchGetItem",
  "dynamodb:Query",
  "dynamodb:Scan",
  "dynamodb:PutItem",
  "dynamodb:UpdateItem",
  "dynamodb:DeleteItem",
]
Resource = "arn:aws:dynamodb:${var.aws_region}:${data.aws_caller_identity.current.account_id}:table/${var.dynamodb_table_name}"
 }]
  })
}

resource "aws_dax_subnet_group" "main" {
  name = "dax-subnet-group"
  subnet_ids = var.private_subnet_ids
}

resource "aws_dax_parameter_group" "main" {
  name = "dax-prod-params"

  parameters {
 name  = "query-ttl-millis"
 value = "60000"
  }

  parameters {
 name  = "record-ttl-millis"
 value = "300000"
  }
}

resource "aws_security_group" "dax" {
  name  = "dax-sg"
  description = "Security group for DAX cluster"
  vpc_id= var.vpc_id

  ingress {
 description  = "DAX from app tier (unencrypted)"
 from_port = 8111
 to_port= 8111
 protocol  = "tcp"
 security_groups = [aws_security_group.app.id]
  }

  ingress {
 description  = "DAX from app tier (TLS)"
 from_port = 9111
 to_port= 9111
 protocol  = "tcp"
 security_groups = [aws_security_group.app.id]
  }

  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
 Name  = "dax-sg"
 Environment = var.environment
  }
}

resource "aws_dax_cluster" "main" {
  cluster_name = "dax-prod-cluster"
  iam_role_arn = aws_iam_role.dax.arn
  node_type = "dax.r5.large"
  replication_factor = 3

  parameter_group_name = aws_dax_parameter_group.main.name
  subnet_group_name = aws_dax_subnet_group.main.name
  security_group_ids= [aws_security_group.dax.id]

  server_side_encryption {
 enabled = true
  }

  cluster_endpoint_encryption_type = "TLS"

  maintenance_window  = "sun:05:00-sun:06:00"
  notification_topic_arn = var.sns_topic_arn

  availability_zones = [
 "${var.aws_region}a",
 "${var.aws_region}c",
 "${var.aws_region}d",
  ]

  tags = {
 Name  = "dax-prod-cluster"
 Environment = var.environment
  }
}

resource "aws_cloudwatch_metric_alarm" "dax_item_cache_hit_rate" {
  alarm_name = "dax-low-item-cache-hit-rate"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 3
  threshold  = 80

  metric_query {
 id = "hit_rate"
 expression  = "hits / (hits + misses) * 100"
 label = "Item Cache Hit Rate (%)"
 return_data = true
  }

  metric_query {
 id = "hits"
 metric {
metric_name = "ItemCacheHits"
namespace= "AWS/DAX"
period= 300
stat  = "Sum"
dimensions = {
  ClusterName = aws_dax_cluster.main.cluster_name
}
 }
  }

  metric_query {
 id = "misses"
 metric {
metric_name = "ItemCacheMisses"
namespace= "AWS/DAX"
period= 300
stat  = "Sum"
dimensions = {
  ClusterName = aws_dax_cluster.main.cluster_name
}
 }
  }

  alarm_description = "DAX Item Cache Hit Rate dropped below 80%"
  alarm_actions  = [var.sns_topic_arn]
}
DAX 設計ミス

  • 全テーブルに DAX を適用する → 読み取り頻度が低いテーブルへの DAX 適用はコスト増のみで効果なし。CloudWatch で RCU 消費量を分析し、読み取り頻度の高いテーブルに限定して適用すること
  • Strongly Consistent Read を DAX 経由で発行する → DAX はキャッシュを返すため Strongly Consistent にならない。金融取引・在庫確認など整合性が重要な操作は DynamoDB client を直接使用すること
  • Eventually Consistent を前提としない設計で DAX を導入する → TTL 期間中は古いデータが返される場合がある。設計段階で Eventually Consistent を前提とし、整合性が必要な箇所を明確に分離すること

5. MemoryDB for Redis 本番運用 — Durable Redis × Multi-Region × Snapshot

graph TB
  subgraph Region["ap-northeast-1"]
 APP["Application\n(ECS/Lambda)"]
 subgraph Cluster["MemoryDB Cluster"]
subgraph AZ1["AZ: ap-northeast-1a"]
  Primary["Primary Node\n(Read/Write)"]
end
subgraph AZ2["AZ: ap-northeast-1c"]
  Replica1["Replica Node\n(Read Only)"]
end
subgraph AZ3["AZ: ap-northeast-1d"]
  Replica2["Replica Node\n(Read Only)"]
end
WAL["Multi-AZ Transaction Log\n(Write-Ahead Log)\nRPO = 0"]
 end
 S3["S3 Bucket\n(Snapshot Export)"]
 DR["DR Region\nap-southeast-1\n(Cross-Region Restore)"]
  end
  APP -->|"6379 (TLS)"| Primary
  APP -->|"6379 (TLS)"| Replica1
  APP -->|"6379 (TLS)"| Replica2
  Primary -->|"Sync Write"| WAL
  WAL -->|"Sync Replication"| Replica1
  WAL -->|"Sync Replication"| Replica2
  Primary -->|"Daily Snapshot"| S3
  S3 -.->|"Cross-Region Export"| DR

MemoryDB for Redis は ElastiCache Redis と同じ Redis 互換 API を持ちながら、Multi-AZ Transaction Log (WAL) によるデータ永続化を提供する。金融取引・EC 注文・在庫管理など「書き込みデータをゼロロスで保護しなければならない」ユースケースを主なターゲットとする。


5-1. Durable Redis — AOFログ永続化のしくみ

MemoryDB の根幹を成す機能が Multi-AZ Transaction Log である。すべての書き込みは Redis のメモリに反映される前に、複数の AZ に分散配置されたトランザクションログへ同期的に書き込まれる。この仕組みにより RPO = 0(データ損失ゼロ) を保証する。

ElastiCache Redis との永続化比較

項目ElastiCache RedisMemoryDB for Redis
永続化方式RDB スナップショット + AOF (非同期)Multi-AZ Transaction Log (同期 WAL)
RPO> 0(最大数秒〜数分のデータロスあり)= 0(ゼロロス保証)
レプリケーション非同期同期
フェイルオーバー後のデータReplica が Primary に昇格 (一部ロスあり)WAL から完全リカバリ
コスト標準ElastiCache の約 2 倍

ElastiCache Redis は非同期レプリケーションを採用しているため、Primary がクラッシュした場合に Replica との差分データが失われる可能性がある。MemoryDB は書き込みのたびに WAL へ同期コミットするため、フェイルオーバー後も WAL からデータを完全にリカバリできる。

MemoryDB を選ぶべきシナリオ

  • 金融取引: 決済・送金トランザクションのステータスをセッション DB として保持する場合
  • EC 注文: カート情報・在庫引き当て・注文確定フローで中間ステータスを保持する場合
  • ゲームランキング: Sorted Set でリアルタイムランキングを管理し、障害時も順位データを消失させたくない場合
  • IoT テレメトリ: センサーデータを時系列でバッファリングし、ダウンストリームへの再送処理まで保持する場合

逆に、セッション管理や API レスポンスキャッシュなど「失われても再生成できる」データには ElastiCache Redis が適切でコストも低い。


5-2. Multi-AZ / Multi-Region クラスタ設計

Multi-AZ 構成(同一リージョン)

MemoryDB クラスタは Primary Node と複数の Replica Node を別 AZ に分散配置する。1 AZ 障害が発生しても残りの AZ の Replica が継続してサービスを提供し、WAL から整合性を担保しながら新 Primary を選出する。

推奨構成:
Primary × 1: 書き込みエンドポイント (cluster-endpoint)
Replica × 2 (別 AZ): 読み取りエンドポイント (reader-endpoint)
シャード数: 読み取りスケールが必要な場合は 2〜4 シャードに分割

Multi-Region 構成(DR 用途)

MemoryDB は現時点でネイティブのグローバルデータストア機能を持たないが、Snapshot → S3 エクスポート → クロスリージョン S3 コピー → DR リージョンでリストアというパターンで DR を実現できる。RPO はスナップショット間隔(最短 1 時間)となる点に注意する。

Subnet Group 設計

MemoryDB はプライベートサブネットにのみ配置する。パブリックサブネットへの配置は不可。

resource "aws_memorydb_subnet_group" "main" {
  name = "memorydb-private-subnet-group"
  subnet_ids = [
 aws_subnet.private_1a.id,
 aws_subnet.private_1c.id,
 aws_subnet.private_1d.id,
  ]

  tags = {
 Name = "memorydb-private-subnet-group"
 Env  = "production"
  }
}

セキュリティグループ設計

アプリケーション層(ECS タスクや Lambda 関数)の SG から MemoryDB SG の 6379 番ポートへのインバウンドのみを許可する。インターネットからの直接アクセスは遮断する。

resource "aws_security_group" "memorydb" {
  name  = "memorydb-sg"
  description = "MemoryDB cluster security group"
  vpc_id= aws_vpc.main.id

  ingress {
 from_port = 6379
 to_port= 6379
 protocol  = "tcp"
 security_groups = [aws_security_group.app.id]
 description  = "Allow Redis traffic from application layer"
  }

  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
 Name = "memorydb-sg"
 Env  = "production"
  }
}

5-3. Snapshot → S3エクスポート / クロスリージョンリストア

自動スナップショット

MemoryDB は毎日自動スナップショットを取得する。snapshot_retention_limit で保持日数(最大 35 日)を指定する。メンテナンスウィンドウ外の深夜帯をスナップショット取得時刻に設定することで、本番トラフィックへの影響を最小化できる。

手動スナップショット(重要操作前の必須手順)

# 手動スナップショット作成
aws memorydb create-snapshot \
  --cluster-name my-memorydb-cluster \
  --snapshot-name pre-maintenance-$(date +%Y%m%d) \
  --region ap-northeast-1

# スナップショット一覧確認
aws memorydb describe-snapshots \
  --cluster-name my-memorydb-cluster \
  --region ap-northeast-1 \
  --query 'Snapshots[*].{Name:Name,Status:Status,CreatedAt:CreationTime}'

手動スナップショットはクラスター削除後も保持されるため、クラスターを削除する前に必ず取得する。

S3 エクスポート手順

# スナップショットを S3 にエクスポート
aws memorydb export-snapshot \
  --snapshot-name pre-maintenance-20260517 \
  --s3-bucket-name my-memorydb-backup-bucket \
  --region ap-northeast-1

# エクスポートステータス確認
aws memorydb describe-snapshots \
  --snapshot-name pre-maintenance-20260517 \
  --region ap-northeast-1 \
  --query 'Snapshots[0].Status'

S3 バケットには memorydb.amazonaws.com への s3:PutObjects3:GetObject 権限を付与したバケットポリシーが必要となる。

クロスリージョンリストア(DR 手順)

  1. 東京リージョンのスナップショットを S3 にエクスポート
  2. aws s3 cp で DR リージョンの S3 バケットにコピー
  3. DR リージョンで aws memorydb create-cluster --snapshot-arns によりリストア

リストア時間目安:
– 10 GB: 約 10 分
– 50 GB: 約 30 分
– 100 GB: 約 60 分

実際のリストア時間はネットワーク帯域やシャード数によって変動するため、DR 訓練時に計測しておくことを推奨する。


MemoryDB Snapshot + クロスリージョン運用ポイント

  • RPO目標: AOFログ永続化により障害直前まで復旧可 (ElastiCache Redisよりデータ損失リスクが低い)
  • Snapshot頻度: 本番は日次自動Snapshot + 変更前の手動Snapshot必須
  • DR用途: クロスリージョンSnapshotでRTOを短縮 — us-east-1のSnapshotをap-northeast-1にコピーして事前準備
  • コスト注意: Snapshotストレージは $0.023/GB-month (東京) — 大容量クラスタでは月次コストを試算すること

5-4. ElastiCache Redis → MemoryDB マイグレーション

マイグレーション手順(ダウンタイム最小化)

1. ElastiCache Redis で RDB スナップショット取得
↓
2. スナップショットを S3 にエクスポート
↓
3. S3 スナップショットから MemoryDB クラスターを作成
↓
4. データ整合性確認(キー数・TTL・データ型)
↓
5. アプリケーション設定変更(エンドポイント切替)
↓
6. 本番トラフィックを MemoryDB にルーティング(Blue-Green)
↓
7. 旧 ElastiCache Redis を削除

Blue-Green 切替のポイント

  • 切替前に ElastiCache と MemoryDB を並行稼働させ、書き込みを二重化するか、メンテナンスウィンドウ中に切替を実施する
  • アプリケーションの Redis クライアントライブラリが Redis 7.x 互換であることを確認する(MemoryDB は Redis 7.x 互換)
  • TLS 接続が必須となるため、ElastiCache で TLS を有効化していない場合はクライアント設定の変更が必要

Terraform での移行準備

# ElastiCache Redis から S3 スナップショットをインポートして MemoryDB を作成
resource "aws_memorydb_cluster" "migrated" {
  name = "memorydb-migrated"
  node_type  = "db.r6g.large"
  num_shards = 1
  num_replicas_per_shard = 2
  subnet_group_name= aws_memorydb_subnet_group.main.name
  security_group_ids  = [aws_security_group.memorydb.id]
  acl_name= aws_memorydb_acl.main.name
  tls_enabled= true
  parameter_group_name= aws_memorydb_parameter_group.main.name

  # ElastiCache スナップショットから初期データをインポート
  snapshot_arns = ["arn:aws:s3:::my-elasticache-backup-bucket/snapshot.rdb"]

  tags = {
 Name = "memorydb-migrated"
 MigratedAt = "2026-05-17"
  }
}

5-5. MemoryDB vs ElastiCache Redis 選定基準

選定マトリクス

要件MemoryDB for RedisElastiCache Redis
データ永続保証(RPO = 0)✅ WAL 同期書き込み❌ 一部ロスあり(非同期)
Multi-AZ フェイルオーバー
読み取りスケールアウト✅ Replica 追加✅ Replica 追加
Redis 互換バージョンRedis 7.xRedis 7.x
TLS 接続必須オプション
コスト高(ElastiCache の約 2 倍)標準
ユースケース金融取引 / EC 注文 / 在庫セッション / API キャッシュ

判断フロー

書き込みデータの損失が許容できない?
  ├─ YES → MemoryDB for Redis
  │ (金融・EC・在庫・ゲームランキング)
  └─ NO → データは揮発してよい(再生成可能)?
  ├─ YES → ElastiCache Redis
  │ (セッション・APIキャッシュ・一時データ)
  └─ 要検討 → データサイズ・コスト・SLA を精査
MemoryDB コスト警戒 — 導入前にコスト試算を必ず実施

  • 基本料金が約 2 倍: MemoryDB の Durability コストは ElastiCache Redis と比較して約 2 倍。月次コストは事前に AWS Pricing Calculator で試算すること。
  • Multi-Region 追加課金: クロスリージョンのスナップショットコピーには S3 転送料金と S3 ストレージ料金が発生する。DR 要件の RTO/RPO に見合うかコスト評価が必要。
  • Snapshot 保管コスト: snapshot_retention_limit=7 以上に設定すると S3 ストレージが線形に増加する。不要なスナップショットは定期的に手動削除またはライフサイクルポリシーで自動削除する。
  • TLS オーバーヘッド: TLS 必須のため、大量の小さいコマンドを扱うワークロードでは CPU/レイテンシへの影響を事前に負荷テストで計測すること。

完全 Terraform 実装例

# MemoryDB ACL(アクセス制御リスト)
resource "aws_memorydb_acl" "main" {
  name = "memorydb-prod-acl"
  user_names = [aws_memorydb_user.app.name]

  tags = {
 Name = "memorydb-prod-acl"
 Env  = "production"
  }
}

resource "aws_memorydb_user" "app" {
  user_name  = "app-user"
  access_string = "on ~* &* +@all"

  authentication_mode {
 type= "password"
 passwords = [var.memorydb_password]
  }

  tags = {
 Name = "memorydb-app-user"
 Env  = "production"
  }
}

# MemoryDB Parameter Group
resource "aws_memorydb_parameter_group" "main" {
  name= "memorydb-prod-params"
  family = "memorydb_redis7"

  parameter {
 name  = "activedefrag"
 value = "yes"
  }

  parameter {
 name  = "lazyfree-lazy-eviction"
 value = "yes"
  }

  parameter {
 name  = "maxmemory-policy"
 value = "allkeys-lru"
  }

  tags = {
 Name = "memorydb-prod-params"
 Env  = "production"
  }
}

# MemoryDB Cluster(本番構成)
resource "aws_memorydb_cluster" "main" {
  name = "memorydb-prod"
  node_type  = "db.r6g.large"
  num_shards = 2
  num_replicas_per_shard = 2

  # Durability 設定
  tls_enabled = true

  # ネットワーク
  subnet_group_name  = aws_memorydb_subnet_group.main.name
  security_group_ids = [aws_security_group.memorydb.id]

  # ACL / Parameter Group
  acl_name = aws_memorydb_acl.main.name
  parameter_group_name = aws_memorydb_parameter_group.main.name

  # Snapshot 設定
  snapshot_retention_limit = 7
  snapshot_window = "02:00-04:00"

  # メンテナンスウィンドウ(JST 深夜 3:00-5:00 = UTC 18:00-20:00)
  maintenance_window = "sun:18:00-sun:20:00"

  tags = {
 Name = "memorydb-prod"
 Env  = "production"
  }
}

# 接続エンドポイント出力
output "memorydb_cluster_endpoint" {
  description = "MemoryDB cluster endpoint (write)"
  value = aws_memorydb_cluster.main.cluster_endpoint[0].address
}

output "memorydb_reader_endpoint" {
  description = "MemoryDB reader endpoint"
  value = aws_memorydb_cluster.main.cluster_endpoint[0].address
}
MemoryDB ユースケース別推奨 — データ損失がビジネス損失に直結する場合に選択

  • 金融サービス: 決済ステータス・残高キャッシュ・不正検知スコア。ゼロロス要件が厳格なため MemoryDB が最適。
  • EC サイトの注文・在庫: 在庫引き当てロック・カート確定ステータス。オーバーセルを防ぐには RPO = 0 が必須。
  • ゲームのリアルタイムランキング: Sorted Set によるスコアボード。障害後もランキングを完全復元できる MemoryDB が信頼性を高める。
  • 広告配信のフリークエンシーキャップ: ユーザーへの広告表示回数制限をカウンターで管理。消えると過配信につながるためデータ保護が重要。
  • セッション管理(高可用性要件): 通常は ElastiCache で十分だが、ログアウト強制が法規制要件となる金融・医療系はMemoryDB を採用するケースもある。

6. キャッシュ本番運用 詰まりポイント7選

本番環境でキャッシュ層を運用していると、設計段階では見えなかった問題が次々と顕在化する。ElastiCache・DAX・MemoryDBを問わず共通して発生しやすい7つの詰まりポイントと、その根本対策を解説する。


6-1. Cache Stampede / Dog-piling 対策

問題

人気コンテンツのTTLが同時に切れると、数百〜数千のリクエストが一斉にOriginDB(RDS・DynamoDB)へ流れ込む。キャッシュが空のため全リクエストがDB直撃となり、DBが過負荷でダウンする連鎖障害(Cache Stampede / Dog-piling)が発生する。

原因

  • 同一キーに同一TTLを設定 → 一斉expire
  • 高トラフィック時にexpireと同時に大量リクエストが集中
  • ロック機構がないためDB読み取りが重複実行される

対策1: Probabilistic Early Expiration(確率的早期更新)

TTLの残り10%に入った時点でランダム確率でキャッシュを早期更新する手法。完全なロックを使わずStampedeを分散させられる。

import random
import time
import redis

r = redis.Redis(host='my-cluster.cache.amazonaws.com', port=6379, ssl=True)

def get_with_probabilistic_refresh(key: str, fetch_fn, ttl: int = 300):
 """
 Probabilistic Early Expiration でCache Stampedeを防止する。
 TTL残り10%以下になったとき、確率的に早期再フェッチを実行する。
 """
 value = r.get(key)
 remaining_ttl = r.ttl(key)

 # TTL残り10%以下かつ30%の確率で早期更新
 if value is not None and remaining_ttl < ttl * 0.1:
  if random.random() < 0.3:
new_value = fetch_fn()
r.setex(key, ttl, new_value)
return new_value

 if value is None:
  value = fetch_fn()
  r.setex(key, ttl, value)

 return value

対策2: Mutex Lock(分散ロック)

RedisのSET NX EXを使い、最初のリクエストのみがDB読み取りを行うフラグを立てる。他のリクエストは待機してキャッシュの更新を待つ。

-- Lua スクリプト: 原子的ロック取得
-- KEYS[1]: cache key, KEYS[2]: lock key
-- ARGV[1]: TTL (秒), ARGV[2]: lock TTL (秒)

local value = redis.call('GET', KEYS[1])
if value then
 return value
end

local lock = redis.call('SET', KEYS[2], '1', 'NX', 'EX', ARGV[2])
if lock then
 -- このプロセスがフェッチを担当
 return nil  -- caller側でDB読み取りを実行してキャッシュに書き戻す
else
 -- 他プロセスがフェッチ中: 短時間待機して再取得
 redis.call('WAIT', 0, 100)  -- 100ms待機
 return redis.call('GET', KEYS[1])
end

対策3: Refresh-Ahead(事前更新)

バックグラウンドジョブがTTL期限前にキャッシュを更新する方式。ユーザーリクエストのレイテンシに影響せずキャッシュをホットに保てる。EventBridge Schedulerや Lambda関数でキーリストを定期的に再フェッチする設計が実践的。

fig03: Cache Stampede 対策フロー — アンチパターン(TTL切れ→DB大量同時hit) vs Probabilistic Early Expiration(事前TTL延長→Cache hit継続)


6-2. Cold Start問題 — Warm-up戦略

問題

ElastiCache再起動・スケールアウト直後・新ノード追加直後はキャッシュが空(Cold Start)となる。この状態でトラフィックが流れ込むと、すべてのリクエストがOriginDBに直撃し、DBが応答不能になるケースがある。

原因

  • デプロイスクリプトにCache事前投入ステップが含まれていない
  • ElastiCache Cluster Modeでシャードが再割り当てされ既存キャッシュが無効化
  • Auto Scalingで新ノードが追加された際のキャッシュ引き継ぎ未設計

対策1: デプロイ前のCache Pre-population

高頻度アクセスキーのリストをS3に保持し、デプロイスクリプトの最後にWarm-upジョブを実行する。

#!/bin/bash
# pre_populate_cache.sh: デプロイ後のキャッシュ事前投入

REDIS_HOST="my-cluster.cache.amazonaws.com"
S3_KEY_LIST="s3://my-app-config/cache-warmup-keys.json"
LOCAL_KEY_LIST="/tmp/warmup_keys.json"

# S3からキーリストを取得
aws s3 cp "$S3_KEY_LIST" "$LOCAL_KEY_LIST"

echo "[Warm-up] キャッシュ事前投入を開始します"
python3 - <<'PYEOF'
import json
import redis
import boto3
import requests

r = redis.Redis(host='my-cluster.cache.amazonaws.com', port=6379, ssl=True)

with open('/tmp/warmup_keys.json') as f:
 warmup_keys = json.load(f)

for item in warmup_keys:
 key = item['key']
 api_url = item['source_url']
 ttl = item.get('ttl', 300)

 # APIから最新データを取得してキャッシュに格納
 resp = requests.get(api_url, timeout=5)
 if resp.status_code == 200:
  r.setex(key, ttl, resp.text)
  print(f"  [OK] {key} (TTL={ttl}s)")
 else:
  print(f"  [SKIP] {key} - status={resp.status_code}")

print("[Warm-up] 完了")
PYEOF

対策2: Background Warm-up Job(Lambda)

Lambda関数をEventBridge Schedulerで5分おきに実行し、人気コンテンツをバックグラウンドでキャッシュ更新し続ける。Cloud Start時の初回ロードでも自動的にWarm-upが走るため人手介入が不要になる。

対策3: CloudFront + Cache-Control でエッジWarm-up

ElastiCacheより上位のエッジキャッシュ(CloudFront)を先行Warm-upすることで、ElastiCacheへのリクエスト集中そのものを防ぐ。Cache-Control: max-age=3600 を適切に設定し、CloudFrontがOriginキャッシュとして機能する設計が有効。


6-3. キャッシュ不整合 — Write-Behind Lag

問題

DBへの書き込みとキャッシュへの反映にタイムラグが生じ、古いデータをユーザーに返してしまう。特にWrite-Behind(非同期書き込み)パターンでは、DBが更新されるまでの間にキャッシュとDBが不整合状態となる。

キャッシュパターン比較

パターン読み取り書き込み整合性推奨用途
Cache-Asideキャッシュmiss→DB→キャッシュ更新DBのみ更新 (キャッシュ手動無効化)結果整合読み取り重視・汎用
Read-Throughキャッシュ層が自動でDB読取アプリ→DB→キャッシュ自動更新結果整合ORM統合・DAX
Write-Throughアプリ→キャッシュ+DBを同期書込両方同期更新強整合書き込み頻度低・金融
Write-Behindアプリ→キャッシュのみ即時・DB非同期キャッシュ→DBは非同期結果整合 (遅延あり)書き込み頻度高・ログ

Write-Behind Lag 対策

TTL短縮: Write-Behindパターンで整合性が求められるデータはTTLを30〜60秒に短縮し、キャッシュの自然期限切れを整合ポイントとして活用する。

Event-Driven Cache Invalidation: DynamoDB Streamsや RDS Aurora with PostgreSQLのLogical Replicationを使い、DB変更イベントをトリガーにキャッシュを即時invalidateする。

import boto3
import redis
import json

r = redis.Redis(host='my-cluster.cache.amazonaws.com', port=6379, ssl=True)

def lambda_handler(event, context):
 """
 DynamoDB Streams トリガー: DB更新を検知してキャッシュを無効化する。
 """
 for record in event.get('Records', []):
  if record['eventName'] in ('MODIFY', 'REMOVE'):
keys = record['dynamodb'].get('Keys', {})
item_id = keys.get('PK', {}).get('S', '')

# 関連キャッシュを全て無効化
cache_key = f"product:{item_id}"
deleted = r.delete(cache_key)
print(f"Cache invalidated: {cache_key} (deleted={deleted})")

6-4. TTL設計のアンチパターン

アンチパターン1: 全キーTTL=86400(固定1日)

すべてのキャッシュキーにTTL=86400を設定すると、データの変化頻度に関わらず1日後まで古いデータが返り続ける。商品価格・在庫情報・ユーザープロフィールなど頻繁に変わるデータが陳腐化する。

アンチパターン2: TTL=60(短すぎ)

全キーに1分TTLを設定すると、キャッシュヒット率が下がりOriginDBへのリクエストが増大する。DBのコネクション数・CPU使用率が跳ね上がり、スパイク時に過負荷状態になる。

正解: 用途別TTL階層設計

データの変化頻度と整合性要求に基づきTTLを分類する。

データ種別TTL理由
静的アセット(画像/CSS/JS)3600s(1時間)変更頻度が低く長期キャッシュが有効
商品情報・カタログ300s(5分)定期更新されるが即時反映不要
セッション・認証トークン1800s(30分)ユーザー操作タイムアウトに合わせる
APIレスポンス(動的)60s(1分)頻繁に変化するが短期間は許容
在庫・価格(高整合性)30s(30秒)誤表示リスクを最小化

TTL Jitter(揺らぎ)でStampede防止

同一種別のキーに同一TTLを設定すると一斉expireが起きる。TTLに±10%のランダム揺らぎを加えることで自然に分散できる。

import random

def get_jittered_ttl(base_ttl: int, jitter_pct: float = 0.1) -> int:
 """
 TTLにjitterを加えてCache Stampedeを防止する。
 例: base_ttl=300 → 270〜330秒のランダム値を返す
 """
 jitter = int(base_ttl * jitter_pct)
 return base_ttl + random.randint(-jitter, jitter)

# 使用例
r.setex('product:123', get_jittered_ttl(300), product_data)

CloudWatch監視: TTL別CacheMisses

ElastiCacheのCloudWatchメトリクスCacheMissesをTTL区分のタグ別に監視し、特定TTL区分のミス率が急増していないか定期確認する。


TTL設計 ベストプラクティス

  • TTL階層設計: 静的コンテンツ3600秒 / 動的データ300秒 / セッション1800秒 — 用途別に使い分ける
  • TTL Jitter: TTL = base_ttl + random(0, base_ttl * 0.1) でCache Stampede防止
  • TTL=0 (無期限) 禁止: Evictionポリシーに依存した不確定な挙動を招く — 必ず明示的TTLを設定
  • 固定TTLの罠: 全キーに同一TTL → 期限切れが集中してOrigin DB過負荷。Jitterで分散させること

6-5. Memory Fragmentation

問題

BytesUsedForCacheがmaxmemoryの70%程度なのにEvictionが発生したり、メモリ使用率が想定より早く上昇するケースがある。メモリフラグメンテーションが原因であることが多い。

原因

Redisはjemmallocを使ってメモリ管理しているが、異なるサイズのキーを頻繁に作成・削除すると断片化が進む。mem_fragmentation_ratioが1.5を超えると、論理的には空きがあっても物理メモリが断片化して有効活用できない状態になる。

診断: INFO memory コマンド

# Redis CLI で mem_fragmentation_ratio を確認
redis-cli -h my-cluster.cache.amazonaws.com -p 6379 --tls INFO memory | \
  grep -E "used_memory_human|used_memory_rss_human|mem_fragmentation_ratio|active_defrag"

# 期待出力例:
# used_memory_human: 2.50G
# used_memory_rss_human: 4.10G
# mem_fragmentation_ratio: 1.64  ← 1.5超えは要対処
# active_defrag_running: 0

対策: Active Defragmentation

redis.conf(またはElastiCacheパラメータグループ)で以下を設定し、Redisが実行時に断片化を自動解消するようにする。

# ElastiCache パラメータグループ設定 (redis.conf 相当)
# active-defrag-enabled: yes
# active-defrag-threshold-lower: 10  (fragmentation ratio 10% 超で開始)
# active-defrag-threshold-upper: 100
# active-defrag-ignore-bytes: 104857600  (100MB 未満は無視)

# AWS CLI でパラメータグループを更新する例
aws elasticache modify-cache-parameter-group \
  --cache-parameter-group-name my-redis-params \
  --parameter-name-values \
 ParameterName=activedefrag,ParameterValue=yes \
 ParameterName=active-defrag-threshold-lower,ParameterValue=10

CloudWatch監視項目

メトリクス警告しきい値説明
BytesUsedForCachemaxmemoryの80%超メモリ逼迫の早期検知
CurrItems急増パターンキー爆発の検知
SwapUsage0を超えた場合スワップ発生は深刻なメモリ不足
Evictions0を超えた場合データ損失が始まっているサイン

6-6. 接続プール枯渇 — Connection Pooling

問題

アプリサーバー台数が増えると、各サーバーからElastiCacheへのTCPコネクション数が線形に増加する。ElastiCacheノードのCurrConnectionsがmaxclientsに近づくと新規接続が拒否され、タイムアウトエラーが連発する。

ElastiCacheのデフォルト上限

インスタンスタイプmaxclients デフォルト
cache.t3.micro65000
cache.r7g.large65000
cache.r7g.4xlarge65000

maxclientsの上限は高いが、コネクションを使い捨て(毎リクエスト新規接続)する実装では接続ハンドシェイクのオーバーヘッドが積み重なりレイテンシが悪化する。コネクションプールを使って接続を再利用することが重要。

対策: アプリ側コネクションプール設定

import redis

# Python redis-py: アプリ起動時に一度だけConnectionPoolを生成し、
# モジュールレベルで共有することでコネクションを再利用する
_pool = redis.ConnectionPool(
 host='my-cluster.cache.amazonaws.com',
 port=6379,
 ssl=True,
 max_connections=50, # 1プロセスあたり最大50コネクション
 socket_timeout=1.0, # 読み取りタイムアウト 1秒
 socket_connect_timeout=0.5,
 retry_on_timeout=True,
)

def get_redis_client() -> redis.Redis:
 """アプリケーション全体で共有するRedisクライアントを返す。"""
 return redis.Redis(connection_pool=_pool)

Java (Lettuce) の場合: StatefulRedisConnectionはスレッドセーフなため、シングルトンとして保持してスレッドプールと組み合わせる。

Go (go-redis) の場合: redis.NewClient時にPoolSizeを設定し、MinIdleConnsを指定してウォームプールを維持する。

CloudWatch監視: CurrConnections

CurrConnectionsがmaxclientsの70%(約45500)を超えたらアラームを発報し、コネクションリークや接続プール設定の見直しをトリガーする。


6-7. セキュリティグループ誤設定によるタイムアウト

問題

ElastiCache(Redis/Memcached)への接続がタイムアウトエラーになる、または断続的に接続が失敗する。アプリログにはConnection timed outConnection refusedが記録される。

原因

VPC内のセキュリティグループ設定ミスが最多原因。ElastiCache側のSGにアプリ層からのinboundルールが未設定、またはアプリ層SGのoutboundが制限されているケースが典型。

確認手順

# 1. ElastiCacheクラスターに紐付くSG IDを確認
aws elasticache describe-cache-clusters \
  --cache-cluster-id my-redis-cluster \
  --query 'CacheClusters[].SecurityGroups[].SecurityGroupId' \
  --output text

# 2. 対象SGのinboundルールを確認 (Redis: 6379, Memcached: 11211)
aws ec2 describe-security-groups \
  --group-ids sg-xxxxxxxxxxxxxxxxx \
  --query 'SecurityGroups[].IpPermissions[]'

# 3. VPC Flow Logs で接続拒否 (REJECT) を確認
aws logs filter-log-events \
  --log-group-name /vpc/flowlogs \
  --filter-pattern 'REJECT' \
  --start-time $(date -d '1 hour ago' +%s000) | \
  grep ':6379 '

Terraform: セキュリティグループ設定例

# ElastiCache 用セキュリティグループ
resource "aws_security_group" "elasticache" {
  name  = "elasticache-sg"
  description = "ElastiCache Redis security group"
  vpc_id= var.vpc_id
}

# アプリ層 SG からの inbound を許可 (Redis port 6379)
resource "aws_vpc_security_group_ingress_rule" "elasticache_from_app" {
  security_group_id= aws_security_group.elasticache.id
  referenced_security_group_id = aws_security_group.app.id
  from_port  = 6379
  to_port = 6379
  ip_protocol= "tcp"
  description= "Allow Redis from app layer"
}

# アプリ層セキュリティグループ (参照用)
resource "aws_security_group" "app" {
  name  = "app-sg"
  description = "Application layer security group"
  vpc_id= var.vpc_id
}

# アプリ層 → ElastiCache への outbound を明示的に許可
resource "aws_vpc_security_group_egress_rule" "app_to_elasticache" {
  security_group_id= aws_security_group.app.id
  referenced_security_group_id = aws_security_group.elasticache.id
  from_port  = 6379
  to_port = 6379
  ip_protocol= "tcp"
  description= "Allow outbound to ElastiCache Redis"
}

Memcachedの場合: portを637911211に変更するのみ。設計パターンは同一。

本番デプロイ前 キャッシュチェックリスト

  • Multi-AZ + Auto-Failover: ElastiCacheのMulti-AZ設定とAuto-Failoverが有効になっているか確認(AWS コンソール→ ElastiCache→クラスター→設定タブ)
  • TTL階層設計レビュー: データ種別ごとのTTLが用途別TTL表に準拠しているか確認。固定TTL(86400 / 60)が残っていないかgrepで確認
  • Cache Stampede対策: ProbabilisticEarlyExpiration・MutexLock・TTL Jitterのいずれかが実装されているか確認
  • Warm-up戦略: デプロイスクリプトにCold Start対策(pre-population jobまたはCloudFrontWarm-up)が含まれているか確認
  • CloudWatch Alarm設定: Evictions・CPUUtilization・SwapUsage・CurrConnectionsの各アラームがしきい値つきで設定されているか確認
  • コネクションプール: アプリ側Redisクライアントがシングルトン+ConnectionPoolで実装されており、毎リクエスト新規接続していないことをコードレビューで確認
  • セキュリティグループ: ElastiCache SGのinboundにアプリ層SGからのport 6379(Redis)または11211(Memcached)が明示的に許可されているか確認
  • Active Defragmentation: パラメータグループでactivedefrag=yesが設定されているか確認。mem_fragmentation_ratioが1.5未満であることを事前測定

7. アンチパターン → 正解パターン変換演習 5問

ElastiCache・DAX・MemoryDB の本番運用でよく発生するアンチパターン5問を解説する。
各問でNG構成の問題点を分析し、正解パターンへの変換コードと設計指針を示す。
実際の事例に基づいた問題ばかりのため、本番設計の最終チェックリストとして活用されたい。


Q1. Single-AZ ElastiCache でフェイルオーバ失敗

アンチパターン

# NG: Single-AZ / Auto-Failover無効
resource "aws_elasticache_replication_group" "ng" {
  automatic_failover_enabled = false
  num_cache_clusters= 1
}

automatic_failover_enabled = false かつ num_cache_clusters = 1 の Primary-Only 構成では、
AZ障害またはノード障害発生時に自動昇格が発動しない。
ElastiCache に依存するアプリケーション全体が 手動復旧まで停止 し、
運用担当者がコンソールまたは CLI で手動フェイルオーバを実行するまでダウンタイムが継続する。

正解パターン

# OK: Multi-AZ / Auto-Failover有効
resource "aws_elasticache_replication_group" "ok" {
  replication_group_id = "prod-cache"
  description = "Production cache with Multi-AZ"
  automatic_failover_enabled = true
  multi_az_enabled  = true
  num_cache_clusters= 3
  availability_zones= ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  node_type= "cache.r7g.large"
  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
}

解説: Failover動作と本番必須設定

項目推奨値理由
automatic_failover_enabledtrueReplica → Primary 自動昇格 (10〜30秒)
multi_az_enabledtrueautomatic_failover_enabled と同時設定が必須
num_cache_clusters3以上Primary + Replica×2、2 AZ以上に分散
DNS TTL (ElastiCache)60秒SDK 側で再接続ロジックと組み合わせて使用

Failover 後の DNS 伝播遅延 (最大60秒) を考慮し、アプリ側で接続リトライを実装すること。
redis-pyretry_on_error / retry_on_timeout オプションを活用することで、
DNS 更新を待たずに自動再接続が可能になる。


Q2. TTL未設定でデータ陳腐化 + Origin過負荷

アンチパターン

# NG: TTL未指定 → メモリ枯渇 + 陳腐化データ返却
r.set("user:profile:1001", user_json)

# NG: 全データ固定1日 → 更新頻度無視で Cache Stampede 発生
r.setex("product:price:2001", 86400, price_json)

TTL未設定のキーはメモリが枯渇するまで Eviction されないため、陳腐化データが返却され続ける。
固定TTLでは、一定時刻に大量のキーが一斉期限切れになると Cache Stampede が発生し、
OriginDB (RDS/DynamoDB) へ膨大なリクエストが集中してカスケード障害につながる。

正解パターン: 用途別 TTL 階層設計 + Jitter

import redis
import random
import json

r = redis.Redis(
 host="your-elasticache-endpoint",
 port=6379,
 ssl=True,
 retry_on_timeout=True,
)

# 用途別 TTL 基準値 (秒)
BASE_TTL = {
 "static":  3600,# 静的データ (設定・テンプレート): 1時間
 "dynamic":  300,# 動的データ (プロファイル・在庫): 5分
 "session": 1800,# セッション: 30分
}

def cache_set(key: str, value: str, data_type: str = "dynamic") -> None:
 base = BASE_TTL.get(data_type, 300)
 jitter = random.randint(0, base // 10)  # ±10% ランダム揺らぎで Stampede 防止
 r.set(key, value, ex=base + jitter)

def cache_get_or_set(key: str, fetch_fn, data_type: str = "dynamic") -> dict:
 cached = r.get(key)
 if cached:
  return json.loads(cached)
 value = fetch_fn()  # OriginDB へのフォールバック
 cache_set(key, json.dumps(value), data_type=data_type)
 return value

# 使用例
cache_set("user:profile:1001",  user_json,data_type="dynamic")# TTL: 300〜330秒
cache_set("config:theme:main",  config_json, data_type="static") # TTL: 3600〜3960秒
cache_set("session:token:abc",  session_json, data_type="session")  # TTL: 1800〜1980秒

Jitter によりキー群の期限切れが分散され、Cache Stampede を根本的に抑制できる。
cache_get_or_set パターンと組み合わせることで、キャッシュミス時のOriginDB負荷も最小化できる。


Q3. キャッシュキー衝突で意図しない上書き

アンチパターン

# NG: テナント識別子なし → マルチテナント環境でデータ混入
r.set("user:1001", json.dumps({"name": "Alice", "plan": "premium"}))
# 別テナントの同一IDが上書き → Alice のデータが消える
r.set("user:1001", json.dumps({"name": "Bob","plan": "free"}))
result = r.get("user:1001")  # {"name": "Bob", "plan": "free"} が返る (Alice のデータ消失)

テナント・環境の識別子がないキー設計では、マルチテナント SaaS やステージング環境共有時に
異なるテナントの同一IDが衝突し、データ混入・情報漏洩 のリスクになる。
デバッグ困難な上、本番障害では顧客への説明責任が生じる深刻な問題となる。

正解パターン: Namespace prefix 設計

キー形式: {env}:{tenant}:{resource_type}:{id}

例: prod:tenant-a:user:1001stg:tenant-b:product:2001

import redis
import json
from typing import Optional, Any

class PrefixedCache:
 """テナント分離・環境分離を保証するキャッシュヘルパー"""

 def __init__(self, client: redis.Redis, env: str, tenant: str) -> None:
  self._r = client
  self._prefix = f"{env}:{tenant}"

 def _key(self, resource_type: str, resource_id: str) -> str:
  return f"{self._prefix}:{resource_type}:{resource_id}"

 def set(self, resource_type: str, resource_id: str,
value: Any, ex: int = 300) -> None:
  self._r.set(self._key(resource_type, resource_id), json.dumps(value), ex=ex)

 def get(self, resource_type: str, resource_id: str) -> Optional[Any]:
  raw = self._r.get(self._key(resource_type, resource_id))
  return json.loads(raw) if raw else None

 def delete(self, resource_type: str, resource_id: str) -> None:
  self._r.delete(self._key(resource_type, resource_id))

# テナント別インスタンス生成
cache_a = PrefixedCache(r, env="prod", tenant="tenant-a")
cache_b = PrefixedCache(r, env="prod", tenant="tenant-b")

cache_a.set("user", "1001", {"name": "Alice", "plan": "premium"})
# 実際のキー: prod:tenant-a:user:1001

cache_b.set("user", "1001", {"name": "Bob", "plan": "free"})
# 実際のキー: prod:tenant-b:user:1001  ← 異なるキーで衝突ゼロ

assert cache_a.get("user", "1001")["name"] == "Alice"  # 正常: データ混入なし
assert cache_b.get("user", "1001")["name"] == "Bob" # 正常: データ混入なし

Namespace prefix を強制するヘルパークラスを共通モジュールとして定義し、
全サービスで利用することでキー設計ミスを構造的に防ぐことができる。


Q4. DAX全テーブル適用で過剰コスト

アンチパターン

全 DynamoDB テーブル (30テーブル) を一律 DAX 経由でアクセスする設計。

コスト試算

項目備考
DAXクラスターdax.r5.large × 3ノード最小推奨構成
月額コスト約 $194/月us-east-1 オンデマンド価格
実際の高頻度テーブル3テーブル (全体の10%)残り27テーブルはほぼ低頻度
ROIマイナス大半のDAXコストが無駄

書き込み中心のテーブルや低頻度アクセステーブルにDAXを適用しても、
キャッシュヒット率が低いため コストだけが増加 する。

正解パターン: 分析 → 選択的適用

Step 1: CloudWatch で全テーブルの読み取り消費量を分析

# 過去7日間の全テーブル ConsumedReadCapacityUnits を一覧取得
for table in $(aws dynamodb list-tables --query 'TableNames[]' --output text); do
  total=$(aws cloudwatch get-metric-statistics \
 --namespace AWS/DynamoDB \
 --metric-name ConsumedReadCapacityUnits \
 --dimensions Name=TableName,Value="$table" \
 --start-time "$(date -u -d '-7 days' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \
 date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)" \
 --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
 --period 604800 \
 --statistics Sum \
 --query 'Datapoints[0].Sum' \
 --output text)
  echo "$total $table"
done | sort -rn | head -5

Step 2: Top-3 高頻度テーブルのみ DAX 適用、残りは DynamoDB 直接接続

import boto3
import amazondax
import json

# DAX クライアント (高頻度テーブル用)
dax = amazondax.AmazonDaxClient(
 endpoints=["prod-dax.xxxxxx.dax-clusters.ap-northeast-1.amazonaws.com:8111"]
)

# DynamoDB クライアント (低頻度テーブル用)
ddb = boto3.resource("dynamodb", region_name="ap-northeast-1")

# CloudWatch 分析結果: Top-3 高頻度テーブルのみ DAX 適用
DAX_TABLES = {"orders", "product_catalog", "user_sessions"}

def get_item(table_name: str, key: dict) -> dict:
 if table_name in DAX_TABLES:
  resp = dax.get_item(TableName=table_name, Key=key)
  return resp.get("Item", {})
 return ddb.Table(table_name).get_item(Key=key).get("Item", {})

Top-3テーブルへの選択的DAX適用でキャッシュヒット率を80%以上に維持しつつ、
コストを全テーブル適用比で 約70%削減 できる。
DAX の導入前後は CloudWatch CacheHits / CacheMisses メトリクスで効果を定量評価すること。


Q4 解答ポイント: DAX 選択的適用の原則

  • DAXの費用対効果はテーブルごとに異なる — 全適用は非推奨
  • まずCloudWatchで全テーブルのRCU消費量を計測し、上位3テーブルを特定してからDAX適用を検討する
  • DAX clientとDynamoDB clientを用途別に使い分けるコードパターンが本番設計の基本

Q5. Memcached on 大量データで OOM多発

アンチパターン

# Memcached の Eviction 多発を確認 (本番ノードで実行)
echo "stats" | nc your-memcached-endpoint.cfg.use1.cache.amazonaws.com 11211 \
  | grep -E "evictions|curr_items|bytes|limit_maxbytes"

# 出力例 (OOM状態):
# STAT limit_maxbytes 3254779904  (3.03GB  — ノード上限)
# STAT bytes 3251000000  (3.02GB  — ほぼ満杯)
# STAT curr_items 412800
# STAT evictions  284729 ← 大量Evictionで有効キャッシュが消滅

Memcached はインメモリ専用でメモリが上限に達すると LRU Eviction を実行する。
10GBのデータセットを cache.m7g.large (3.09GB) に詰め込むと、
有効キャッシュが次々と追い出されキャッシュヒット率が急落 する。
Memcached にはクラスターモード・レプリケーション・永続化がなく、
ノード障害でデータが全消失する点も本番リスクになる。

正解パターン Option A: Redis Cluster Mode + Data Tiering (cache.r6gd)

# OK: SSD階層化でメモリを拡張 — 冷データを自動的にSSDへオフロード
resource "aws_elasticache_replication_group" "redis_dt" {
  description = "Redis with Data Tiering"
  replication_group_id = "prod-cache-dt"
  node_type= "cache.r6gd.xlarge"
  num_cache_clusters= 3
  automatic_failover_enabled = true
  multi_az_enabled  = true
  data_tiering_enabled = true  # ホットデータ→DRAM / コールドデータ→SSD
  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
}

data_tiering_enabled = true で「ホットデータ → DRAM」「コールドデータ → SSD」に
自動階層化し、実効メモリ容量を大幅拡張できる。アクセスパターンに偏りがある場合に最適。

正解パターン Option B: Redis Cluster Mode Enabled + シャード追加

# OK: シャード数を増やして総メモリをスケールアウト
resource "aws_elasticache_replication_group" "redis_cme" {
  description = "Redis Cluster Mode Enabled"
  replication_group_id = "prod-cache-cme"
  node_type= "cache.r7g.large"
  num_node_groups= 4# 総メモリ ≒ 13.07GB × 4 = ~52GB
  replicas_per_node_group = 2
  automatic_failover_enabled = true
  multi_az_enabled  = true
  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
}

Memcached → Redis 移行の主要注意点

移行ポイント対応内容
データ型変換Memcached は文字列のみ → Redis の Hash/List/Set/SortedSet に再設計
TTL 挙動差異Memcached の exptime → Redis の EX / EXAT オプションに読み替え
接続文字列Memcached の Auto-Discovery endpoint → Redis の Cluster endpoint に変更
クライアントライブラリpymemcacheredis-py (cluster_mode=True) に変更
カットオーバBlue/Green デプロイで旧 Memcached → 新 Redis を段階的に切り替え

8. まとめ — Database三部作完結 + 全17軸クロスリンク

8-1. Database三部作完結サマリ

本シリーズ3冊で AWS データベース本番運用の全レイヤーを体系的に網羅した。

担当レイヤー主要カバー技術設計テーマ
Vol1主データストア層RDS / Aurora / DynamoDB永続化・整合性・スケーリング
Vol2災対と移行層DMS / Aurora Global / Streams / BackupDR・クロスリージョン・バックアップ
Vol3 (本記事)キャッシュ層ElastiCache / DAX / MemoryDBレイテンシ最小化・コスト最適化

三部作完結により、新規システム設計から障害対応・コスト最適化まで
Database 全域を一貫した設計思想で構築できる基盤が整った。

  • Vol1 で設計した主データストアの整合性モデルを土台に
  • Vol2 で策定した DR 方針・バックアップ戦略で耐障害性を担保し
  • Vol3 でキャッシュ層を追加してレイテンシを最小化する

この三層アーキテクチャが AWS データベース本番運用の完成形となる。

fig04: Database 三部作 完結アーキテクチャ — Vol1(主データストア) / Vol2(災対・移行) / Vol3(キャッシュ) 三層全体像

8-2. Vol3 設計3原則

Database Vol3 (Cache) 設計3原則

  • 原則1: Multi-AZ + Auto-Failover を本番デフォルト
    ElastiCache・MemoryDB とも automatic_failover_enabled = true / multi_az_enabled = true を必須設定とし、AZ障害時の自動昇格で RTO を最小化する。
  • 原則2: TTL階層設計 + Cache Stampede対策で OriginDB保護
    静的3600s / 動的300s / セッション1800s の3階層 + Jitter でキャッシュ一斉期限切れを防ぎ、OriginDB (RDS/DynamoDB) へのスパイクを遮断する。
  • 原則3: 用途別キャッシュ選定 (ElastiCache / MemoryDB / DAX の使い分け)
    Volatile キャッシュ → ElastiCache Redis、Durable (Redis API + AOF) → MemoryDB for Redis、DynamoDB 高速化 → DAX の3択で最適な永続性・コスト・レイテンシを実現する。

8-3. キャッシュ本番運用 落とし穴10選

キャッシュ本番運用 — 避けるべき落とし穴 10選

  1. Single-AZ ElastiCachemulti_az_enabled = true + Auto-Failover 必須
  2. TTL未設定 → 用途別TTL階層設計 (静的3600s / 動的300s / セッション1800s)
  3. Cache Stampede未対策 → Jitter付きTTL + Probabilistic Early Expiration
  4. Cold Start未考慮 → Warm-up戦略: デプロイ後に主要キーを事前ロード
  5. キャッシュキー衝突 → Namespace prefix設計: {env}:{tenant}:{type}:{id}
  6. DAX全テーブル適用 → CloudWatch分析でTop-3高頻度テーブルのみ選択的適用
  7. Memcached on 大量データ → Redis Cluster Mode / Data Tiering へ移行
  8. Backup未設定 → MemoryDB: Multi-AZ Snapshot / ElastiCache: 自動バックアップ有効化
  9. TLS/RBAC未設定transit_encryption_enabled = true + User/ACL を本番デフォルト有効化
  10. CloudWatch監視未設定 → Evictions / CPUUtilization / SwapUsage / CacheHitRate をアラーム化

8-4. 全17軸 双方向ハブナビゲーション

AWS本番運用 全17軸 + 縦深シリーズ (Database Vol3 起点)

関連: ML/AI本番運用 Vol2 を読む