SJ blog
database
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

DynamoDBパーティションキー設計 — ホットパーティション・カーディナリティ・アクセスパターン

DynamoDBのパーティションキー設計の原則、ホットパーティション問題の発生条件と対策、アダプティブキャパシティ、Writeシャーディングパターン、GSIを活用した設計を解説。

一言結論

ホットパーティションを防ぐには高カーディナリティのキーを選び、書き込みが集中するケースはシャーディング(乱数サフィックス)で分散させることが基本戦略であり、アダプティブキャパシティはあくまで緩和策であって根本的な設計問題の解決にはならない。

DynamoDBのデータ分散の仕組み

DynamoDBはパーティションキーのハッシュ値によってデータを複数のパーティションに分散する。

データ → パーティションキーのハッシュ計算 → パーティション選択 → ストレージ

1パーティションの容量: 10GB
1パーティションのスループット上限: 3,000 RCU / 1,000 WCU

ホットパーティション問題

アクセスが特定のパーティションキーに集中すると、そのパーティションのスループット上限に達し、ProvisionedThroughputExceededException が発生する。

❌ 悪いパーティションキーの例:
  status(active/inactive の2値)
    → 全レコードが2パーティションに集中
  
  date(YYYY-MM-DD形式)
    → 当日のアクセスが今日のパーティションに集中(ホット日付)
  
  user_type(admin/member/guest の3値)
    → ユーザータイプに偏りがある場合にホットパーティション
✅ 良いパーティションキーの例:
  userId(UUID, 高カーディナリティ)
    → ユーザーごとに分散
  
  deviceId(IoTデバイスID)
    → デバイスごとに分散
  
  orderId(自動生成UUID)
    → 注文ごとに分散

アダプティブキャパシティ

DynamoDBは自動的にホットパーティションを検出し、一時的にキャパシティを増やす機能(Adaptive Capacity)を持つ。ただしこれは緩和策であり、根本的な設計解決ではない。

On-demand モード:
  → 需要に応じて自動スケール(アダプティブキャパシティに加えて)
  → 突発的なトラフィックに対応

Provisioned モード + Auto Scaling:
  → CloudWatchメトリクスに基づいて自動調整
  → アダプティブキャパシティが補助

ホットパーティション対策: シャーディング(Suffix追加)

書き込みが集中するキーに乱数サフィックスを追加して分散する。

import random
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('GameLeaderboard')

# シャード数(書き込みのホットパーティション対策)
SHARD_COUNT = 10

def write_score(user_id: str, score: int):
    # パーティションキーにシャードサフィックスを追加
    shard = random.randint(0, SHARD_COUNT - 1)
    sharded_key = f"{user_id}_shard{shard}"
    
    table.put_item(Item={
        'pk': sharded_key,
        'score': score,
        'timestamp': int(time.time())
    })

def read_user_scores(user_id: str):
    # 全シャードから読み取って集計
    scores = []
    for shard in range(SHARD_COUNT):
        sharded_key = f"{user_id}_shard{shard}"
        response = table.get_item(Key={'pk': sharded_key})
        if 'Item' in response:
            scores.append(response['Item'])
    return scores

複合主キーの設計

パーティションキー(PK)とソートキー(SK)の組み合わせでデータモデルを設計する。

# 1テーブルデザイン(Single Table Design)
# ユーザー情報、注文、注文アイテムを1テーブルに

# ユーザー情報
{
  "PK": "USER#user123",
  "SK": "PROFILE",
  "name": "田中太郎",
  "email": "tanaka@example.com"
}

# ユーザーの注文
{
  "PK": "USER#user123",
  "SK": "ORDER#2026-04-08#order456",
  "total": 5000,
  "status": "completed"
}

# 注文アイテム
{
  "PK": "ORDER#order456",
  "SK": "ITEM#item789",
  "quantity": 2,
  "price": 2500
}

このパターンでは:

  • PK=USER#user123 かつ SK begins_with ORDER でユーザーの注文一覧を取得
  • PK=ORDER#order456 かつ SK begins_with ITEM で注文アイテムを取得

時系列データのキー設計

# ❌ 悪い例: 日付をパーティションキーに使う
{
  "date": "2026-04-08",   ← ホットパーティション
  "timestamp": 1712534400,
  "event_type": "click"
}

# ✅ 良い例: 高カーディナリティのIDをPKにして時刻をSKに
{
  "deviceId": "device-uuid-xyz",   ← 高カーディナリティ
  "timestamp": 1712534400,          ← ソートキーで時系列順
  "event_type": "click"
}

# クエリ例: 特定デバイスの時系列データを取得
table.query(
    KeyConditionExpression=Key('deviceId').eq('device-uuid-xyz') &
                           Key('timestamp').between(start_time, end_time)
)

DynamoDB Streams + Lambdaでの集計

ホットパーティションを避けつつリアルタイム集計をする場合、書き込みを分散してStreamで集計する。

# DynamoDB Streamsで変更を検知して集計テーブルに反映
def handler(event, context):
    for record in event['Records']:
        if record['eventName'] == 'INSERT':
            new_item = record['dynamodb']['NewImage']
            # 集計ロジック
            update_aggregation(new_item)

試験頻出ポイント

シナリオ回答
ProvisionedThroughputExceededException が頻発ホットパーティション問題、PKの見直し
On-demandとProvisioned、どちらを使うか予測不能なトラフィックはOn-demand
時系列データで日付をPKにした場合ホットパーティション問題が発生
特定ユーザーへのアクセス集中対策シャーディング(乱数サフィックス)
DynamoDBの1パーティションあたりのWCU上限1,000 WCU

まとめ

DynamoDBのパフォーマンスはパーティションキーの設計で決まる。高カーディナリティのキーを選択し、書き込みが集中するケースではシャーディングで分散する。On-demandモードはホットパーティションのアダプティブキャパシティが有効で、予測不能なワークロードに適している。