Day21 後半のゴール
「“きれいな正規化”から“現場で回る設計”に落とし込む」
前半で、「正規化の原則」と「履歴・冗長性・インデックス」の話まで来ました。
後半では、もう一歩踏み込んで「実務でよく悩むポイント」を具体例で整理します。
どこまで正規化して、どこからは“割り切って持つか”。
JSON やタグのような“柔らかい情報”をどこに置くか。
将来の変更やセキュリティ・監査まで見据えて、どう設計するか。
ここを一緒に言語化していきます。
どこまで正規化するかの判断軸
「“更新頻度”と“意味の独立性”で考える」
実務で「この情報、別テーブルに分けるべき?」と迷ったとき、
判断軸として使いやすいのがこの2つです。
更新頻度
意味の独立性
更新頻度で考える
例えば、ユーザーの「名前」と「メールアドレス」と「プロフィール文」があるとします。
名前・メールアドレス
→ ログインや通知に使う。頻繁には変わらないが、重要度は高い。
プロフィール文
→ ユーザーが気分で変える。頻度は高いが、壊れても致命傷ではない。
この場合、全部 users テーブルに持ってもいいし、
プロフィールだけ別テーブル(user_profiles)に分ける選択もあります。
プロフィールを分けるメリットは、
プロフィールだけを更新する処理が多いなら、
users テーブルのロックや更新負荷を減らせる。
逆に、分けるデメリットは、
JOIN が増える。
アプリ側のコードが少し複雑になる。
「どちらが正解」ではなく、「どの更新がどのテーブルにどれだけ当たるか」をイメージして決める、という感覚が大事です。
意味の独立性で考える
例えば、「都道府県名」「国名」「通貨」などは、
ユーザーや注文とは別に“独立した意味”を持つことが多いです。
都道府県コード・名前を prefectures テーブルに分ける。
通貨コード・小数桁数を currencies テーブルに分ける。
こうしておくと、
マスタデータとして一元管理できる。
外部キー制約で「存在しない都道府県コードを入れない」ようにできる。
セキュリティ・監査の観点でも、「マスタの変更履歴を追う」「誰がいつ通貨設定を変えたか」を追いやすくなります。
“柔らかい情報”をどこに置くか
「正規カラム+JSONBのハイブリッド設計」
最近の実務でよく出てくるのが、「柔らかい情報」の扱いです。
ユーザーの任意属性(好み、設定、A/Bテストのバリアントなど)
外部APIレスポンスの一部
イベントログの追加情報
これらを全部カラムにすると、テーブル定義がすぐにパンパンになります。
かといって、全部 JSONB にすると、「何が必須で何が任意か」が分からなくなる。
そこでよく使うのが、「正規カラム+JSONB」のハイブリッドです。
例:ユーザー設定テーブル
CREATE TABLE user_settings (
user_id BIGINT PRIMARY KEY REFERENCES users(id),
language TEXT NOT NULL,
timezone TEXT NOT NULL,
extras JSONB NOT NULL DEFAULT '{}'::jsonb
);
SQLlanguage / timezone
→ アプリの動作に必須。カラムとして定義し、NOT NULL 制約をかける。
extras
→ 実験的なフラグや、特定の機能だけが使う追加設定を入れる。
こうしておくと、
「必須で守りたい情報」はスキーマで守れる。
「変わりやすい・増えやすい情報」は JSONB に逃がせる。
さらに、extras に対して GIN インデックスを張れば、extras @> '{"beta_feature": true}' のような検索も現実的になります。
ここでのポイントは、「全部 JSON に逃げない」「全部カラムにもしない」というバランス感覚です。
将来の変更をどう見込むか
「“変わりにくい軸”と“変わりやすい軸”を分ける」
設計でよく効く考え方が、「変わりにくいもの」と「変わりやすいもの」を分けることです。
変わりにくいもの
→ ユーザーID、注文ID、商品ID、作成日時など。
→ これらは主キー・外部キー・インデックスの軸になりやすい。
変わりやすいもの
→ 表示名、説明文、タグ、フラグ類など。
→ UIやビジネスルールの変更で変わりやすい。
変わりにくいものは、テーブルの“骨格”としてしっかりカラムに持ち、
制約やインデックスをきちんと張る。
変わりやすいものは、
別テーブルに切り出す(例:product_descriptions)
JSONB に逃がす(例:extras)
履歴テーブルにする(例:product_price_history)
など、変更に耐えられる形にしておく。
セキュリティ的にも、「変わりにくいもの(ID・キー)」は厳しく守り、
「変わりやすいもの」は変更ログや監査の対象として扱う、という線引きがしやすくなります。
実務レベル設計の“チェックポイント”
「テーブルを作る前に、自分に問いかけてほしいこと」
新しいテーブルを設計するとき、最低限これだけは自分に聞いてほしい、という問いをまとめます。
このテーブルの“主語”は何か
→ users なのか、orders なのか、logs なのか。
→ 1テーブルに主語を増やしすぎていないか。
このテーブルの主キーは何か
→ 自然キーか、サロゲートキー(id)か。
→ 主キーで一意に決まらない情報を詰め込んでいないか。
どの情報が“現在の真実”で、どの情報が“過去の真実(履歴)”か
→ price と unit_price のように、意味を分けているか。
どのクエリが一番多く走るか
→ そのクエリの WHERE / JOIN に必要なインデックスは何か。
どの情報が“変わりにくい軸”で、どの情報が“変わりやすい装飾”か
→ 骨格と装飾を分けているか。
この問いに答えられるようになると、
「なんとなくテーブルを作る」から「意図を持って設計する」に一段上がれます。
セキュリティ・監査を意識した設計の一歩
「“変えていいもの”と“変えちゃいけないもの”をスキーマで分ける」
最後に、あなたがこれから実務に出たときに、
ちょっと差がつく視点を1つだけ。
設計の段階で、「変えていいもの」と「変えちゃいけないもの」を分けておくと、
セキュリティ・監査・障害対応が一気に楽になります。
変えちゃいけないものの例
→ 注文ID、注文日時、当時の価格(unit_price)、決済結果、監査ログ。
これらは、
NOT NULL
CHECK 制約
外部キー制約
UPDATE を基本禁止にして、INSERT だけで履歴を積む
といった形で、「スキーマで守る」ことができます。
変えていいものの例
→ 表示名、説明文、タグ、フラグ。
これらは、
別テーブルにして履歴を残す
JSONB にして柔軟に扱う
など、変更しやすさを優先して設計できます。
「どのカラムが“法律・契約・監査に絡むか”」を意識して設計できるようになると、
ただのDB設計者ではなく、「システム全体の信頼性を支えるエンジニア」に一歩近づきます。
Day21 後半のまとめ
実務レベル設計では、「正規化の原則」を土台にしつつも、更新頻度・意味の独立性・履歴要件・将来の変更を踏まえて、「どこまで分けるか」「どこから冗長に持つか」を決めていく必要があり、そのために「変わりにくい骨格(ID・キー・日時)」と「変わりやすい装飾(説明・タグ・フラグ)」を分ける発想が有効になる。
さらに、必須の情報はカラムと制約で守り、柔らかい情報は JSONB や別テーブルに逃がすハイブリッド設計を取りつつ、「どの値が現在の真実で、どの値が過去の真実か」「どのカラムは変えてよくて、どのカラムは変えちゃいけないか」をスキーマで表現しておくことが、正規化と実務をつなぐ Day21 後半の着地点になる。
