非正規化
読み取り性能を向上させるために、意図的にデータの冗長性を持たせる設計手法
データベース設計
非正規化とは
非正規化 (Denormalization) は、正規化されたデータベースに意図的に冗長性を持たせ、読み取り性能を向上させる設計手法である。JOIN を排除し、1 回のクエリで必要なデータを取得できるようにする。
正規化 vs 非正規化
| 観点 | 正規化 | 非正規化 |
|---|---|---|
| 冗長性 | なし | あり (意図的) |
| 読み取り | 遅い (JOIN が必要) | 速い (1 回のクエリ) |
| 書き込み | 速い (1 箇所を更新) | 遅い (複数箇所を更新) |
| 一貫性 | 高い | 低い (同期が必要) |
| ストレージ | 効率的 | 冗長 |
DynamoDB での非正規化
DynamoDB は JOIN をサポートしないため、非正規化が基本設計となる。
// ❌ 正規化 (RDS 的な設計): 2 回のクエリが必要
// orders テーブル: { orderId: "1", userId: "U1" }
// users テーブル: { userId: "U1", name: "Alice" }
// ✅ 非正規化: 1 回のクエリで取得
{
"orderId": "1",
"userId": "U1",
"userName": "Alice",
"items": [
{ "product": "りんご", "price": 100 }
]
}
非正規化のパターン
| パターン | 説明 | 例 |
|---|---|---|
| 埋め込み | 関連データをアイテム内に埋め込む | 注文に顧客名を含める |
| 複製 | 同じデータを複数のアイテムに持つ | 商品名を注文明細にも持つ |
| 集計値の事前計算 | 集計結果を保存 | 注文の合計金額を保存 |
一貫性の維持
非正規化したデータの一貫性を維持する方法:
// DynamoDB トランザクションで複数アイテムを同時更新
await db.transactWrite({
TransactItems: [
{ Update: { TableName: 'orders', Key: { id: orderId }, UpdateExpression: 'SET userName = :name', ... } },
{ Update: { TableName: 'users', Key: { id: userId }, UpdateExpression: 'SET #name = :name', ... } },
],
});
// DynamoDB Streams で非同期に同期
[users テーブル更新] → [DynamoDB Streams] → [Lambda] → [orders テーブルの userName を更新]
いつ非正規化するか
| ケース | 推奨 |
|---|---|
| DynamoDB (NoSQL) | ✅ 基本的に非正規化 |
| 読み取りが圧倒的に多い | ✅ 非正規化 |
| 書き込みが多く一貫性が重要 | ❌ 正規化 (RDS) |
| データの更新頻度が低い | ✅ 非正規化 (同期コストが低い) |
非正規化の背景や設計思想は関連書籍に詳しい。