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

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

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

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


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

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

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

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

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

##PHPレベルの改善策(OPcache)
``OPcacheとは``
>
OPcache はコンパイル済みのバイトコードを共有メモリに保存し、PHP がリクエストのたびにスクリプトを読み込み、パースする手間を省くことでパフォーマンスを向上させます。
``引用:``[PHPマニュアル OPcache](https://www.php.net/manual/ja/intro.opcache.php)

本番環境時に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 実行時設定](https://www.php.net/manual/ja/opcache.configuration.php)を参照してください。

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

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

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

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

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

##DBへの接続回数を最小限にする
DBへの接続回数を最小限にする上で、重要となる知識が``Eloquent``です。
Eloquentに関する知識は[リンク先](https://readouble.com/laravel/6.x/ja/eloquent.html)を参照してください。

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

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


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

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

``N+1問題のコード例``

```php
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ロード](https://readouble.com/laravel/6.x/ja/eloquent-relationships.html#lazy-eager-loading)

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

``withメソッドとは``
withメソッドは、loadメソッドと同じでEagerLoadingという機能で発行するSQLを少なくして効率的にDBからデータを取得するメソッドです。
``参考文献:``[laravel リレーション Eagerロード](https://readouble.com/laravel/6.x/ja/eloquent-relationships.html#eager-loading)



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

```php
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 キャッシュ](https://readouble.com/laravel/6.x/ja/cache.html)

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

##php artisan config:cache
``php artisan config:cacheとは``
>
アプリケーションをスピードアップさせるために、全設定ファイルを一つのファイルへまとめる、config:cache Artisanコマンドを使ってください。これによりアプリケーションの全設定ファイルのオプションが、単一のファイルに結合され、フレームワークが素早くロードできるようになります。
``引用:``[laravel6.x 設定 設定キャッシュ](https://readouble.com/laravel/6.x/ja/configuration.html#:~:text=php%20artisan%20config%3Acache)

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

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

``開発中にconfig:cacheコマンドを実行する場合``
>
開発過程の一環としてconfig:cacheコマンド実行を採用する場合は、必ずenv関数を設定ファイルの中だけで使用してください。設定ファイルがキャッシュされると、.envファイルはロードされなくなり、env関数の呼び出しはすべてnullを返します。
``引用:``[laravel6.x 設定 設定キャッシュ](https://readouble.com/laravel/6.x/ja/configuration.html#:~:text=php%20artisan%20config%3Acache)

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

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

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

```
$ php artisan config:clear
```


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

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

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

```
$ php artisan route:clear
```

``注意点``
>
この機能はPHPのシリアライゼーション機能を使用するため、アプリケーションの全ルートをキャッシュするには、コントローラベースのルート定義だけを使用してください。PHPはクロージャをシリアライズできません。
``引用:``[laravel6.x デプロイ ](https://readouble.com/laravel/6.x/ja/deployment.html)

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

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

##queuesを使いパフォーマンスを向上させる
>
Laravelのキューサービスは、Beanstalk、Amazon SQS、Redis、さらにはリレーショナル・データベースなどさまざまなキューバックエンドに対し共通のAPIを提供しています。キューによりメール送信のような時間を費やす処理を遅らせることが可能です。時間のかかるタスクを遅らせることで、よりアプリケーションのリクエストをドラマチックにスピードアップできます。
<br>
キューの設定ファイルはconfig/queue.phpです。このファイルにはフレームワークに含まれているそれぞれのドライバーへの接続設定が含まれています。それにはデータベース、Beanstalkd、Amazon SQS、Redis、ジョブが即時に実行される同期（ローカル用途）ドライバーが含まれています。 nullキュードライバはキューされたジョブが実行されないように、破棄します。
``引用:``[laravel6.x キュー](https://readouble.com/laravel/6.x/ja/queues.html)

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

このようなパフォーマンスを改善する策として、Laravel側でのメール送信を「キュー」と呼ばれる仕組みを使った**非同期処理**が存在します。キューに関する詳細は[リンク先](https://readouble.com/laravel/6.x/ja/queues.html)を確認してください。

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

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



##参考文献
[PHPマニュアル OPcache](https://www.php.net/manual/ja/book.opcache.php)
[laravel6.x キュー](https://readouble.com/laravel/6.x/ja/queues.html)
[laravel6.x デプロイ ](https://readouble.com/laravel/6.x/ja/deployment.html)
[laravel6.x リレーション 遅延Eagerロード](https://readouble.com/laravel/6.x/ja/eloquent-relationships.html#lazy-eager-loading)
[laravel リレーション Eagerロード](https://readouble.com/laravel/6.x/ja/eloquent-relationships.html#eager-loading)
[How to Optimize PHP Laravel Web Application for High Performance?](https://geekflare.com/laravel-optimization/)
[12 Tips for Laravel Performance Optimization in 2020](https://www.cloudways.com/blog/laravel-performance-optimization/)
[サイトを37倍に高速化した7つの手法](https://allabout-tech.hatenablog.com/entry/2016/09/28/094600)
[Laravel を高速化というか最適化する](https://localdisk.hatenablog.com/entry/2015/12/05/202228)