performance-optimization について
アプリケーションの性能を診断・改善するための開発スキルです。測定、原因特定、最適化、検証のサイクルに沿って実際のボトルネックを調べ、測定可能な効果がある変更だけを残します。
主な機能
- 01変更前の性能ベースライン測定
- 02システム層ごとのボトルネック分析
- 03一度に一つの変更を適用
- 04再測定による効果検証
- 05キャッシュとバンドル最適化の指針
- 06データベース、Web Vitals、負荷テスト
活用シーン
- 01フロントエンド描画の遅延調査
- 02実行計画に基づくクエリ調整
- 03負荷時のAPI動作評価
- 04最適化による指標改善の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill performance-optimizationインストール後、Claude Code で/performance-optimizationを入力して読み込まれたか確認します。
同梱ファイルperformance-budgets.md
詳細
パフォーマンス最適化
概要
測定に基づく方法論を使用して、パフォーマンスのボトルネックを体系的に特定し、解消します。このスキルでは、厳密な「測定・特定・最適化・検証」のサイクルを徹底し、時期尚早な最適化や憶測を防ぎます。すべての最適化で測定可能な改善が得られなければ、変更を元に戻します。
開始時に宣言:「パフォーマンス最適化スキルを使用して、ボトルネックを診断し、解消します。」
フェーズ 1:測定(ベースラインを確立する)
目標: 変更を加える前に、実際の指標を取得します。
アクション
# Web: Lighthouse CI
npx lighthouse https://your-app.com --output=json --output-path=baseline.json
# API: load test with k6
k6 run --out json=baseline.json loadtest.js
# Database: slow query log
# PostgreSQL: SET log_min_duration_statement = 100; -- log queries > 100ms
これらの数値を記録します。改善を測定する際のベースラインになります。
停止 — 次の条件を満たすまでフェーズ 2 に進まないでください:
- ベースライン指標を取得して保存している
- 具体的な指標の目標を定義している(例:LCP < 2.5s)
- 測定方法を文書化している(再実施できるように)
フェーズ 2:特定(実際のボトルネックを見つける)
目標: プロファイリングツールを使用して、時間がどこで費やされているかを特定します。推測してはいけません。
プロファイリングツールの選択表
| レイヤー | ツール | 表示される内容 |
|---|---|---|
| フロントエンドのレンダリング | React DevTools Profiler、Chrome Performance タブ | コンポーネントのレンダリング時間 |
| ネットワーク | Chrome Network タブ、WebPageTest | リクエストのウォーターフォール、TTFB |
| JavaScript | Chrome Performance タブ、console.time() | 関数の実行時間 |
| Node.js サーバー | --prof フラグ、clinic.js、0x | CPU フレームグラフ |
| データベース | EXPLAIN ANALYZE、pg_stat_statements | クエリプラン、遅いクエリ |
| メモリ | Chrome Memory タブ、heapdump | メモリ割り当てのパターン、リーク |
| バンドルサイズ | webpack-bundle-analyzer、vite-bundle-visualizer | モジュールのサイズ |
ボトルネックが想定した場所にあることは、ほとんどありません。まず測定してください。
停止 — 次の条件を満たすまでフェーズ 3 に進まないでください:
- 対象レイヤーに適したプロファイリングツールを使用している
- データに基づいて具体的なボトルネックを特定している
- ボトルネックが問題の大きな部分を占めている
フェーズ 3:最適化(特定したボトルネックを修正する)
目標: 対象を絞った修正を適用します。一度に変更するのは 1 つだけです。
最適化の判断表
| ボトルネックの種類 | 最適化の方法 | 例 |
|---|---|---|
| 大きなバンドル | コード分割、ツリーシェイキング、動的インポート | React.lazy(() => import('./HeavyComponent')) |
| API レスポンスが遅い | キャッシュ、クエリの最適化、ページネーション | TTL 5min の Redis キャッシュを追加 |
| データベースクエリが遅い | インデックスの追加、クエリプランの最適化、マテリアライズドビュー | CREATE INDEX idx_user_email ON users(email) |
| 再レンダリングが過剰 | メモ化、仮想化、状態の再構成 | React.memo、useMemo |
| 画像が大きい | 圧縮、遅延読み込み、レスポンシブ画像 | <img loading="lazy" srcset="..."> |
| TTFB が遅い | サーバーサイドキャッシュ、CDN、エッジレンダリング | Stale-while-revalidate パターン |
| メモリリーク | イベントリスナーのクリーンアップ、弱参照を修正 | 適切な useEffect のクリーンアップ |
停止 — 次の条件を満たすまでフェーズ 4 に進まないでください:
- 変更を 1 つだけ加えている
- 変更が特定したボトルネックを直接対象としている
- 最適化と併せて、無関係な変更を加えていない
フェーズ 4:検証(再度測定する)
目標: フェーズ 1 とまったく同じ測定を再実行します。
アクション
- フェーズ 1 と同じプロファイリング/測定を実行します
- 結果を比較します:
- 指標は改善しましたか?
- どのくらい改善しましたか?
- ほかの指標が悪化しましたか?
- 改善を測定できない場合は、変更を元に戻します。
測定できない最適化は、最適化ではありません。
停止 — 次の条件を満たしたら検証完了です:
- フェーズ 1 と同じ測定方法を使用している
- 改善を定量化している(例:「LCP が 3.2s から 2.1s に短縮」)
- ほかの指標に悪化がない
- 改善がない場合:変更を元に戻している
キャッシュ戦略の判断表
| キャッシュの種類 | 使用する場面 | TTL の目安 | 無効化 |
|---|---|---|---|
| インメモリ(LRU) | 単一インスタンス、頻繁に使うデータ、計算済みの値 | 数秒〜数分 | 退避ポリシー |
| Redis/Memcached | 複数インスタンス、共有キャッシュ、セッション | 数分〜数時間 | イベントベースまたは TTL |
| CDN | 静的アセット、公開ページ、API レスポンス | 数時間〜数日 | デプロイをトリガーとしたパージ |
| ブラウザー | 再訪問、静的リソース | 数日〜数か月(バージョン管理) | キャッシュ回避用ハッシュ |
Cache-Control ヘッダー
# Immutable assets (hashed filenames)
Cache-Control: public, max-age=31536000, immutable
# API responses (cacheable but must revalidate)
Cache-Control: public, max-age=0, must-revalidate
ETag: "abc123"
# Private user data
Cache-Control: private, no-store
# Stale-while-revalidate (fast response + background refresh)
Cache-Control: public, max-age=60, stale-while-revalidate=300
バンドル最適化の手法
| 手法 | 効果 | 実装 |
|---|---|---|
| ルート単位のコード分割 | 高 | ルートごとに React.lazy() + Suspense |
| ツリーシェイキング | 高 | ES モジュールのみを使用し、sideEffects: false を指定 |
| 動的インポート | 中 | ユーザー操作時に await import('heavy-lib') を実行 |
| 画像の最適化 | 高 | next/image、WebP/AVIF、レスポンシブな srcset |
| フォントの最適化 | 中 | next/font、font-display: swap、サブセット |
| 依存関係の置き換え | 中 | moment.js の代わりに day.js、lodash の代わりに lodash-es |
バンドル分析コマンド
# Webpack
npx webpack-bundle-analyzer stats.json
# Vite
npx vite-bundle-visualizer
# Next.js
ANALYZE=true next build
データベースクエリのチューニング
インデックスの最適化
-- Find missing indexes (PostgreSQL)
SELECT schemaname, tablename, seq_scan, idx_scan
FROM pg_stat_user_tables
WHERE seq_scan > idx_scan
ORDER BY seq_scan DESC;
インデックスのルール
| ルール | 説明 |
|---|---|
| WHERE、JOIN、ORDER BY の列にインデックスを作成する | DB が検索する列です |
| 複合インデックスでは等価条件の列を先にする | 最も選択性の高い条件で先に絞り込みます |
| 複合インデックスでは範囲条件の列を最後にする | 選択性が低く、等価条件の後に適用されます |
| 使用されていないインデックスを削除する | 書き込みが遅くなります |
| フィルター付きクエリには部分インデックスを使用する | インデックスが小さくなり、検索が速くなります |
クエリプランで注意すべき点
| EXPLAIN ANALYZE での注意点 | 意味 | 対処法 |
|---|---|---|
| 大きなテーブルでの Seq Scan | テーブル全体のスキャン | インデックスを追加する |
| 行数の多い Nested Loop | O(n*m) の結合 | インデックスを追加するか、クエリを再構成する |
| メモリー使用量の多い Sort | メモリー内でのソート | ORDER BY に一致するインデックスを追加する |
| 実際の行数が推定行数を大幅に上回る | 統計情報が古い | ANALYZE を実行する |
| ビルド対象が大きい Hash Join | メモリーを大量に使用する | 結合列にインデックスがあることを確認する |
Web Vitals の目標値
| 指標 | 良好 | 要改善 | 不良 |
|---|---|---|---|
| LCP(最大コンテンツ描画) | < 2.5s | 2.5-4s | > 4s |
| INP(次の描画までの操作時間) | < 200ms | 200-500ms | > 500ms |
| CLS(累積レイアウトシフト) | < 0.1 | 0.1-0.25 | > 0.25 |
Web Vitals の最適化表
| 指標 | 最適化 | 実装 |
|---|---|---|
| LCP | LCP リソースをプリロードする | <link rel="preload"> または fetchpriority="high" |
| LCP | クリティカル CSS をインライン化する | ファーストビューの CSS を抽出してインライン化する |
| LCP | TTFB を最適化する | CDN、エッジレンダリング、サーバーキャッシュ |
| INP | 長いタスクを分割する | requestIdleCallback、scheduler.yield() |
| INP | 入力ハンドラーをデバウンスする | 負荷の高いハンドラーに 100-300ms のデバウンスを適用する |
| INP | Web Workers | 計算処理をメインスレッドから移す |
| CLS | 寸法を明示する | 画像と動画に width/height を設定する |
| CLS | 動的コンテンツ用のスペースを確保する | 広告や埋め込みコンテンツのプレースホルダーのサイズを指定する |
| CLS | transform アニメーションを使用する | レイアウトを発生させるプロパティを避ける |
負荷テスト
テストの種類
| 種類 | ユーザー数 | 期間 | 目的 |
|---|---|---|---|
| スモーク | 1-2 | 1 分 | テストが機能することを確認する |
| 負荷 | 想定トラフィック | 10-30 分 | 通常時のパフォーマンスを確認する |
| ストレス | 想定の 2-3 倍 | 10-30 分 | 限界点を見つける |
| ソーク | 通常負荷 | 2-8 時間 | メモリーリークを見つける |
主な指標
- レスポンスタイムのパーセンタイル(p50、p95、p99)— 平均値ではありません
- 負荷時のエラー率
- スループット(リクエスト/秒)
- リソース使用率(CPU、メモリー、接続数)
アンチパターン/よくある間違い
| アンチパターン | 誤りである理由 | 正しいアプローチ |
|---|---|---|
| 計測せずに最適化する | 何を修正すべきか分かりません | まず必ず計測します |
| 時期尚早な最適化 | ボトルネックではない箇所に時間を費やします | プロファイリングで実際のボトルネックを特定します |
| すべてをメモ化する | 効果が確認されていないまま複雑さが増します | まずプロファイリングし、その後にメモ化します |
| 無効化戦略なしでキャッシュする | 古いデータがバグの原因になります | キャッシュを追加する前に無効化の方法を定義します |
| パーセンタイルではなく平均値を最適化する | 平均値ではテールレイテンシが見えません | p95 と p99 を追跡します |
| 複数の最適化を同時に行う | 改善の要因を特定できません | 一度に1つだけ変更します |
| 効果を計測できない最適化を残す | 不要なコードと複雑さが残ります | 計測可能な改善がなければ元に戻します |
| クエリのパターンを確認せずにインデックスを追加する | 使われないインデックスは書き込みを遅くします | まずスロークエリログを確認します |
サブエージェントへの委任機会
| タスクのパターン | 委任先 | 実施するタイミング |
|---|---|---|
| 異なるシステムレイヤーを並行してプロファイリングする | subagent_type="Explore" を指定した Agent ツール(レイヤーごとに1つ) | フロントエンド、バックエンド、データベースを個別に分析するとき |
| バンドル分析とツリーシェイキングのレビュー | subagent_type="general-purpose" を指定した Agent ツール | フロントエンドのバンドルサイズが懸念されるとき |
| データベースクエリの最適化分析 | database-architect エージェントに処理を委任する Agent ツール | 複数のテーブルにわたって遅いクエリが特定されたとき |
委任する際は、dispatching-parallel-agents スキルのプロトコルに従ってください。
統合ポイント
| スキル | 関連性 |
|---|---|
senior-frontend | フロントエンドのパフォーマンスでは、バンドルと Web Vitals の最適化を使用します |
senior-backend | バックエンドのパフォーマンスでは、キャッシュとクエリのチューニングを使用します |
testing-strategy | 負荷テストはテストピラミッドの一部です |
code-review | レビューではパフォーマンスの低下を確認します |
systematic-debugging | パフォーマンスの問題にも同じ調査方法を適用します |
acceptance-testing | パフォーマンスの目標を受け入れ基準にします |
スキルの種類
柔軟 — プロジェクトの状況に応じて最適化の深さを調整します。すべての最適化で、計測・特定・最適化・検証のサイクルを必ず実施してください。計測可能な改善につながらない変更は、元に戻してください。