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 sort key設計の一般化パターン|AI実務ノート 編集部

0
Last updated at Posted at 2026-01-03

1. 結論(この記事で得られること)

この記事では、DynamoDBのsort key設計で実務レベルでよく使われる一般化パターンを体系的に整理します。

具体的には:

  • 複合sort keyの設計原則(区切り文字、階層表現、時系列の扱い)
  • 実務でよく失敗する5つのアンチパターンとその回避方法
  • AI(Claude/GPT)を使った設計レビューの自動化手法
  • 本番環境で安全にスキーマ変更する手順とロールバック戦略

昔、私もEコマース案件でsort keyを「ITEM#」みたいに設計して、後から「商品カテゴリ × 登録日時でソートしたい」という要件が来て、全テーブルスキャンするLambdaを夜な夜な書いた経験があります。あの悪夢を皆さんには味わってほしくない。

2. 前提(環境・読者層)

想定読者

  • DynamoDBの基本(partition key / sort key / GSI)は理解している
  • 実際のプロジェクトでテーブル設計を任されているor任される予定
  • 「なんとなく動く」から「拡張性のある設計」にレベルアップしたい

技術環境

  • AWS DynamoDB(従量課金 or オンデマンドモード)
  • 言語例:TypeScript(Node.js)、Python
  • AWS SDK v3前提(v2でも考え方は同じ)

3. Before:よくあるつまずきポイント

実務で見てきたsort key設計の失敗パターンを3つ紹介します。

失敗①:sort keyに何も考えずUUIDを入れる

// ❌ Bad
{
  PK: "USER#123",
  SK: "c5e1a8f3-4d2b-4e8a-9c7f-1a2b3c4d5e6f",  // ランダムUUID
  createdAt: "2025-01-15T10:00:00Z"
}

何が問題か:

  • sort keyでソートする意味がない(UUIDはランダム)
  • 時系列順に取得したいときに「createdAt」でフィルタ → 全件スキャン

失敗②:区切り文字を統一しない

// ❌ Bad(案件内で混在)
SK: "METADATA#2025-01-15"       // `#`で区切り
SK: "ORDER_2025-01-15"          // `_`で区切り
SK: "ITEM::electronics"         // `::`で区切り

何が問題か:

  • チーム内で分割ロジックがバラバラになる
  • パース処理が属人化 → 障害時に調査コストが跳ね上がる

失敗③:将来のクエリパターンを考慮しない

// ❌ Bad:最初は「ユーザーごとの注文一覧」だけ想定
{
  PK: "USER#123",
  SK: "ORDER#456"
}
 
// 後から「特定期間の注文だけ取得したい」→ できない
// 後から「ステータスごとに絞りたい」→ GSI追加 → コスト増

この設計だと、Query条件に柔軟性がなく、後からGSIを乱立させることになります。

4. After:基本的な解決パターン

実務でよく使われる一般化パターンを3つ紹介します。

パターン①:時系列 prefix 型(最頻出)

// ✅ Good
{
  PK: "USER#123",
  SK: "ORDER#2025-01-15T10:30:00Z#456",  // タイプ + ISO8601 + ID
  status: "shipped",
  totalAmount: 15000
}

メリット:

  • 「begins_with("ORDER#2025-01")」で1月の注文だけQueryできる
  • 自然に時系列でソートされる
  • IDを末尾に置くことで一意性も担保

Query例:

const params = {
  TableName: "Orders",
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk_prefix)",
  ExpressionAttributeValues: {
    ":pk": "USER#123",
    ":sk_prefix": "ORDER#2025-01"
  }
};

パターン②:階層型(カテゴリ・地域など)

// ✅ Good
{
  PK: "SHOP#tokyo",
  SK: "CATEGORY#electronics#ITEM#item-123",
  price: 29800,
  stock: 5
}

メリット:

  • 「begins_with("CATEGORY#electronics")」で電化製品だけ取得
  • 階層を増やしても後方互換性がある

使い所:

  • EC:カテゴリ → サブカテゴリ → 商品
  • SaaS:組織 → チーム → メンバー

パターン③:複合条件型(ステータス × 時刻)

// ✅ Good
{
  PK: "TENANT#abc",
  SK: "TICKET#open#2025-01-15T10:00:00Z#ticket-789",
  assignee: "user-456"
}

メリット:

  • 「begins_with("TICKET#open")」でオープンチケットだけQuery
  • ステータスが変わったらSKごと更新(後述)

注意点:

  • ステータス変更時はUpdateではなくDelete + Putが必要
  • トランザクション必須(後ほど詳述)
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?