株式会社renue
AI導入・DXの悩みをプロに相談してみませんか?
AIやDXに関する悩みがありましたら、お気軽にrenueの無料相談をご利用ください。 renueのAI支援実績、コンサルティングの方針や進め方をご紹介します。
リレーショナルデータベースのテーブル設計では、ER図とDDLレビューを通過しても、本番稼働後に N+1 問題・インデックス過剰・パーティション設計の3つの罠で詰まる現場が多い。これらは負荷とデータ分布によって初めて顕在化するため、設計時点では予測が難しい。本記事は、中堅エンジニアが設計レビューで3罠を予測するための判断軸を、PostgreSQL 公式ドキュメントの記述と現場の実装経験から整理する。
なぜ DB 設計の罠は「設計時点」では見えないのか
3罠には共通点がある。書き込みパフォーマンス・クエリ実行プラン・データ分布という3つの観点を設計時点で同時には検証できない点である。PostgreSQL の Table Partitioning 公式ドキュメントは、パーティションの利点は主にデータ管理であり、クエリ性能は副次的だと明言している。インデックスも同様で、「とりあえず張る」が逆に書き込みコストを押し上げる事例は珍しくない。中堅期のエンジニアが詰まるのは、こうした副作用を設計レビューで言語化できないときである。
AI コード生成(Claude Code・GitHub Copilot など)は、設計時点のスキーマ生成や DDL の素案出しは得意だが、罠の予測は人の判断に依存する範囲が広い。本記事はその予測の言語化を試みる。
N+1 問題 — Eager ロード設計より先に必要な「呼び出し境界」の整理
N+1 問題は ORM の慣用句として広く知られるが、解決策として真っ先に挙がる Eager ロード一辺倒が別の問題を生む。具体的には、不要な JOIN 肥大化と SELECT カラム膨張が、本来必要だった軽量クエリより遅延を増やすケースがある。
中堅エンジニアが現場で考える判断軸は、まず呼び出し境界を「API エンドポイント」と「バッチ処理」で切り分けることに尽きる。API エンドポイントは厳密に N+1 を排除すべき範囲で、レスポンス時間に直結する。一方バッチ処理は N+1 のままでも問題が出ないことが多く、むしろ JOIN を肥大化させると COMMIT が遅延しトランザクションが長期化する。
次に、Eager ロードと「呼び出し集約 + Batch Load」の使い分けがある。SQLAlchemy では joinedload と selectinload の2系統があり、後者は IN 句で別クエリを発行することで JOIN の重さを避ける。データ件数と JOIN カラム数で使い分ける判断が必要になる。
さらに、インデックスとの相互作用も忘れてはならない。N+1 を解いたつもりが JOIN 先テーブルのインデックスが効かない設計だと、結局スロークエリが残る。この観点は次節のインデックス設計と同時に検証する必要がある。
renue 社内の言語変換 API プロジェクトでは、「ターゲットごとに最新セッション1件をロード」する処理を JOIN ベースから IN 句のバッチロードに置き換えることで N+1 を解消した実例がある。設計時点では「単一クエリにまとめる」発想が出にくく、本番稼働後にスロークエリログから発見されるケースが多い。
インデックス設計 — 「とりあえずカバリング」が遅くする理由
カバリングインデックス(PostgreSQL の INCLUDE 句、MySQL のセカンダリインデックス)は「読み取り速度を上げる」と説明されることが多いが、無条件ではない。書き込みコスト・統計情報のメンテナンス・カーディナリティの3点で逆に遅くなる事例がある。
Microsoft Learn が公開する Azure Database for PostgreSQL のアーキテクチャ・ベストプラクティスは、インデックスの管理コストを「INSERT・UPDATE のたびに更新が必要な隠れたオーバーヘッド」として明示している。Google Cloud SQL for PostgreSQL の一般的なベストプラクティスも、過剰インデックスがチューニングの足を引っ張る具体例を挙げている。
中堅エンジニアが現場で詰まるパターンを整理する。まず書き込みコスト。複数本のインデックスを重ねて張ると、INSERT のスループットが目に見えて低下する。書き込みヘビーなテーブル(イベントログ・トランザクション履歴)で特に問題になる。
次に統計情報の鮮度。PostgreSQL の ANALYZE が古いまま運用されると、オプティマイザがインデックスを使わない実行プランを選ぶ。設計時点で自動 VACUUM の頻度も合わせて決める必要がある。
そしてカーディナリティの誤読。low-cardinality 列(boolean、性別、ステータス)に単独でインデックスを張ってもフルスキャンと変わらない。複合インデックスの先頭列に置く判断は、業務クエリの WHERE 句の出現順を実測してから決めるのが安全である。
Qiita で 2026 年 4 月に公開された PostgreSQL 実戦テクニック集は、「WHERE と ORDER BY が組み合わさるなら複合インデックスを張る」「数万件規模の COUNT(*) は pg_class.reltuples の概算値で代替する」など、現場感のあるルールを整理している。設計時点でこの種の経験則を知識として持っておくと、レビューで指摘できる範囲が広がる。
パーティション設計 — 設計時点でテーブル分割を決める判断軸
パーティション設計の罠は、「将来データが増えたら検討する」と先送りにする運用にある。データ分布が見えてからのリスケは、テーブル定義変更とアプリ側のクエリ書き換えを同時に強いる。設計時点で見るべき軸は3つに整理できる。
1つめは書き込み分布である。時系列データ(イベントログ、センサーデータ)は Range partition が基本になる。書き込みが時間軸で偏るため、最新パーティションのみがホットスポット化する前提で設計する必要がある。
2つめは削除パターン。古いデータを定期削除する運用なら、パーティション単位の DROP で I/O コストを大幅に下げられる。逆に「行単位の論理削除」を運用上選ぶなら、パーティションは過剰になる可能性が高い。
3つめはクエリ局所性。WHERE 句にパーティションキーが常に含まれるかどうか。含まれないクエリが多いと Partition Pruning が効かず全パーティション走査となり、設計の意味が消える。PostgreSQL の Partition Pruning During Query Execution 機能は実行時 Pruning にも対応するが、それでもパーティションキーが WHERE 句に出ない設計はアンチパターンになる。
実務上、シードフェーズや PMF 前のプロダクトでパーティションを採用する判断は早すぎることが多い。データ件数が小さい段階ではテーブル分割の管理コストの方が重く、PostgreSQL 単体テーブルの性能で十分にスケールする。設計判断は「データ件数の予測カーブ」と「削除運用の頻度」で線を引く。
中堅エンジニアが現場で身につける「設計レビューの観点」
3罠を踏まないために、設計レビュー時にチームで共有する観点を整理する。
まず、設計レビューに「負荷観点」を入れる。ER 図と DDL レビューだけでは罠を予測できない。負荷の見立て・データ分布の見積もり・将来のクエリパターンを、設計レビュー前提として明示的に議論する場をつくる。
次に、実装と検証を同じスプリントで完結させる。スキーマだけ先に決めて実装を後回しにすると、検証段階で設計に戻れない。設計ドキュメントと実装 PR を同じスプリントで仕上げ、本番想定のデータボリュームで負荷テストを設計検証に組み込む。
そして、AI エージェントとの分担を明示する。Claude Code・Copilot は DDL の素案、インデックスのサジェスト、クエリの実装は得意だが、業務クエリの分布予測・データの将来傾向・削除運用との整合は人が判断する範囲が大きい。AI に任せる作業と人が判断する作業の境界を、設計レビューで明示的に分ける運用が現場の判断力を育てる。
中堅期のエンジニアにとって、DB 設計は単なる技術スキルではなく「将来の運用負荷をどこまで見通せるか」というキャリア軸そのものになる。3罠を踏むことで身につく判断力は、テックリード・スタッフエンジニア・スタートアップ CTO へのキャリアパスでも資産になる。
