キー設計パターン
このガイドでは、DynamoDBにおけるエンティティのパーティションキー(PK)とソートキー(SK)の設計方法を説明します。適切なキー設計は、パフォーマンス、スケーラビリティ、クエリ効率にとって重要です。
このガイドを使用するタイミング
以下が必要な場合にこのガイドを使用してください:
- 新しいエンティティタイプのキーを設計する
- 親子関係をモデル化する(Order → OrderItems)
- マルチテナントデータ分離をサポートする
- 効率的なクエリパターンを有効にする(テナント別リスト、日付フィルター)
- 楽観的ロック用のバージョニングを処理する
このパターンが解決する問題
| 問題 | 解決策 |
|---|---|
| テナントの全アイテムをクエリするのが遅い | パーティションレベルの分離のためにPKにテナントコードを含める |
| すべてのキーを知らないと子アイテムをリストできない | 異なるSKプレフィックスで共有PKを使用する |
| IDが作成時間でソートできない | 一意性と時間ソートの両方を持つULIDを使用する |
| 同時更新でバージョン競合が発生する | SKのバージョンサフィックスで楽観的ロックを有効にする |
キー構造の概要
フレームワークは一貫したキー構造を使用します:
PK = PREFIX#TENANT_CODE
SK = IDENTIFIER[#VERSION]
ID = PK#SK (without version)
KEY_SEPARATOR定数(#)はキーコンポーネントを区切るために使用されます。
基本的なキー生成
コアパッケージからユーティリティをインポートします:
import {
generateId,
KEY_SEPARATOR,
removeSortKeyVersion,
addSortKeyVersion,
VERSION_FIRST,
} from "@mbc-cqrs-serverless/core";
import { ulid } from "ulid";
キーの生成
const PRODUCT_PK_PREFIX = "PRODUCT";
// Generate PK
const pk = `${PRODUCT_PK_PREFIX}${KEY_SEPARATOR}${tenantCode}`;
// Result: "PRODUCT#tenant001"
// Generate SK (using ULID for uniqueness and sortability)
const sk = ulid();
// Result: "01HX7MBJK3V9WQBZ7XNDK5ZT2M"
// Generate ID (combination of PK and SK)
const id = generateId(pk, sk);
// Result: "PRODUCT#tenant001#01HX7MBJK3V9WQBZ7XNDK5ZT2M"
バージョン管理
// Add version to SK
const skWithVersion = addSortKeyVersion(sk, 3);
// Result: "01HX7MBJK3V9WQBZ7XNDK5ZT2M#v3"
// Remove version from SK
const baseSk = removeSortKeyVersion(skWithVersion);
// Result: "01HX7MBJK3V9WQBZ7XNDK5ZT2M"
一般的なキーパターン
パターン1: シンプルなエンティティ
Use Case: Product Catalog
Scenario: Store products that belong to a tenant with unique IDs.
When to use: Standalone entities without parent-child relationships.
// Key Structure
PK: PRODUCT#<tenantCode>
SK: <ulid>
// Example
PK: PRODUCT#tenant001
SK: 01HX7MBJK3V9WQBZ7XNDK5ZT2M
ID: PRODUCT#tenant001#01HX7MBJK3V9WQBZ7XNDK5ZT2M
const pk = `PRODUCT${KEY_SEPARATOR}${tenantCode}`;
const sk = ulid();
const id = generateId(pk, sk);
パターン2: 階層エンティティ
Use Case: Order with Line Items
Scenario: An order contains multiple items. Need to query all items for an order efficiently.
Solution: Share PK between parent and children, use SK prefix to distinguish item types.
// Order Key Structure
PK: ORDER#<tenantCode>
SK: ORDER#<orderId>
// Order Item Key Structure (same PK, different SK prefix)
PK: ORDER#<tenantCode>
SK: ORDER_ITEM#<orderId>#<itemId>
// Example
Order:
PK: ORDER#tenant001
SK: ORDER#01HX7MBJK3V9WQBZ7XNDK5ZT2M
Order Items:
PK: ORDER#tenant001
SK: ORDER_ITEM#01HX7MBJK3V9WQBZ7XNDK5ZT2M#001
SK: ORDER_ITEM#01HX7MBJK3V9WQBZ7XNDK5ZT2M#002
const ORDER_SK_PREFIX = "ORDER";
const ORDER_ITEM_SK_PREFIX = "ORDER_ITEM";
// Create order
const orderPk = `ORDER${KEY_SEPARATOR}${tenantCode}`;
const orderId = ulid();
const orderSk = `${ORDER_SK_PREFIX}${KEY_SEPARATOR}${orderId}`;
// Create order item
const itemSk = `${ORDER_ITEM_SK_PREFIX}${KEY_SEPARATOR}${orderId}${KEY_SEPARATOR}${itemId}`;
パターン3: 複数認証プロバイダーを持つユーザー
Use Case: Unified User Identity
Scenario: Users can sign in with local password, SSO, or OAuth. Need to link all auth methods to one user.
Solution: Same PK for all user records, SK prefix indicates authentication provider.
// Key Structure
PK: USER#<tenantCode>
SK: <provider>#<userId>
// Examples
PK: USER#common
SK: local#user123 // Local authentication
SK: sso#abc123def456 // SSO provider
SK: oauth#google789 // OAuth provider
SK: temp#session456 // Temporary session
SK: profile#user123 // User profile data
type AuthProvider = "local" | "sso" | "oauth" | "temp" | "profile";
function generateUserSk(provider: AuthProvider, userId: string): string {
return `${provider}${KEY_SEPARATOR}${userId}`;
}
const pk = `USER${KEY_SEPARATOR}common`;
const sk = generateUserSk("sso", cognitoSubId);