バックエンド 2025.12.27

APIレスポンスが遅い!Laravel×MySQL最適化で処理速度3倍改善術

約9分で読めます

Webサービスの動作が重くてお困りではありませんか?LaravelとMySQLの最適化によりAPIレスポンス時間を大幅短縮する実践的な手法をご紹介します。

こんな悩みありませんか?

「Webサービスの動作が重くて、ユーザーから苦情が来ている...」 「データが増えるにつれて、管理画面の表示が遅くなってきた...」 「APIのレスポンスが遅くて、フロントエンドの動作に影響している...」

横浜のWeb制作会社「Fivenine Design」では、これまで20年以上にわたり多くのWebサービスのパフォーマンス改善を手がけてきました。特にLaravelとMySQLを使った案件では、適切な最適化により処理速度を3倍以上改善できるケースがほとんどです。

今回は、実際のプロジェクトで効果を実証した最適化手法を、中級レベルの技術者向けに詳しく解説します。

実案件での課題:ECサイトのレスポンス遅延

ある中小企業のECサイトで、こんな問題が発生していました:

  • 商品一覧APIのレスポンス時間:3.2秒
  • 注文履歴取得:2.8秒
  • 管理画面での売上データ表示:5秒以上

商品数が1万点を超えた頃から急激に遅くなり、「買い物カゴに入れるボタンを押しても反応が遅い」というユーザーからの声が増えていました。売上にも影響が出始めていた状況です。

原因調査で見つかった3つの問題

詳細な調査を行った結果、以下の問題が判明しました:

  1. N+1クエリ問題:商品一覧で各商品のカテゴリ情報を個別取得
  2. 非効率なJOIN処理:複数テーブルを結合する際のインデックス不備
  3. 不適切なページング:全件数取得のためのCOUNT処理

最適化手法1:Eloquentクエリの改善

N+1問題の解決

改善前のコード(問題あり):

// 商品一覧取得
$products = Product::paginate(20);

foreach ($products as $product) {
    // 各商品ごとにクエリが実行される(N+1問題)
    echo $product->category->name;
    echo $product->reviews->count();
}

改善後のコード:

// Eager Loadingで一度にデータを取得
$products = Product::with(['category', 'reviews'])
    ->select(['id', 'name', 'price', 'category_id'])
    ->paginate(20);

foreach ($products as $product) {
    echo $product->category->name;
    echo $product->reviews->count();
}

この改善により、商品一覧のクエリ実行回数が21回から3回に削減されました。

集計クエリの最適化

改善前:

// 各商品のレビュー数を個別に取得
$products = Product::all();
foreach ($products as $product) {
    $product->review_count = $product->reviews()->count();
}

改善後:

// withCount()で一度に集計
$products = Product::withCount('reviews')
    ->orderBy('reviews_count', 'desc')
    ->paginate(20);

最適化手法2:データベースインデックスの追加

複合インデックスの戦略的配置

商品検索でよく使われるクエリパターンを分析し、以下のインデックスを追加しました:

-- 商品検索用の複合インデックス
ALTER TABLE products 
ADD INDEX idx_search (category_id, status, created_at);

-- 注文履歴取得用
ALTER TABLE orders 
ADD INDEX idx_user_orders (user_id, created_at DESC);

-- レビュー集計用
ALTER TABLE reviews 
ADD INDEX idx_product_rating (product_id, rating);

インデックスの効果測定

MySQLのEXPLAINを使って、クエリ実行計画を確認:

EXPLAIN SELECT * FROM products 
WHERE category_id = 1 
AND status = 'active' 
ORDER BY created_at DESC 
LIMIT 20;

インデックス追加後、rowsの値が10,000から20に削減され、大幅な改善が確認できました。

最適化手法3:キャッシュ戦略の導入

Redisを活用したクエリキャッシュ

use Illuminate\Support\Facades\Cache;

class ProductService
{
    public function getPopularProducts($categoryId = null)
    {
        $cacheKey = "popular_products_{$categoryId}";
        
        return Cache::remember($cacheKey, 3600, function () use ($categoryId) {
            return Product::withCount('reviews')
                ->when($categoryId, function ($query, $categoryId) {
                    return $query->where('category_id', $categoryId);
                })
                ->orderBy('reviews_count', 'desc')
                ->limit(10)
                ->get();
        });
    }
}

適切なキャッシュ無効化

// 商品更新時にキャッシュをクリア
class ProductObserver
{
    public function updated(Product $product)
    {
        Cache::forget("popular_products_{$product->category_id}");
        Cache::forget('popular_products_');
    }
}

実装時の注意点とよくある失敗

1. 過度なEager Loadingは逆効果

「とりあえず全部withしておけば安心」と考えがちですが、不要なデータまで取得してしまうと、むしろパフォーマンスが悪化します。

避けるべき例:

// 必要以上にデータを取得してしまう
$products = Product::with([
    'category', 'reviews', 'images', 'variants', 'tags'
])->get();

2. インデックスの重複と肥大化

似たようなインデックスを複数作成すると、INSERT/UPDATE処理が遅くなります。定期的な見直しが重要です。

3. キャッシュの適切な有効期限設定

データの更新頻度に応じて、キャッシュ時間を調整する必要があります:

  • 商品マスタ:1時間
  • 在庫情報:5分
  • リアルタイム性が重要なデータ:キャッシュなし

最適化後の結果

3つの手法を組み合わせた結果、以下の改善を達成しました:

  • 商品一覧API:3.2秒 → 0.8秒(4倍改善
  • 注文履歴取得:2.8秒 → 0.6秒(4.7倍改善
  • 管理画面表示:5秒 → 1.2秒(4.2倍改善

クライアントからは「お客様から『サイトが軽くなった』との声をいただくようになった」「管理業務の効率が大幅に向上した」と喜びの声をいただいています。

まず始めるべき3つのアクション

  1. 現状分析:LaravelのQuery Logを有効にして、遅いクエリを特定する
  2. N+1問題の確認:Laravel Debugbarでクエリ実行回数をチェック
  3. 基本的なインデックス追加:WHERE句でよく使うカラムにインデックスを設定
// config/logging.phpでSQLログを有効化
'channels' => [
    'sql' => [
        'driver' => 'daily',
        'path' => storage_path('logs/sql.log'),
    ],
],

パフォーマンス改善は、ユーザー体験の向上だけでなく、売上やコンバージョン率の改善に直結します。技術的な課題でお困りの際は、20年以上の実績を持つFivenine Designにお気軽にご相談ください。お客様のWebサービスを次のレベルへと導きます。

この記事をシェア