---
note: プロジェクト初期に「遅さを検出する手段」を用意する。実行計画・クエリ数・効率比をどう判定に組むか
created: 2026-08-15T12:30:00+09:00
---

# DB性能検証の初期セットアップ

> pre-project setup checklist の一項目。**「遅くなってから測る手段を作る」のでは遅い**ため、繋ぎ込みの実装が始まる前に検証手段を用意しておく。エージェントに実装させる場合はとくに、これがないと「動くが遅い」実装が緑のまま通過する。

## なぜ初期に要るのか

- 機能テストは「動くこと」しか見ない。N+1も全件走査もテストは緑になる
- 人間のレビューでしか捕まらない失敗モードだが、実行計画は**機械可読な形でそれを暴ける**
- ただし検証手段を後から入れると、既に積み上がった非効率の追認になりやすい

## チェックリスト

### 1. 本番相当のseedデータがあるか

- [ ] 主要テーブルに、本番の桁数に近い件数のseedデータを投入する手段がある
- [ ] 件数だけでなく**値の分布**が本番に近い（全行同じ値のダミーだと索引の効きが再現しない）
- [ ] seed投入後に `ANALYZE`（統計情報の収集）を実行する手順が組み込まれている

> **これがない状態で性能検証を組んでも、すべてノイズになる。** 開発用DBの1000行では全件走査が最速なので、正しい判断が「遅い」と判定される。最初に投資すべきはここ。

### 2. 合格基準がどこから来ているか

- [ ] 性能要件（レスポンスタイム等）が**要求層**に書かれている
- [ ] その数字を誰が決めたかが辿れる（実測値の現状追認になっていない）
- [ ] 書かれていない場合、テストに閾値を書く前に要求側に確認する

> 「300ms以内」は仕様でも設計でもなく要求の成功基準。エージェントに決めさせると最初の実測値が基準になり、劣化を検出できなくなる。ここは自動化できない。

### 3. 検証がコマンドとして固定されているか

- [ ] 検証が単一コマンドで実行できる（例: `npm run db:check`）
- [ ] 判定基準がコード側にあり、プロンプトや指示書側にない
- [ ] レポート出力があり、before/afterを差分で示せる

> 指示書に「実行計画を確認せよ」と書くだけでは、エージェントが解釈で基準をずらせる。コマンド化すれば基準の改変が差分に出る。

### 4. 判定の順序と内容

判定は必ず以下の順序で、上位が失敗したら下位を評価しない。

| 優先 | 判定 | 内容 | 失敗条件にするか |
|---|---|---|---|
| 1 | 機能テスト | 仕様どおり動くこと | **必須** |
| 2 | クエリ発行回数 | N+1検出。件数非依存で最も安定 | する |
| 3 | 効率比 | 返した行数に対し読んだ行数/バッファが異常でないか | する |
| 4 | 実行時間(ms) | 参考値 | **しない**（ログ出力のみ） |

- [ ] 機能テストが前段の必須条件になっている
- [ ] クエリ発行回数の上限が定義されている
- [ ] 効率比の閾値が定義されている（例: 読んだ行数 / 返した行数）
- [ ] msは記録するが失敗条件にしていない

> **msを失敗条件にすると誤魔化される。** テストデータを減らす、`LIMIT` を足す（仕様が変わる）、索引を乱造する（書き込み劣化はこの閾値では見えない）、キャッシュで2回目だけ速くする——どれもmsは改善する。回数と比率は比なので誤魔化せない。

### 5. アサートしてはいけないもの

- [ ] 実行計画の**形**（`"Index Scan" in output` 等）をアサートしていない

> 計画の形は実装詳細。DBバージョン・統計・データ件数・コストパラメータで変わる。件数が少なければ全件走査を選ぶのが**正しい**ので、このテストは「プランナが正しく判断したとき赤くなる」。宣言的インターフェースのテストで実装詳細を固定している状態。

代わりに安定するもの:

- クエリ発行回数（計画非依存）
- 効率比（件数非依存）
- 索引が**存在する**ことのマイグレーションテスト（「使われるか」ではなく「あるか」）

### 6. 修正ループの上限とエスカレーション

- [ ] 自動修正ループに回数上限がある（3回程度）
- [ ] 上限超過時に人間へエスカレーションする経路がある

> 「索引を足せば直る」ケースと「データモデルが間違っている」ケースを、実行計画から区別することはできない。後者を自動修正させると、根本原因の上に索引が積み上がる。

### 7. 実行レーンの分離

- [ ] 1〜3（速い判定）は通常のテストレーンに置く
- [ ] 本番相当データでのベンチマークは nightly か手動レーンに分離している
- [ ] ベンチの閾値は絶対時間ではなく**前回比の劣化率**で見ている

> Red-Green-Refactorの内側は「速く・決定的に」が生命線。性能は環境依存で遅いので、内側に置くと開発が止まる。

### 8. 本番側の恒常観測

- [ ] 遅いクエリを自動記録する設定が入っている
- [ ] 定点観測の手段があり、総実行時間の上位を特定できる

| | 広く見つける（定点観測） | 一点を深く見る（虫眼鏡） |
|---|---|---|
| PostgreSQL | `pg_stat_statements` / `auto_explain` | `EXPLAIN (ANALYZE, BUFFERS)` |
| MongoDB | Database Profiler | `explain("executionStats")` |
| Elasticsearch | slow log | `?profile=true` |

- `auto_explain.log_min_duration` を設定。`log_analyze = on` は全クエリに計測コストが乗るため、まずoffで始めて段階的に
- **`EXPLAIN ANALYZE` は実際に実行する。** `UPDATE`/`DELETE` に対しては `BEGIN; ... ROLLBACK;` で囲む

## Elasticsearch固有の注意

ESをデータストアに使う場合、上記の枠組みがそのままは適用できない。

- [ ] `took` を単発で判定条件にしていない

> フィルタキャッシュ・リクエストキャッシュにより2回目から速くなる。ウォームアップして複数回実行し中央値を取らないと安定しない。RDBMSより「msが揺れる/誤魔化される」度合いが強い。

- [ ] shard時間は合計ではなく**最大値**で見ている（遅いshard1枚がレイテンシを決めるため）
- [ ] `rewrite_time` の異常を監視している（ワイルドカードや巨大な `terms` の展開爆発を検出できる）
- [ ] リクエスト回数を見ている（msearchにまとめられているか＝N+1相当）

補足として、Query DSL側にはコストベースのプランナが存在しない（転置インデックスという単一経路のため選択が発生しない）。「効率比」に相当する指標がprofile出力から素直に取れないので、構造的な指標（書き換え時間・リクエスト回数・対象shard数）に寄せる。

なお `_explain` APIは**1ドキュメントのスコア内訳**を出すもので、性能診断用ではない。名前が紛らわしい。

## 関連

- 実行計画の読み方 → db-course/08-explain.md
- 定点観測（pg_stat_statements） → db-course/12-operations-monitoring.md
- 索引設計 → db-course/07-storage-indexes-collation.md
- 性能要件が属する層 → concepts/requirements-five-elements.md（成功基準）
