非正規化

読み取り性能を向上させるために、意図的にデータの冗長性を持たせる設計手法

データベース設計

非正規化とは

非正規化 (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 にデータを置く非正規化を前提にするJOIN が使えないため、正規化したままでは 1 画面のために複数回のクエリを重ねることになる
読み取りが書き込みより圧倒的に多い非正規化する遅くなるのは頻度の低い書き込み側だけで、多数を占める読み取りは 1 回のクエリで済むようになる
書き込みが多く、値の食い違いが許されない正規化のまま (RDS)複製した値を持つと更新のたびに全複製をトランザクションで揃える必要があり、書き込みが多いほどその負担が積み上がる
埋め込んだ値がほとんど変わらない非正規化する顧客名のように更新が稀な項目なら、同期のために動く経路が走る回数自体が少なく済む

非正規化の背景や設計思想は関連書籍に詳しい。

この記事は役に立ちましたか?

関連用語

関連する記事