database-design について
アプリケーション開発者向けのデータベース設計スキルです。アクセスパターンや一貫性の要件に基づくスキーマ設計を扱い、データモデリング、インデックス、データベースの種類、移行方法を解説します。
主な機能
- 01リレーションのモデル化と正規化
- 02ワークロードに応じたDB選定
- 03クエリに基づくインデックス設計
- 04安全なスキーマ変更の計画
- 05パーティショニングとシャーディングの検討
活用シーン
- 01新規アプリケーションのスキーマ設計
- 02頻出クエリ向けインデックスの改善
- 03本番環境のスキーマ変更の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ krzysztofsurdy/ code-virtuoso --skill database-designインストール後、Claude Code で/database-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/indexing-strategies.mdmigration-patterns.mdmodeling-patterns.md詳細
データベース設計
データベース設計は、データ駆動型アプリケーションの長期的な保守性、パフォーマンス、正確性を左右します。スキーマに関する初期の決定を後から覆すには、大きなコストがかかります。すべてのテーブル、列、インデックス、制約には、アクセスパターンとビジネスルールに基づく理由が必要です。
データモデリングの原則
アクセスパターンから始める
エンティティがドメインモデル上でどのように見えるかではなく、アプリケーションがデータをどのように読み書きするかを中心にテーブルを設計します。すべてのスキーマ設計の判断は、次の2つの問いに基づきます。
- 最も頻繁に実行されるクエリは何ですか? これによって、テーブル構造、インデックス、非正規化の方針が決まります。
- データにはどのような整合性保証が必要ですか? これによって、正規化のレベル、制約、トランザクション境界が決まります。
エンティティ間のリレーションシップ
| リレーションシップ | 実装方法 | 使用する場面 |
|---|---|---|
| 1対1 | 子テーブルに UNIQUE 制約付きの外部キーを設定 | ほとんどアクセスされない列を別のテーブルに分割する場合、または必ず1件であることを保証する場合 |
| 1対多 | 子テーブルに親を参照する外部キーを設定 | 注文と注文明細、ユーザーと住所 |
| 多対多 | 複合主キーを持つ結合テーブル | タグと記事、学生とコース |
| 属性付き多対多 | 2つの外部キー以外に独自の列を持つ結合テーブル | 成績を持つ履修登録、役割を持つメンバーシップ |
| 自己参照 | 同じテーブルを参照する外部キー | 組織図、カテゴリーツリー、スレッド形式のコメント |
命名規則
一貫した命名により、チームやツールをまたいだ混乱を防げます。
- テーブル:snake_case の複数形の名詞(
order_items、user_addresses) - 列:値の内容を表す snake_case の単数形(
created_at、total_amount) - 外部キー:
<referenced_table_singular>_id(user_id、order_id) - インデックス:
idx_<table>_<columns>(idx_orders_user_id_created_at) - 制約:
chk_<table>_<rule>、uq_<table>_<columns>、fk_<table>_<referenced>
正規化と非正規化
正規形
| 形式 | ルール | 違反の例 |
|---|---|---|
| 1NF | すべての列が原子的な値を保持し、繰り返しグループがない | 1つの列にカンマ区切りのタグを保存する |
| 2NF | すべての非キー列が主キー全体に依存する | 複合キーのテーブルで、キーの一部だけに依存する列がある |
| 3NF | 非キー列が別の非キー列に依存しない | 郵便番号から都市が決まるのに、city と zip_code の両方を保存する |
| BCNF | すべての決定項が候補キーである | 部屋から建物が決まるものの、部屋がキーではないスケジュールテーブル |
非正規化する場面
正規化はデータの不整合を防ぎますが、JOIN が増えます。非正規化は、次の場合に限って選択的に行います。
- 読み取りが多いワークロードが中心で、プロファイリングによって JOIN のコストが測定できる
- レポート用テーブルで、そうしなければコストの高いクエリが必要になるデータの事前集計が必要
- 計算値のキャッシュによって、読み取りのたびに再計算する手間を省ける(例:注文行に
order_totalを保存する) - ドキュメント指向のアクセスで、集約全体を1回の読み取りで取得する
安全に非正規化するためのルール:
- 正規化された信頼できる唯一の情報源を必ず保持します。非正規化データは派生キャッシュです
- 非正規化したコピーをどのように、いつ更新するかを定義します(トリガー、アプリケーションイベント、バッチジョブ)
- 情報源とコピーの間に生じるずれを監視します
- 非正規化が必要な理由と、それが対応するアクセスパターンを文書化します
データベースの種類の選択
| 種類 | 強み | 適している用途 |
|---|---|---|
| リレーショナル(PostgreSQL、MySQL) | ACID トランザクション、複雑なクエリ、成熟したツール、JOIN | リレーションを持つ構造化データ、トランザクション処理、多くの CRUD アプリケーション |
| ドキュメント(MongoDB、DynamoDB) | 柔軟なスキーマ、ネストされたデータ、水平スケーリング | 集約単位でアクセスするデータ、急速に変化するスキーマ、テナントごとの分離 |
| キー・バリュー(Redis、Memcached) | サブミリ秒の読み取り、シンプルなデータモデル | セッションストレージ、キャッシュ、カウンター、レート制限 |
| カラムファミリー(Cassandra、ScyllaDB) | 高い書き込みスループット、ワイド行、線形スケーリング | 時系列データ、IoT テレメトリ、追記中心のワークロード |
| グラフ(Neo4j、Neptune) | トラバーサルクエリ、リレーション中心のデータ | ソーシャルネットワーク、レコメンデーションエンジン、不正検知 |
| 時系列(TimescaleDB、InfluxDB) | タイムスタンプ付きデータ向けに最適化、自動パーティショニング | メトリクス、モニタリング、金融のティックデータ |
ポリグロットパーシステンス(同じシステムの異なる部分に異なるデータベースを使用すること)は、アクセスパターンが異なる場合に妥当です。これは、1つのデータベースを深く学ぶことを避けるための方法ではありません。
インデックスの基礎
インデックスは、書き込みの遅延と追加のストレージを代償に、読み取りを高速化します。すべてのインデックスは、クエリパターンに照らして、その存在意義を示す必要があります。
インデックスの種類
| 種類 | 構造 | 最適な用途 |
|---|---|---|
| B-tree | 平衡木、ソート済みデータ | 等価検索と範囲検索、ORDER BY、汎用的なインデックスのほとんど |
| Hash | ハッシュテーブル | 完全一致の検索のみ。範囲検索には対応しません |
| GiST | 汎用検索木 | 空間データ、幾何クエリ、範囲型、最近傍検索 |
| GIN | 汎用転置インデックス | 全文検索、JSONB の包含検索、配列のメンバー検索 |
| BRIN | ブロック範囲インデックス | 自然な順序(タイムスタンプ、連番 ID)でデータが格納された大規模テーブル |
複合インデックスの設計
複合インデックスでは、列の順序が重要です。左端プレフィックスルールにより、(a, b, c) の複合インデックスは、(a)、(a, b)、(a, b, c) で絞り込むクエリには対応しますが、(b, c) のみで絞り込むクエリには対応しません。
列の順序に関するガイドライン:
- 等価条件を先に配置します(
=で比較する列) - 範囲条件を最後に配置します(
>、<、BETWEENで比較する列) - 等価条件の列の中では、最も選択性の高い列を先に配置します
カバリングインデックスと部分インデックス
- カバリングインデックス:クエリに必要なすべての列を含むため、データベースはインデックスだけを読み取ります。
INCLUDE(PostgreSQL)を使用するか、列をインデックスキーに追加します。 - 部分インデックス:条件に一致する行のみをインデックス化し、サイズと書き込みのオーバーヘッドを削減します。大規模なテーブルの小さなサブセットをクエリする場合(例:
WHERE status = 'pending')に最適です。
インデックスの種類、EXPLAIN の分析、アンチパターンについて詳しくは、インデックス戦略リファレンスを参照してください。
スキーマの進化
スキーマの変更は避けられません。重要なのは、それによって稼働中のアプリケーションが壊れるかどうかです。
後方互換性のある変更(安全)
- 新しい NULL 許容列を追加する
- 新しいテーブルを追加する
- 新しいインデックスを追加する(一部のエンジンでは短時間ロックされる場合があります)
- 列の型の幅を広げる(例:
VARCHAR(50)からVARCHAR(100))
互換性を損なう変更(移行戦略が必要)
- 列の名前を変更する、または削除する
- 互換性のない方法で列の型を変更する
- NULL 値を含む既存の列に NOT NULL 制約を追加する
- テーブルを分割または統合する
エクスパンド・コントラクトパターン
本番環境でダウンタイムなしに互換性を損なう変更を行う場合:
- 拡張: 古い構造と並行して新しい構造を追加します
- 移行: 古い構造から新しい構造にデータをバックフィルし、移行中は両方に書き込みます
- 切り替え: 新しい構造を使用するようにアプリケーションコードを更新します
- 縮小: 参照するものがなくなったら、古い構造を削除します
ダウンタイムなしの戦略、ロールバック手法、複数のツールを使った例について詳しくは、移行パターンリファレンスを参照してください。
パーティショニングとシャーディング
テーブルのパーティショニング(単一データベース)
| 戦略 | 仕組み | ユースケース |
|---|---|---|
| 範囲 | 値の範囲(例:月ごと)で行を分割します | 時系列データ、ログテーブル、アーカイブ |
| リスト | 個別の値(例:地域ごと)で行を分割します | マルチテナントデータ、地理的なセグメント化 |
| ハッシュ | 列のハッシュ値に基づいて行を分散します | 自然な範囲が存在しない場合の均等な分散 |
シャーディング(複数のデータベース)
シャーディングでは、データを個別のデータベースインスタンスに分散します。単一インスタンスの最適化(インデックス作成、キャッシュ、読み取りレプリカ)を使い切った後にのみ使用してください。
シャードキーの選定基準:
- カーディナリティが高い:均等に分散できるよう、多くの異なる値がある
- ほとんどのクエリに含まれている:すべてのシャードに対する散布・集約を回避できる
- 安定している:作成後に値が変更されない
- ホットスポットがない:書き込みが集中する値(例:現在の日付)でシャーディングしない
クイックリファレンス:よくある設計上の誤り
| 誤り | 影響 | 対策 |
|---|---|---|
| 外部キー制約がない | 孤立した行、データの不整合 | そうしない理由が文書化されていない限り、常に外部キーを定義します |
| インデックスの過剰な作成 | 書き込みの遅延、ストレージの無駄 | 実際のクエリで WHERE、JOIN、ORDER BY に使用される列のみインデックスを作成します |
| 更新戦略なしで計算値を保存する | データの陳腐化、検出されにくいバグ | 非正規化とあわせて、更新トリガー、イベント、またはバッチジョブを定義します |
| 変更される値に ENUM 型を使用する | 値が追加されるたびにスキーマを移行する必要がある | 代わりに外部キーを持つルックアップテーブルを使用します |
| 金額を浮動小数点数として保存する | 丸め誤差 | DECIMAL/NUMERIC を使用するか、整数のセント単位で保存します |
| created_at / updated_at タイムスタンプがない | 監査ができず、デバッグが困難になる | 既定で、すべてのテーブルにタイムスタンプ列を追加します |
あらゆる場所で汎用的な type + type_id のポリモーフィズムを使用する | 参照整合性がなく、クエリが複雑になる | まず STI、CTI、または個別のテーブルを検討します |
参考ファイル
| 参考資料 | 内容 |
|---|---|
| モデリングパターン | ポリモーフィック関連(STI/CTI/TPT)、論理削除、監査履歴、時間データ、自己参照ツリー、JSON 列 |
| インデックス戦略 | B-tree/hash/GiST/GIN の詳細、複合インデックスの設計、カバリングインデックスと部分インデックス、EXPLAIN 分析、アンチパターン |
| マイグレーションパターン | バージョンベースと状態ベースのマイグレーション、拡張・縮小、データマイグレーション、ロールバック戦略、複数ツールの例 |
他のスキルとの連携
| 状況 | 推奨スキル |
|---|---|
| クエリパフォーマンスとキャッシュの最適化 | performance |
| ドメインモデルと集約の設計 | clean-architecture |
| データベースを利用するリソースを公開する API の構築 | api-design |
| データベースとのやり取りのテスト | testing |