LoginSignup
10
7

More than 1 year has passed since last update.

[Laravel] パフォーマンス改善策 メモ

Last updated at Posted at 2021-06-23

はじめに

この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。

今回は、Laravelに関するパフォーマンス改善策について調べたので、まとめておきたいと思います。(新しい情報を見つけ次第、随時更新します)

間違いなどがございましたら、ご指摘のほどよろしくお願い致します。

パフォーマンス改善策について

Laravelというフレームワークは、様々なインターフェース等を利用することで比較的簡単に自分が実装したい機能を実現できてしまいます。この比較的簡単に実装できてしまう点に落とし穴があり、開発者がパフォーマンス面を意識して開発しなければパフォーマンスが落ちてしまいます。

基本的には、公式ドキュメントに沿ってコードを記述すべきですが、ドキュメントの各項目に書かれているコードにどのような役割があり、なぜそのような機能があるのかを理解した上でコードを書くことが重要になってくると思われます。

PHPアプリケーションでは、4つの段階に分けてパフォーマンスを改善できるという情報を目にしました。

4つの段階とは
・言語レベル
・フレームワークレベル
・インフラレベル
・ハードウエアレベル

今回は、PHPレベルの改善策(opcache)Laravelフレームワークレベルの改善策についてまとめたいと思います。

PHPレベルの改善策(OPcache)

OPcacheとは

OPcache はコンパイル済みのバイトコードを共有メモリに保存し、PHP がリクエストのたびにスクリプトを読み込み、パースする手間を省くことでパフォーマンスを向上させます。
引用:PHPマニュアル OPcache

本番環境時にphp.iniでOPcacheを有効にします。
ソースコードを変更してもサーバーを再起動しないと変更が反映されなくなるため開発環境時は使用しない方が良いと思います。

一般的なphp.iniの設定

php.ini
[opcache]
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.validate_timestamps = 0
opcache.huge_code_pages = 0
opcache.preload_user = rootユーザー以外を指定する

その他の設定はPHPマニュアル OPcache 実行時設定を参照してください。

Laravelの初期起動を改善する

Laravelの初期起動を改善するには、DIServiceProviderに関する理解が必要となります。
私自身がすぐに復習できるよう簡単にまとめておきます。

DIとは
「Dependency Injection」の略語であり、クラス間およびオブジェクト間の依存関係を外側からコントロールする仕組みのことです。
Laravelを使う場合は、基本的にはDIを利用すべきだと思います。
参考記事
DI・DIコンテナ、ちゃんと理解出来てる・・?
最高にわかるDIコンテナ(特にPHPにフォーカスした
Laravelの依存性の注入(DI)を具体的にどう使うのか

ServiceProviderとは

サービスプロバイダは、Laravelアプリケーション全体の起動処理における、初めの心臓部です。皆さんのアプリケーションと同じく、Laravelのコアサービス全部もサービスプロバイダを利用し、初期起動処理を行っています。
ほとんどのプロバイダは、すべてのリクエストで必ずロードされるとは限らず、そのプロバイダが提供するサービスが、実際に必要なときにのみロードされる「遅延」プロバイダです。
引用:laravel6.x サービスプロバイダ

DIのために用いられるLaravelの機能で、依存解決の部分を実装するために用いられます。
DIの量が増えるとServiceProviderの仕事量が増える仕組みとなっています。
ServiceProviderの起動は、全ての処理実行前に実施されるので、ServiceProviderの仕事量が増加すると、その分処理速度が遅くなってしまうといった側面を持っています。

この解決策として、「遅延」プロバイダが存在し、必要になったときに遅延解決する仕組みになっています。

DBへの接続回数を最小限にする

DBへの接続回数を最小限にする上で、重要となる知識がEloquentです。
Eloquentに関する知識はリンク先を参照してください。

DBへの接続回数を最小限にするポイントは、どれだけselectの回数を最小限にすることができるかだと思います。

下記にて、selectの回数を最小限に抑える解決策として比較的簡単に取り入れることができるN+1問題の解決策をまとめておきます。

N+1問題とは
リレーションを保持しているテーブルのデータを取得する際、
対象となるテーブルのデータを全件取得するのにSQLを1回
取得したデータに紐付く情報(リレーション先のデータ)を取得するのにSQLをN回
といったように、1 + N 回のSQLを発行する必要性が出てきます。

このように、対象となるテーブルのデータ取得件数が増加するほど、紐付く側のデータを取得するSQLの発行回数が増えてしまうことを「N+1問題」といいます。

N+1問題のコード例

public function index()
{
    $users = User::limit(4)->get();
    return view('user.index',compact('users'));
}

//実行結果
//usersのデータに紐づいているpostsデータを取得するSQLがusersの取得件数分だけ発行される
//このようにuserデータ4件を取得するSQLを1回発行して、追加でuserデータに紐付くデータを取得するSQLがuserデータ分発行されるの(例だと4回発行する)で、結果として5回発行されることになる(1+4)
select * from users limit 4
select * from posts where posts.user_id = 1
select * from posts where posts.user_id = 2
select * from posts where posts.user_id = 3
select * from posts where posts.user_id = 4

解決策
1回のSQLで、データに紐付く情報(リレーション先のデータ)を全て取得することができれば、パフォーマンスの改善が期待できると思います。
LaravelにおけるN+1問題に対する解決策として、withメソッドloadメソッドが存在します。

loadメソッドとは

すでに親のモデルを取得した後に、リレーションをEagerロードする必要がある場合もあるでしょう。たとえば、どの関連しているモデルをロードするかを動的に決める場合に便利です。
引用:laravel6.x リレーション 遅延Eagerロード

loadメソッドは、遅延Eagerロード(Lazy Eager Loading)と呼ばれます。
EagerLoadingという機能で発行するSQLを少なくして効率的にDBからデータを取得するメソッドです。

withメソッドとは
withメソッドは、loadメソッドと同じでEagerLoadingという機能で発行するSQLを少なくして効率的にDBからデータを取得するメソッドです。
参考文献:laravel リレーション Eagerロード

withメソッドを使用した解決策例

public function index()
{
    $users = User::with('posts')->limit(4)->get();
    return view('user.index',compact('users'));
}

//実行結果
//下記のように1つのSQLで複数の紐づいたデータを取得できるようになる
select * from users limit 4
select * from posts where posts.user_id in (1, 2, 3, 4)

未使用のAutoloaded Service Providersを削除する

Laravelは起動時に大量のサービスをオートロードします。これらはconfig/app.phpファイルの'providers'配列キーの一部として様々なサービスが存在しています。

下記のように、数多くのService Providersがデフォルトで設定されています。

config/app.php
    /*
    |--------------------------------------------------------------------------
    | Autoloaded Service Providers
    |--------------------------------------------------------------------------
    |
    | The service providers listed here will be automatically loaded on the
    | request to your application. Feel free to add your own services to
    | this array to grant expanded functionality to your applications.
    |
    */

    'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,

        /*
         * Package Service Providers...
         */

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

    ],

注意点
未使用のService Providersを削除することは好ましいことではあるが、やみくもにコメントアウトして、本番にプッシュしてはいけない。
すべてのテストを実行し、手動でチェックすることでエラーが発生しないことを確認してから本番にプッシュすべきです。

可能な限りキャッシュを使用する

キャッシュを使うとDBへの負荷を削減する事が可能になります。

データを、キャッシュから取得する事によって、DBへのリクエストを減らし、結果的に負荷が削減できるようになります。

また、複雑なクエリなどが多い場合は、その結果をキャッシュさせる事によってデータの読み込みが高速化され、リクエスト処理も高速化できるようになります。

Laravelは読み書きしやすい、多くのキャッシュシステムに対する統一したAPIを提供します。キャッシュの設定は、config/cache.phpで指定します。アプリケーション全体のデフォルトとして使用するキャッシュドライバをこのファイルの中で指定します。MemcachedやRedisなど、人気のあるキャッシュシステムをLaravelは最初からサポートしています。
引用:laravel6.x キャッシュ

詳細はリンク先を確認する。

php artisan config:cache

php artisan config:cacheとは

アプリケーションをスピードアップさせるために、全設定ファイルを一つのファイルへまとめる、config:cache Artisanコマンドを使ってください。これによりアプリケーションの全設定ファイルのオプションが、単一のファイルに結合され、フレームワークが素早くロードできるようになります。
引用:laravel6.x 設定 設定キャッシュ

アプリケーションをプロダクションへデプロイする場合、デプロイプロセスの中で、確実にconfig:cache Artisanコマンドを実行してください。
このコマンドは、Laravelの全設定ファイルをキャッシュされる一つのファイルへまとめるため、設定値をロードする場合に、フレームワークがファイルシステムを数多くアクセスする手間を大いに減らします。
引用:laravel6.x デプロイ

使用する際の注意点
・ローカルでの開発中にこのコマンドを実行する場合は要注意
・可能な限り本番環境でのみ行う
・実行後、何か問題が発生した場合は、php artisan cache:clearで設定を元に戻す

開発中にconfig:cacheコマンドを実行する場合

開発過程の一環としてconfig:cacheコマンド実行を採用する場合は、必ずenv関数を設定ファイルの中だけで使用してください。設定ファイルがキャッシュされると、.envファイルはロードされなくなり、env関数の呼び出しはすべてnullを返します。
引用:laravel6.x 設定 設定キャッシュ

開発中にphp artisan config:cacheを使うと[.env]が読み込めず内容がnullになってしまう可能性があることを覚えておく必要がありそうです。

また、configファイルが変更されるたびにコマンドを実行することを忘れないでください。

キャッシュをクリアするには、下記のコマンドを使用します。

$ php artisan config:clear

php artisan route:cache

アプリケーションの設定と同様に、ルートは時間の経過とともにあまり変化しないので、キャッシュの理想的な候補となります。

routesファイルが変更されるたびにコマンドを実行することを忘れないでください。

キャッシュをクリアするには、下記のコマンドを使用します。

$ php artisan route:clear

注意点

この機能はPHPのシリアライゼーション機能を使用するため、アプリケーションの全ルートをキャッシュするには、コントローラベースのルート定義だけを使用してください。PHPはクロージャをシリアライズできません。
引用:laravel6.x デプロイ

画像を扱う場合の最適化

画像は帯域幅を最も消費するものであり、アプリケーションやウェブサイトが遅くなる最大の原因の1つでもあります。アップロードされた画像を単純にサーバに保存し、HTTPレスポンスで送り返すだけでは、パフォーマンスの最適化に反してしまいます。

AWS S3へ画像をアップロードするなど、別の形で画像を扱うべきです。

queuesを使いパフォーマンスを向上させる

Laravelのキューサービスは、Beanstalk、Amazon SQS、Redis、さらにはリレーショナル・データベースなどさまざまなキューバックエンドに対し共通のAPIを提供しています。キューによりメール送信のような時間を費やす処理を遅らせることが可能です。時間のかかるタスクを遅らせることで、よりアプリケーションのリクエストをドラマチックにスピードアップできます。


キューの設定ファイルはconfig/queue.phpです。このファイルにはフレームワークに含まれているそれぞれのドライバーへの接続設定が含まれています。それにはデータベース、Beanstalkd、Amazon SQS、Redis、ジョブが即時に実行される同期(ローカル用途)ドライバーが含まれています。 nullキュードライバはキューされたジョブが実行されないように、破棄します。
引用:laravel6.x キュー


キューを設定せずに、メール機能を実装(同期処理)すると、メールの「送信する」ボタンを押して次の画面が表示されるまで数秒の待ち時間が発生します。

このようなパフォーマンスを改善する策として、Laravel側でのメール送信を「キュー」と呼ばれる仕組みを使った非同期処理が存在します。キューに関する詳細はリンク先を確認してください。

おわりに

今回は、パフォーマンスの改善策について調べた情報が溜まっていたので、1つの記事としてまとめてみました。記事が完成したので、内容を参考にして実際にパフォーマンスの改善につながるかどうか試してみたいと思います。エラー等が発生した場合、別の記事でまとめたいと思います...。

新しい情報を見つけ次第、随時更新していきます。

参考文献

PHPマニュアル OPcache
laravel6.x キュー
laravel6.x デプロイ
laravel6.x リレーション 遅延Eagerロード
laravel リレーション Eagerロード
How to Optimize PHP Laravel Web Application for High Performance?
12 Tips for Laravel Performance Optimization in 2020
サイトを37倍に高速化した7つの手法
Laravel を高速化というか最適化する

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7