0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DynamoDB 体系的学習ガイド

0
Last updated at Posted at 2025-12-22

目次

  1. DynamoDBの基本概念
  2. データモデリングの基礎
  3. キーの設計原則
  4. アクセスパターンとクエリ
  5. キャパシティ管理
  6. 実践的な設計例

1. DynamoDBの基本概念

1.1 NoSQLデータベースとしての特徴

RDBとの根本的な違い:

  • 結合(JOIN)が存在しない: 複数テーブルを結合する概念がないため、必要なデータは1回のクエリで取得できるよう設計する
  • 正規化不要: データの重複を許容し、読み取りパフォーマンスを優先
  • スキーマレス: 項目ごとに異なる属性を持てる(ただしPrimary Keyは必須)

1.2 フルマネージドサービスの利点

  • インフラ管理不要: サーバー管理、パッチ適用、バックアップが自動化
  • 自動スケーリング: トラフィック増加時に自動でキャパシティ調整
  • 高可用性: 複数のAZに自動レプリケーション

1.3 DynamoDBが適しているユースケース

向いている場面:

  • セッション管理(認証トークン、ユーザーセッション)
  • リアルタイムデータ(チャット、通知)
  • 時系列データ(ログ、IoTセンサーデータ)
  • キーバリューストア的な用途

向いていない場面:

  • 複雑な集計クエリ(SUMやAVERAGE等)
  • 頻繁なスキーマ変更が必要
  • アドホックな分析クエリ

1.4 RDBとの使い分けの判断軸

  • アクセスパターンが事前に明確 → DynamoDB
  • 複雑なリレーションと集計 → RDB
  • スケーラビリティ最優先 → DynamoDB
  • データ整合性最優先 → RDB

2. データモデリングの基礎

2.1 Single Table Design(単一テーブル設計)

RDBとの最大の違い:

  • RDB: エンティティごとに別テーブル(Users、Meetings、Comments等)
  • DynamoDB: 1つのテーブルに全エンティティを格納

理由: JOINが存在しないため、関連データを1回のクエリで取得するには同一テーブルに配置する必要がある

具体例(会議アプリケーション):

テーブル名: AppTable
---------------------------------
PK: USER#123          SK: PROFILE
PK: USER#123          SK: MEETING#456
PK: MEETING#456       SK: METADATA
PK: MEETING#456       SK: COMMENT#789

2.2 テーブル構造の3要素

1. Primary Key(必須)

  • テーブル内で項目を一意に識別
  • Partition KeyまたはPartition Key + Sort Keyの組み合わせ

2. 属性(Attribute)

  • 項目に含まれるデータフィールド
  • 項目ごとに異なる属性セットを持てる(スキーマレスの利点)

3. 項目(Item)

  • RDBの「行」に相当
  • Primary Keyと0個以上の属性で構成

2.3 Primary Keyの2つのパターン

パターンA: Partition Keyのみ(Simple Primary Key)

PK: USER#123
属性: name, email, createdAt
  • シンプルなキーバリューストア
  • 1つのPKに対して1つの項目のみ

パターンB: Partition Key + Sort Key(Composite Primary Key)

PK: USER#123    SK: PROFILE
PK: USER#123    SK: MEETING#456
PK: USER#123    SK: MEETING#457
  • 1つのPKに対して複数の項目を格納可能
  • Sort Keyで範囲検索やソート順を制御

2.4 Single Table Designの適用原則

適用すべきケース:
関連データをビジネスロジック上で一緒に取得する必要がある場合

適用すべきでないケース:

  • アクセスパターンが完全に独立(例: トークンとアプリデータ)
  • セキュリティ境界が異なる
  • TTL等の管理ポリシーが異なる

3. キーの設計原則

3.1 Partition Keyの設計思想

役割: データを物理的に分散させる基準

重要な原則:

  • 均等分散: 特定のPartition Keyに負荷が集中しないようにする(ホットパーティション問題の回避)
  • アクセスパターン優先: 「どのように検索するか」から逆算して設計

3.2 Sort Keyの設計思想

役割: 同一Partition Key内のデータをソート・範囲検索

活用パターン:

  • 時系列データ: 2024-12-22T10:30:00Z
  • 階層構造: MEETING#456#COMMENT#789
  • バージョン管理: v1, v2, v3

3.3 複合キー戦略: プレフィックスパターン

なぜプレフィックスを付けるのか:

良い例: USER#123, MEETING#456
悪い例: 123, 456

理由:

  1. エンティティタイプの明示: コードの可読性向上
  2. キーの衝突回避: 異なるエンティティで同じID(123)が存在しても区別可能
  3. デバッグの容易性: DynamoDB管理コンソールで一目でデータ構造を把握

3.4 Composite Primary Keyの仕組み

重要な理解:

  • Partition KeyとSort Keyは別々のカラム(属性)
  • USER#123MEETING#456のように連結されるわけではない
Primary Key = {
  Partition Key: "USER#123",
  Sort Key: "MEETING#456"
}
(2つの属性の組み合わせ)

一意性の保証:

  • (Partition Key, Sort Key)ペアが一意である必要がある
  • Partition Keyが同じでも、Sort Keyが異なれば別の項目
  • 同じペアで書き込むと既存項目が上書きされる

3.5 マルチテナント設計

推奨パターン:

PK: tenant1#MEETING#meeting1
SK: METADATA

利点:

  • テナント間でデータが物理的に分離
  • クエリ時にテナントIDを含めることで誤った横断アクセスを防止
  • セキュリティ境界が明確

Partition Keyの粒度:

粗い粒度: PK: tenant1 (非推奨: ホットパーティション化)
細かい粒度: PK: tenant1#user_abc123 (推奨: 分散性が高い)

4. アクセスパターンとクエリ

4.1 Query vs Scan の違い

Query(効率的):

  • Partition Keyを必ず指定
  • 特定のパーティションのみスキャン
  • Sort Keyで範囲指定可能
  • コスト: 取得した項目分のRCUのみ消費

Scan(非効率):

  • テーブル全体をスキャン
  • フィルタは取得後に適用
  • コスト: テーブル全体のRCUを消費(フィルタで絞っても)

4.2 基本的なQuery操作

TypeScriptでの実装例:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: "ap-northeast-1" });
const docClient = DynamoDBDocumentClient.from(client);

// 特定会議の全データ取得
const command = new QueryCommand({
  TableName: "AppTable",
  KeyConditionExpression: "PK = :pk",
  ExpressionAttributeValues: {
    ":pk": "tenant1#MEETING#meeting1"
  }
});

const result = await docClient.send(command);
console.log(result.Items);

4.3 Sort Keyを使った条件指定

begins_with(前方一致):

// 特定会議の意見のみ取得
QueryCommand({
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk_prefix)",
  ExpressionAttributeValues: {
    ":pk": "tenant1#MEETING#meeting1",
    ":sk_prefix": "OPINION#"
  }
})

BETWEEN(範囲指定):

// 特定期間の意見のみ取得
QueryCommand({
  KeyConditionExpression: "PK = :pk AND SK BETWEEN :start AND :end",
  ExpressionAttributeValues: {
    ":pk": "tenant1#MEETING#meeting1",
    ":start": "OPINION#2025-12-18T10:00:00.000Z",
    ":end": "OPINION#2025-12-18T12:00:00.000Z"
  }
})

ソート順の制御:

QueryCommand({
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk_prefix)",
  ExpressionAttributeValues: {
    ":pk": "tenant1#MEETING#meeting1",
    ":sk_prefix": "OPINION#"
  },
  ScanIndexForward: false // false = 降順(新しい順), true = 昇順(古い順)
})

4.4 GetItem vs Query の使い分け

GetItem(1項目を直接取得):

import { GetCommand } from "@aws-sdk/lib-dynamodb";

const command = new GetCommand({
  TableName: "AuthTokens",
  Key: {
    userId: "tenant1#user_abc123",
    tokenType: "id_token"
  }
});

const result = await docClient.send(command);
console.log(result.Item);

使い分けの基準:

  • Primary Key(PK+SK)が完全に分かっている → GetItem(最速)
  • PKは分かるがSKで範囲検索したい → Query
  • 複数項目を取得したい → Query

4.5 フィルタ式の使い方と注意点

Query後にさらに絞り込み:

QueryCommand({
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk_prefix)",
  FilterExpression: "role = :role", // Query後にフィルタ
  ExpressionAttributeValues: {
    ":pk": "tenant1#MEETING#meeting1",
    ":sk_prefix": "PARTICIPANT#",
    ":role": "moderator"
  }
})

重要な注意点:

  • FilterExpressionはQuery後に適用される
  • RCUはフィルタ前の全項目で消費される
  • 頻繁にフィルタするなら、GSIまたはSort Keyの設計を見直す

4.6 GSI(Global Secondary Index)の活用

GSIの基本概念

GSIとは: クエリしやすくするために、自分で追加の検索用キーを用意する仕組み

重要な理解:

  • GSI用に新しい属性を追加する必要がある
  • Base TableのPK/SKとは別のキーでインデックスを構築

GSIが必要なパターン

パターン1: 逆引き検索

Primary Key: PK: tenant1#MEETING#meeting1, SK: PARTICIPANT#user_abc123
実現できないクエリ: 「ユーザーが参加している全会議の一覧」

GSI設計:
PK: tenant1#USER#user_abc123
SK: MEETING#meeting1#2025-12-18T10:00:00Z

パターン2: 時系列検索(会議横断)

Primary Key: PK: tenant1#MEETING#meeting1, SK: OPINION#timestamp
実現できないクエリ: 「テナント内の全会議から最新の意見を取得」

GSI設計:
PK: tenant1#OPINION
SK: timestamp#meeting1#opinion-id

パターン3: ステータスフィルタ

Primary Key: PK: tenant1#USER#user_abc123, SK: PROFILE
実現できないクエリ: 「テナント内のアクティブなユーザー一覧」

GSI設計:
PK: tenant1#ACTIVE_USERS
SK: userId

GSIの実装方法

Base Table書き込み時にGSI用の属性を追加:

import { PutCommand } from "@aws-sdk/lib-dynamodb";

const command = new PutCommand({
  TableName: "AppTable",
  Item: {
    // Base TableのPrimary Key
    PK: "tenant1#MEETING#meeting1",
    SK: "PARTICIPANT#user_abc123",
    
    // GSI用のキー(自分で追加)
    GSI_PK: "tenant1#USER#user_abc123",
    GSI_SK: "MEETING#meeting1#2025-12-18T10:00:00Z",
    
    // 通常の属性
    joinedAt: "2025-12-18T10:00:00Z",
    role: "moderator"
  }
});

await docClient.send(command);

GSIを使ったQuery:

const command = new QueryCommand({
  TableName: "AppTable",
  IndexName: "UserMeetingsIndex", // GSI名を指定
  KeyConditionExpression: "GSI_PK = :gsi_pk AND begins_with(GSI_SK, :gsi_sk_prefix)",
  ExpressionAttributeValues: {
    ":gsi_pk": "tenant1#USER#user_abc123",
    ":gsi_sk_prefix": "MEETING#"
  },
  ScanIndexForward: false // 最新参加順
});

const result = await docClient.send(command);
console.log(result.Items);

GSIのコスト注意点

  • GSIは追加のストレージとキャパシティを消費
  • 書き込み時にBase TableとGSI両方に書き込み(WCU2倍)
  • 本当に必要なアクセスパターンか慎重に検討

5. キャパシティ管理

5.1 キャパシティの2つのモード

オンデマンドモード(推奨: スタートアップ・不規則な負荷):

  • 使った分だけ課金
  • 自動スケーリング
  • キャパシティ計画不要
  • コスト: やや高め(プロビジョニングの約5倍)

プロビジョニングモード(推奨: 安定した負荷・コスト最適化):

  • RCU/WCUを事前に設定
  • 予測可能なコスト
  • Auto Scalingで自動調整可能
  • コスト: 安い(ただし設定ミスでスロットリングのリスク)

5.2 RCU(Read Capacity Unit)の計算

1 RCU = 以下のいずれか:

  • 最大4KBの項目を強力な整合性読み込みで1回/秒
  • 最大4KBの項目を結果整合性読み込みで2回/秒

計算例:

シナリオ: 8KBの項目を強力な整合性で100回/秒読み込む

計算:
項目サイズ: 8KB ÷ 4KB = 2 RCU/項目
必要RCU: 2 × 100 = 200 RCU

結果整合性の場合:

8KBの項目を結果整合性で100回/秒読み込む

計算:
項目サイズ: 8KB ÷ 4KB = 2
結果整合性: 2 ÷ 2 = 1 RCU/項目
必要RCU: 1 × 100 = 100 RCU

5.3 WCU(Write Capacity Unit)の計算

1 WCU = 最大1KBの項目を1回/秒書き込み

計算例:

シナリオ: 3KBの項目を50回/秒書き込む

計算:
項目サイズ: 3KB → 切り上げで3 WCU/項目
必要WCU: 3 × 50 = 150 WCU

5.4 GSIのキャパシティ

重要: GSIは独立したキャパシティを消費

Base Tableに1項目書き込み + GSI 2個の場合:

Base Table: 3KB → 3 WCU
GSI1: 3KB → 3 WCU
GSI2: 3KB → 3 WCU
合計: 9 WCU消費

5.5 コスト最適化のポイント

1. 結果整合性読み込みを活用:

QueryCommand({
  TableName: "AppTable",
  KeyConditionExpression: "PK = :pk",
  ExpressionAttributeValues: { ":pk": "tenant1#MEETING#meeting1" },
  ConsistentRead: false // 結果整合性(RCUが半分)
})

2. バッチ操作を使う:

import { BatchGetCommand, BatchWriteCommand } from "@aws-sdk/lib-dynamodb";

// 最大100項目を1回で取得
const batchGetCommand = new BatchGetCommand({
  RequestItems: {
    "AppTable": {
      Keys: [
        { PK: "tenant1#MEETING#meeting1", SK: "METADATA" },
        { PK: "tenant1#MEETING#meeting2", SK: "METADATA" }
      ]
    }
  }
});

// 最大25項目を1回で書き込み
const batchWriteCommand = new BatchWriteCommand({
  RequestItems: {
    "AppTable": [
      {
        PutRequest: {
          Item: { PK: "tenant1#MEETING#meeting1", SK: "PARTICIPANT#user1" }
        }
      }
    ]
  }
});

3. TTL機能でストレージ削減:

// トークンテーブルでの設定
{
  userId: "tenant1#user_abc123",
  tokenType: "id_token",
  token: "eyJhbGc...",
  expiresAt: 1703260800,
  ttl: 1703260800  // ← この時刻になると自動削除(無料)
}

4. オンデマンド vs プロビジョニングの判断:

  • 開発初期・トラフィック予測困難 → オンデマンド
  • 安定稼働・コスト削減優先 → プロビジョニング + Auto Scaling

5.6 スタート時の推奨設定

AuthTokens: オンデマンドモード
AppTable: オンデマンドモード
GSI: オンデマンドモード(Base Tableと同じモード必須)

理由:
- 初期はトラフィックパターンが不明
- キャパシティ不足でサービス停止のリスク回避
- データ蓄積後にプロビジョニングへ移行検討

6. 実践的な設計例

6.1 トークンテーブルの設計

テーブル定義:

テーブル名: AuthTokens
Partition Key: userId (String)
Sort Key: tokenType (String)
TTL属性: ttl
モード: オンデマンド

データ構造:

{
  userId: "tenant1#user_abc123",      // Partition Key
  tokenType: "id_token",               // Sort Key
  token: "eyJhbGc...",                 // 暗号化済みトークン
  expiresAt: 1703260800,               // Unix timestamp
  createdAt: 1703174400,
  ttl: 1703260800                      // DynamoDB TTL用
}

{
  userId: "tenant1#user_abc123",
  tokenType: "refresh_token",
  token: "eyJhbGc...",
  expiresAt: 1703347200,
  createdAt: 1703174400,
  ttl: 1703347200
}

アクセスパターン:

// 1. ユーザーの全トークン取得
Query(userId = "tenant1#user_abc123")

// 2. 特定トークンのみ取得
Query(userId = "tenant1#user_abc123", tokenType = "id_token")

// 3. 特定トークンを直接取得
GetItem(userId = "tenant1#user_abc123", tokenType = "id_token")

// 4. トークン削除(ログアウト)
DeleteItem(userId = "tenant1#user_abc123", tokenType = "id_token")

設計のポイント:

  • マルチテナント対応でtenant#user形式のPartition Key
  • TTL機能で期限切れトークンを自動削除
  • GSI不要(トークン検証は常にuserIdが分かっている)

6.2 会議アプリケーションテーブルの設計

テーブル定義:

テーブル名: AppTable
Partition Key: PK (String)
Sort Key: SK (String)
モード: オンデマンド

GSI: UserMeetingsIndex
  Partition Key: GSI_PK (String)
  Sort Key: GSI_SK (String)

データ構造:

// 会議メタデータ
{
  PK: "tenant1#MEETING#meeting1",
  SK: "METADATA",
  title: "四半期振り返り会議",
  description: "Q4の振り返りと改善点の議論",
  createdAt: "2025-12-18T09:00:00Z",
  status: "active"
}

// 会議の意見
{
  PK: "tenant1#MEETING#meeting1",
  SK: "OPINION#2025-12-18T10:25:09.584Z#opinion-1766053509584",
  content: "集団思考の傾向が見られるため、デビルズアドボケートの導入を提案します",
  userId: "tenant1#USER#user_abc123",
  userName: "西島康孝",
  createdAt: "2025-12-18T10:25:09.584Z"
}

// 会議の参加者
{
  PK: "tenant1#MEETING#meeting1",
  SK: "PARTICIPANT#user_abc123",
  GSI_PK: "tenant1#USER#user_abc123",  // GSI用
  GSI_SK: "MEETING#meeting1#2025-12-18T09:00:00Z",  // GSI用
  userId: "tenant1#USER#user_abc123",
  userName: "西島康孝",
  role: "moderator",
  joinedAt: "2025-12-18T09:00:00Z"
}

アクセスパターン:

// 1. 会議の全データ取得(メタデータ + 意見 + 参加者)
Query(PK = "tenant1#MEETING#meeting1")

// 2. 会議の意見のみ取得(時系列順)
Query(
  PK = "tenant1#MEETING#meeting1",
  SK begins_with "OPINION#"
)

// 3. 会議の参加者のみ取得
Query(
  PK = "tenant1#MEETING#meeting1",
  SK begins_with "PARTICIPANT#"
)

// 4. 特定期間の意見のみ取得
Query(
  PK = "tenant1#MEETING#meeting1",
  SK BETWEEN "OPINION#2025-12-18T10:00" AND "OPINION#2025-12-18T11:00"
)

// 5. ユーザーの参加会議一覧(GSI使用)
Query(
  IndexName = "UserMeetingsIndex",
  GSI_PK = "tenant1#USER#user_abc123",
  GSI_SK begins_with "MEETING#",
  ScanIndexForward = false  // 最新参加順
)

設計のポイント:

  • Single Table Designで関連データを1回のQueryで取得
  • Sort Keyに時刻を含めることで時系列ソートとGSI不要化
  • マルチテナント対応で全てのPKにtenant#プレフィックス
  • ユーザー視点の検索にはGSI(UserMeetingsIndex)を活用

6.3 Railsでの実装例

# app/services/dynamodb/meeting_service.rb
class DynamoDB::MeetingService
  def initialize
    @client = Aws::DynamoDB::Client.new(region: 'ap-northeast-1')
  end
  
  # 会議の全データを取得
  def get_meeting(tenant_id, meeting_id)
    response = @client.query(
      table_name: 'AppTable',
      key_condition_expression: 'PK = :pk',
      expression_attribute_values: {
        ':pk' => "#{tenant_id}#MEETING##{meeting_id}"
      }
    )
    
    format_meeting_data(response.items)
  end
  
  # 会議に意見を追加
  def add_opinion(tenant_id, meeting_id, user_id, content)
    timestamp = Time.now.utc.iso8601(3)
    opinion_id = "opinion-#{Time.now.to_i * 1000}"
    
    @client.put_item(
      table_name: 'AppTable',
      item: {
        'PK' => "#{tenant_id}#MEETING##{meeting_id}",
        'SK' => "OPINION##{timestamp}##{opinion_id}",
        'content' => content,
        'userId' => user_id,
        'createdAt' => timestamp
      }
    )
  end
  
  # ユーザーの参加会議一覧を取得(GSI使用)
  def get_user_meetings(tenant_id, user_id)
    response = @client.query(
      table_name: 'AppTable',
      index_name: 'UserMeetingsIndex',
      key_condition_expression: 'GSI_PK = :gsi_pk AND begins_with(GSI_SK, :gsi_sk_prefix)',
      expression_attribute_values: {
        ':gsi_pk' => "#{tenant_id}#USER##{user_id}",
        ':gsi_sk_prefix' => 'MEETING#'
      },
      scan_index_forward: false  # 最新参加順
    )
    
    response.items
  end
  
  private
  
  def format_meeting_data(items)
    {
      metadata: items.find { |item| item['SK'] == 'METADATA' },
      opinions: items.select { |item| item['SK'].start_with?('OPINION#') }
                     .sort_by { |item| item['SK'] },
      participants: items.select { |item| item['SK'].start_with?('PARTICIPANT#') }
    }
  end
end

6.4 Lambda(DynamoDB Streams)での実装例

import { DynamoDBStreamEvent, DynamoDBRecord } from 'aws-lambda';

// 項目の変更を検知して処理
export const handler = async (event: DynamoDBStreamEvent) => {
  for (const record of event.Records) {
    await processRecord(record);
  }
};

async function processRecord(record: DynamoDBRecord) {
  const eventName = record.eventName; // INSERT, MODIFY, REMOVE
  const newItem = record.dynamodb?.NewImage;
  const oldItem = record.dynamodb?.OldImage;
  
  if (!newItem) return;
  
  const pk = newItem.PK?.S;
  const sk = newItem.SK?.S;
  
  // 新しい意見が投稿された場合
  if (eventName === 'INSERT' && sk?.startsWith('OPINION#')) {
    await notifyNewOpinion({
      meetingId: pk,
      userId: newItem.userId?.S,
      content: newItem.content?.S
    });
  }
  
  // 参加者が追加された場合
  if (eventName === 'INSERT' && sk?.startsWith('PARTICIPANT#')) {
    await notifyNewParticipant({
      meetingId: pk,
      userId: newItem.userId?.S,
      role: newItem.role?.S
    });
  }
}

async function notifyNewOpinion(data: any) {
  // SNS、SES、WebSocket等で通知
  console.log('New opinion posted:', data);
}

async function notifyNewParticipant(data: any) {
  // 参加者追加の通知処理
  console.log('New participant joined:', data);
}

付録: よくある質問と回答

Q1: いつGSIを作成すべきか?

A: Base TableのPrimary Keyでは実現できないアクセスパターンがある場合のみ。例:

  • 「ユーザーの参加会議一覧」(会議→ユーザーの逆引き)
  • 「全会議の最新意見」(会議横断の時系列検索)
  • 「アクティブなユーザー一覧」(ステータスフィルタ)

Sort Keyに時刻を含めることで、多くの時系列検索はGSI不要で実現できる。

Q2: Partition Keyにどこまで情報を詰め込むべきか?

A: アクセスパターンと分散性のバランスで判断:

  • tenant1: 粗すぎる(ホットパーティション化)
  • tenant1#USER#user_abc123: ちょうど良い(推奨)
  • tenant1#USER#user_abc123#SESSION#session_xyz: 細かすぎる(管理が煩雑)

基本は「エンティティタイプ + ID」レベルが最適。

Q3: トークンテーブルとアプリテーブルを分ける理由は?

A: 以下の理由で分離すべき:

  1. アクセスパターンの独立性(トークン検証とアプリ機能は別)
  2. セキュリティ境界の分離(暗号化・アクセス制御が異なる)
  3. TTL管理ポリシーの違い
  4. キャパシティ最適化(トークン検証は高頻度)

Q4: 結果整合性と強力な整合性はどう使い分ける?

A:

  • 結果整合性(デフォルト): ダッシュボード表示、一覧取得等、若干の遅延が許容できる場合
  • 強力な整合性: 金融取引、在庫管理、認証トークン検証等、最新データが必須の場合

迷ったら結果整合性で開始し、問題があれば強力な整合性に変更。

Q5: Scanは絶対に使ってはいけない?

A: 以下の場合は許容される:

  • 管理画面でのデータエクスポート(低頻度)
  • バッチ処理でのデータ移行
  • 開発環境でのデバッグ

本番環境の通常フローでは避けるべき。どうしても必要ならGSIの追加を検討。


まとめ: DynamoDB設計の黄金ルール

  1. アクセスパターンを先に定義する: データ構造ではなく、どう検索するかから設計
  2. Primary Keyで80%解決する: GSIは必要最小限に
  3. Sort Keyを賢く使う: 時刻やプレフィックスで多様なクエリに対応
  4. マルチテナントはPartition Keyに含める: セキュリティと分散性を両立
  5. オンデマンドモードでスタート: トラフィックパターン確立後にプロビジョニングへ
  6. TTLで自動削除: ストレージコスト削減
  7. 結果整合性を活用: RCUを半減
  8. Scanは避ける: どうしても必要ならGSI追加

作成日: 2025-12-22
最終更新: 2025-12-22

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?