2.

Web アプリが重いときの対処 — DB / キャッシュ / CDN / プロファイラの実践順

編集
この記事の要点
  • 計測 → 仮説 → 改善 → 計測 のループ。最初に必ずプロファイラで遅い箇所を特定
  • DB が原因のことが圧倒的多数: EXPLAIN でインデックス確認 → N+1 解消 → スロークエリログ
  • キャッシュ: Redis / Memcached でクエリ結果・セッション・レンダー結果をキャッシュ
  • CDN (CloudFront / Cloudflare): 静的ファイルとエッジキャッシュで TTFB 大幅短縮
  • 配信最適化: Gzip / Brotli 圧縮 / HTTP/2 or HTTP/3 / 画像 WebP・AVIF / lazy loading
  • 監視: APM (New Relic / Datadog) / Laravel Telescope / Debugbar

原則: まず計測する

「重い」と言われたら勘で改善しないこと。プロファイラ・APM・ブラウザ DevTools でどこに時間がかかっているかを特定してから打ち手を選びます。

区間計測方法典型的な原因
TTFB(サーバー初動)DevTools Network、curl -wDB / 外部 API / レンダリング
サーバー処理時間APM (New Relic / Datadog) / xhprof / Laravel TelescopeDB クエリ / N+1 / 外部 API
ダウンロード時間DevTools Network Waterfallファイルサイズ / 帯域 / 距離
レンダリングDevTools Performance / LighthouseJS 重い / Render-Blocking CSS

DB が原因(最多パターン)

Web アプリの遅さの 7 割は DB に起因します。優先順位はこの順:

1. インデックスがあるか EXPLAIN で確認

-- MySQL / PostgreSQL
EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND status = 'paid';

-- type=ALL や Seq Scan が出たらインデックス不足
-- type=ref / Index Scan を目指す

-- 複合インデックス作成
CREATE INDEX idx_orders_user_status ON orders (user_id, status);

-- 実際に使われたか
EXPLAIN ANALYZE SELECT ...;  -- PostgreSQL
EXPLAIN FORMAT=JSON SELECT ...; -- MySQL

2. N+1 クエリの解消

// ❌ N+1 (100 ユーザーなら 1 + 100 = 101 クエリ)
$users = User::all();
foreach ($users as $u) {
    echo $u->posts->count();   // SELECT * FROM posts WHERE user_id = ?
}

// ✅ Eager Loading (2 クエリ)
$users = User::with('posts')->get();
foreach ($users as $u) {
    echo $u->posts->count();
}

// ✅ さらに最適化(集計だけなら withCount)
$users = User::withCount('posts')->get();
foreach ($users as $u) {
    echo $u->posts_count;
}

3. スロークエリログ

# MySQL: 1 秒以上のクエリをログに
# my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

# PostgreSQL: log_min_duration_statement = 1000  (1 秒)

# 解析
mysqldumpslow -s t /var/log/mysql/slow.log | head -20
pt-query-digest /var/log/mysql/slow.log   # Percona Toolkit

キャッシュ層導入

レベル用途選択肢
クエリ結果重い集計・人気記事一覧Redis / Memcached
セッションユーザー認証情報Redis
レンダー結果HTML フラグメント全体Redis / Varnish
HTTP キャッシュAPI レスポンスCDN / Cache-Control
アプリ内同一リクエスト内memoize / static 変数
// Laravel: Cache::remember で 5 分キャッシュ
$articles = Cache::remember('articles.popular', 300, function () {
    return Article::orderBy('views', 'desc')->take(10)->get();
});

// 更新時にキャッシュ無効化
Cache::forget('articles.popular');

// タグ付け(Redis のみ)
Cache::tags(['articles'])->remember('popular', 300, fn() => ...);
Cache::tags(['articles'])->flush();

CDN と静的ファイル配信

  • CloudFront / Cloudflare / Fastly: 画像・CSS・JS をエッジから配信、TTFB を 100ms 以下に
  • Cache-Control: public, max-age=31536000, immutable: 1 年キャッシュ(ファイル名にハッシュを入れる)
  • Stale-While-Revalidate: 期限切れでも一旦古いものを返しつつバックグラウンド更新
  • S3 + CloudFront で安価な無限スケール配信

画像と転送量最適化



  
  
  hero



hero

サーバー圧縮 / HTTP/2 / HTTP/3

# nginx.conf
http {
    # Gzip
    gzip on;
    gzip_comp_level 6;
    gzip_types text/css application/javascript application/json text/html;

    # Brotli (要 ngx_brotli モジュール)
    brotli on;
    brotli_comp_level 6;
    brotli_types text/css application/javascript application/json text/html;

    server {
        listen 443 ssl http2;
        # listen 443 quic reuseport;  # HTTP/3
        # add_header Alt-Svc 'h3=":443"; ma=86400';
    }
}

コードレベルのプロファイリング

// PHP: xhprof / xdebug profiler / Tideways

// Laravel Telescope
composer require laravel/telescope --dev
php artisan telescope:install

// Laravel Debugbar (開発用)
composer require barryvdh/laravel-debugbar --dev
// → ブラウザ下部にバーが出て SQL / リクエスト時間 / メモリ全部見える

// 手動計測
$start = microtime(true);
$result = doHeavyTask();
$elapsed = microtime(true) - $start;
Log::info("doHeavyTask: {$elapsed}s");

APM (本番監視)

製品得意分野
New RelicPHP / Java / Ruby / Node の APM 老舗
DatadogAPM + Infra + Log 統合
Sentryエラートラッキング + Performance
TidewaysPHP 特化(プロダクションプロファイラ)
AWS X-RayAWS ネイティブ・分散トレース

フロントエンド最適化

  • Lighthouse で Core Web Vitals (LCP / INP / CLS) を計測
  • JS バンドル分割 (Code Splitting)、Tree Shaking、minify
  • Critical CSS インライン化、残りは defer
  • サードパーティスクリプト遅延読み込み (analytics.js, ads)
  • preconnect / preload / dns-prefetch を要所に

インフラスケール

  • DB のリードレプリカで読み取り分散
  • アプリサーバーの水平スケール (ALB + Auto Scaling)
  • 非同期処理 (キュー: SQS / Redis / RabbitMQ) で重い処理をバックグラウンドへ
  • セッションを Redis に外出しすることでアプリサーバーをステートレス化

FAQ

Q: 何から手をつければ良いか
A: APM か Telescope で「最も時間かかっている処理」を特定。8 割は DB クエリ起因なので、EXPLAIN とインデックスから着手すれば大体直ります。

Q: キャッシュ vs インデックス、どちらが先か
A: インデックスが先。基本クエリが速くなれば、無理にキャッシュを増やさなくても十分なケースが多いです。キャッシュは複雑化と一貫性問題を伴うので最後の手段。

Q: フロントが遅いのかバックが遅いのか分からない
A: ブラウザ DevTools の Network タブで、TTFB(リクエスト送信からレスポンスの最初の 1 バイトまで)を見てください。TTFB が大きければバック、ダウンロードや描画が大きければフロント・配信側の問題です。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. ブラウザゲームの公開
  2. 重い場合の対処法