Laravelは多くのものです。しかし、高速はそれらの1つではありません。それをより速くするために貿易のいくつかのトリックを学びましょう!
PHP開発者が手つかずの人はいない ララヴェル 最近。彼らは、Laravelが提供する迅速な開発を愛するジュニアまたはミッドレベルの開発者であるか、市場のプレッシャーのためにLaravelを学ぶことを余儀なくされている上級開発者です.
どちらにしても、LaravelがPHPエコシステムを活性化したことは否定できません(確かに、Laravelがなかったら、はるか昔にPHPの世界を去っていたでしょう)。.
Laravelからの(ある程度正当化された)自己称賛の抜粋
ただし、Laravelは物事を簡単にするために後方に曲がっているため、開発者として快適な生活を送るために、その下では膨大な量の作業を行っています。機能しているように見えるLaravelのすべての「魔法の」機能には、機能が実行されるたびに修正する必要があるコードのレイヤーが何層にもあります。単純な例外トレースでも、ウサギの穴の深さを追跡します(エラーの発生場所に注意し、メインカーネルに至るまで)。
いずれかのビューでコンパイルエラーのように見えるものについて、トレースする18個の関数呼び出しがあります。私は40歳ですが、他のライブラリやプラグインを使用している場合は、さらに多くなる可能性があります。.
ポイントは、デフォルトでは、このレイヤーがコードのレイヤーを重ねているため、Laravelが遅くなることです.
ララヴェルはどのくらい遅い?
正直なところ、いくつかの理由でこの質問に答えることは明らかに不可能です.
最初, Webアプリの速度を測定するための、受け入れられた客観的で実用的な標準はありません。何に比べて速いですか、遅いですか?どのような条件下で?
二番目, Webアプリは非常に多くの要素(データベース、ファイルシステム、ネットワーク、キャッシュなど)に依存しているため、速度について話すのは馬鹿げています。データベースが非常に遅い非常に高速なWebアプリは、非常に遅いWebアプリです。
しかし、この不確実性がまさにベンチマークが人気の理由です。それらは何も意味しませんが(参照 この そして この)、それらは参照のフレームを提供し、私たちが怒るのを助けます。したがって、ソルトのピンチがいくつか用意されているので、PHPフレームワーク間の速度の誤った大まかな考えを取得しましょう.
このかなり立派なGitHubで行く ソース, 比較すると、PHPフレームワークは次のようになります。
ケースをテールの最後にキャストしない限り、ここでLaravelに気付くことはありません(本当の目を凝らしていても)。はい、親愛なる友人、ララヴェルが最後に来ます!当然のことながら、これらの「フレームワーク」のほとんどはあまり実用的でも有用でもありませんが、Laravelが他の人気のフレームワークと比べてどれほど遅いかがわかります。.
通常、私たちの日常のWebアプリが高い数値に達することはめったにないため、この「遅さ」はアプリケーションでは機能しません。しかし、それらが実行すると(たとえば、同時実行数が200〜500を超えると)、サーバーは窒息し、死に始めます。より多くのハードウェアを問題に投入しても問題が解決しないときであり、インフラストラクチャのコストが非常に速く上昇するため、クラウドコンピューティングの高い理想が打ち砕かれます.
でもねえ、元気を!この記事では、何ができないかについてではなく、何ができるかについて説明します。
良いニュースは、Laravelアプリを高速化するために多くのことができるということです。数倍速い。はい、冗談ではありません。同じコードベースを弾道化して、インフラストラクチャ/ホスティングの請求額を毎月数百ドル節約できます。どうやって?それを始めましょう.
4種類の最適化
私の意見では、最適化は4つの異なるレベルで実行できます(PHPアプリケーションに関しては)。
- 言語レベル: これは、より高速なバージョンの言語を使用し、コードを遅くする言語でのコーディングの特定の機能/スタイルを回避することを意味します.
- フレームワークレベル: これらは、この記事で取り上げるものです.
- インフラストラクチャレベル: PHPプロセスマネージャー、Webサーバー、データベースなどのチューニング.
- ハードウェアレベル: より良い、より速く、より強力なハードウェアホスティングプロバイダーへの移行.
これらのタイプの最適化はすべてその場所にあります(たとえば、php-fpm最適化は非常に重要で強力です)。しかし、この記事の焦点は純粋にタイプ2の最適化です:フレームワークに関連するもの.
ちなみに、番号付けの根拠はなく、標準として認められていません。私はこれらを作りました。私を引用して「サーバーにタイプ3の最適化が必要です」と言わないでください。チームリードがあなたを殺し、私を見つけて、私も殺します。
そしてついに、約束の地に到着.
Contents
n + 1データベースクエリに注意してください
ORMを使用する場合、n + 1クエリの問題はよくある問題です。 LaravelにはEloquentと呼ばれる強力なORMがあります。これは非常に美しく、便利なので、何が起こっているのかを忘れてしまいがちです。.
非常に一般的なシナリオを考えてみましょう:特定の顧客リストによって行われたすべての注文のリストを表示します。これは、eコマースシステムや、一部のエンティティに関連するすべてのエンティティを表示する必要がある一般的なレポートインターフェイスではかなり一般的です。.
Laravelでは、次のような働きをするコントローラー関数を想像するかもしれません:
クラスOrdersControllerはControllerを拡張します
{
// …
public function getAllByCustomers(Request $ request、array $ ids){
$ customers = Customer :: findMany($ ids);
$ orders = collect(); // 新しいコレクション
foreach($ customers as $ customer){
$ orders = $ orders->merge($ customer->注文);
}
return view( ‘admin.reports.orders’、[‘orders’ => $ orders]);
}
}
甘い!さらに重要なのは、エレガントで美しいことです。
残念ながら、これはLaravelでコードを記述する悲惨な方法です.
ここに理由があります.
ORMに特定の顧客を探すように依頼すると、次のようなSQLクエリが生成されます。
SELECT * FROM Customers WHERE id IN(22、45、34、。。);
予想通りです。その結果、返されたすべての行は、コントローラー関数内の$ customersコレクションに格納されます。.
次に、各顧客を1つずつループして、注文を取得します。これは次のクエリを実行します . . .
SELECT * FROM WHERE WHERE customer_id = 22;
. . . 顧客の数だけ.
つまり、1000人の顧客の注文データを取得する必要がある場合、実行されるデータベースクエリの総数は1(すべての顧客のデータを取得する場合)+ 1000(各顧客の注文データを取得する場合)= 1001になります。これはn + 1という名前の由来です.
もっと上手くできる?もちろん! Eager Loadingと呼ばれるものを使用することで、ORMに強制的にJOINを実行させ、必要なすべてのデータを1つのクエリで返すことができます。このような:
$ orders = Customer :: findMany($ ids)->with( ‘orders’)->取得する();
結果のデータ構造は確かにネストされたものですが、注文データは簡単に抽出できます。この場合、結果の単一クエリは次のようになります。
SELECT * FROM Customers INNER JOIN注文ON Customers.id = orders.customer_id WHERE Customers.id IN(22、45、。。。);
もちろん、1つのクエリは1000の余分なクエリよりも優れています。 10,000人の顧客が処理するとしたらどうなるか想像してみてください。または、すべての注文に含まれるアイテムも表示したい場合は、神が禁止します!覚えておいてください、テクニックの名前は熱心なロードであり、ほとんど常に良いアイデアです.
構成をキャッシュする!
Laravelの柔軟性の理由の1つは、フレームワークの一部である多数の構成ファイルです。画像の保存方法/場所を変更したい?
(少なくとも執筆時点で)config / filesystems.phpファイルを変更するだけです。複数のキュードライバーを操作したいですか? config / queue.phpに自由に記述してください。数えたところ、フレームワークのさまざまな側面に対応する13個の構成ファイルがあり、何を変更したい場合でもがっかりしないようにしています。.
PHPの性質上、新しいWebリクエストが着信するたびに、Laravelはウェイクアップしてすべてを起動し、これらすべての構成ファイルを解析して、今回は異なる方法で実行する方法を理解します。過去数日間に何も変更されていない場合は愚かです!すべてのリクエストで設定を再構築することは無駄であり(実際には回避する必要があります)、その方法はLaravelが提供する簡単なコマンドです。
php artisan config:cache
これは、使用可能なすべての構成ファイルを1つのファイルに結合することであり、キャッシュは高速検索のためのどこかにあります。次回Webリクエストがあったとき、Laravelはこの単一のファイルを読み取って処理を開始します.
とは言っても、構成のキャッシュは非常にデリケートな操作であり、あなたの顔を爆発させる可能性があります。最大の落とし穴は、このコマンドを発行すると、構成ファイルを除くすべての場所からのenv()関数呼び出しがnullを返すことです。!
あなたがそれについて考えるとき、それは理にかなっています。構成キャッシュを使用する場合、フレームワークに次のように伝えます。「何を知っているのか、問題なく設定できているので、変更したくないと100%確信しています。」言い換えると、環境は静的なままであると想定しています。これが.envファイルの目的です。.
そうは言っても、ここに、構成のキャッシングに関するいくつかの鉄に覆われた、神聖な、破壊できない規則があります。
- 本番システムでのみ行う.
- あなたが本当に、本当に設定を凍結したいという確信がある場合にのみ、それを実行してください.
- 何か問題が発生した場合は、php artisan cache:clearで設定を元に戻します
- ビジネスへの被害がそれほど大きくないことを祈る!
自動ロードされるサービスを減らす
役立つために、Laravelはウェイクアップしたときに大量のサービスをロードします。これらは、「providers」配列キーの一部としてconfig / app.phpファイルで使用できます。私が私の場合に持っているものを見てみましょう:
/ *
|————————————————————————–
|自動ロードされたサービスプロバイダー
|————————————————————————–
|
|ここにリストされているサービスプロバイダーは自動的にロードされます
|アプリケーションにリクエストします。自由に独自のサービスを追加してください
|この配列は、アプリケーションに拡張機能を付与します.
|
* /
‘プロバイダー’ => [
/ *
* Laravelフレームワークサービスプロバイダー…
* /
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,
/ *
*パッケージサービスプロバイダー…
* /
/ *
*アプリケーションサービスプロバイダー…
* /
App \ Providers \ AppServiceProvider :: class,
App \ Providers \ AuthServiceProvider :: class,
// App \ Providers \ BroadcastServiceProvider :: class,
App \ Providers \ EventServiceProvider :: class,
App \ Providers \ RouteServiceProvider :: class,
],
もう一度、私は数えて、27のサービスがリストされています!今、あなたはそれらすべてを必要とするかもしれませんが、それはほとんどありません.
たとえば、私はたまたまREST APIを作成しています。つまり、セッションサービスプロバイダーやビューサービスプロバイダーなどは必要ありません。また、フレームワークのデフォルトに従っていないため、いくつかのことを自分で行っているので、認証サービスプロバイダー、ページネーションサービスプロバイダー、翻訳サービスプロバイダーなどを無効にすることもできます。全体として、これらのほぼ半分は私のユースケースでは不要です.
アプリケーションを長く、しっかりと見てください。これらすべてのサービスプロバイダーが必要ですか?しかし、神のために、これらのサービスを盲目的にコメントアウトして本番環境にプッシュしないでください。すべてのテストを実行し、開発マシンとステージングマシンで手動で確認し、トリガーを引く前に非常に偏執的です。
ミドルウェアスタックを賢く使う
着信Webリクエストのカスタム処理が必要な場合は、新しいミドルウェアを作成するのが答えです。さて、app / Http / Kernel.phpを開き、ミドルウェアをWebまたはAPIスタックに貼り付けたくなります。そうすることで、アプリ全体で利用できるようになり、邪魔な操作(ロギングや通知など)を行わない場合に.
ただし、アプリが成長するにつれ、ビジネス上の理由がなくても、すべての(または過半数の)リクエストがすべてのリクエストに存在する場合、このグローバルミドルウェアのコレクションはアプリに負担をかけることになります。.
つまり、新しいミドルウェアを追加/適用する場所に注意してください。グローバルに何かを追加する方が便利な場合がありますが、パフォーマンスのペナルティは長期的には非常に高くなります。新しい変更があるたびにミドルウェアを選択的に適用する場合に経験する必要がある痛みを知っていますが、それは私が喜んで受け入れ、推奨する痛みです!
ORMを回避する(時々)
EloquentはDBインタラクションの多くの側面を楽しいものにしますが、速度は犠牲になります。 ORMはマッパーであるため、データベースからレコードをフェッチするだけでなく、モデルオブジェクトをインスタンス化して列データでハイドレート(入力)する必要があります.
したがって、単純な$ users = User :: all()を実行し、10,000ユーザーがいる場合、フレームワークはデータベースから10,000行をフェッチし、内部で10,000の新しいUser()を実行して、プロパティに関連データを入力します。これは裏で行われている膨大な量の作業であり、データベースがアプリケーションのボトルネックになっている場合は、ORMをバイパスすることをお勧めします。.
これは、複雑なSQLクエリに特に当てはまります。複雑なSQLクエリでは、多くのフープを飛ばしてクロージャを作成し、それでも効率的なクエリを作成する必要があります。このような場合は、DB :: raw()を実行し、クエリを手動で書き込むことをお勧めします.
で行きます この 単純な挿入の場合でも、パフォーマンスの調査では、レコード数が増えるとEloquentの速度が大幅に低下します。
可能な限りキャッシュを使用する
Webアプリケーション最適化の秘訣の1つはキャッシングです.
初心者の場合、キャッシュとは、高価な結果(CPUとメモリの使用量の点で高価)を事前に計算して保存し、同じクエリが繰り返されたときに結果を返すことを意味します.
たとえば、eコマースストアでは、200万個の製品に出くわす可能性があります。ほとんどの場合、特定の価格帯で、特定の年齢層の新鮮な商品に関心があります。この情報についてデータベースにクエリを実行するのは無駄です。クエリは頻繁に変更されないため、これらの結果をすばやくアクセスできる場所に保存することをお勧めします.
Laravelには、いくつかのタイプのサポートが組み込まれています キャッシング. キャッシングドライバーを使用してゼロからキャッシングシステムを構築することに加えて、いくつかのLaravelパッケージを使用して、 モデルのキャッシュ, クエリキャッシング, 等.
ただし、特定の単純化された使用例を超えて、事前に構築されたキャッシングパッケージは、解決する以上の問題を引き起こす可能性があることに注意してください.
インメモリキャッシングを優先
Laravelに何かをキャッシュするとき、キャッシュする必要がある結果の計算を保存する場所のいくつかのオプションがあります。これらのオプションは、 キャッシュドライバー. したがって、キャッシュの結果を保存するためにファイルシステムを使用することは可能であり、完全に合理的ですが、それは実際にキャッシュが意図するものではありません.
理想的には、Redis、Memcached、MongoDBなどのインメモリ(完全にRAMに常駐)キャッシュを使用して、より高い負荷でキャッシュがボトルネックになるのではなく、重要な用途に役立つようにします.
さて、SSDディスクを使用することは、RAMスティックを使用することとほとんど同じであると思うかもしれませんが、それだけではありません。非公式でも ベンチマーク 速度に関しては、RAMがSSDを10〜20倍上回ることを示しています.
キャッシングに関して私のお気に入りのシステムはRedisです。それは 途方もなく速い (1秒あたり100,000回の読み取り操作が一般的)、非常に大規模なキャッシュシステムの場合、 集まる 簡単に.
ルートをキャッシュする
アプリケーション構成と同様に、ルートは時間の経過とともにあまり変化せず、キャッシングの理想的な候補です。これは、私のような大きなファイルを我慢できず、web.phpとapi.phpをいくつかのファイルに分割してしまう場合に特に当てはまります。単一のLaravelコマンドで、利用可能なすべてのルートがまとめられ、将来のアクセスのためにそれらが手軽に保管されます。
php artisan route:cache
ルートを追加または変更する場合は、次のようにしてください。
php artisan route:clear
画像の最適化とCDN
画像は、ほとんどのWebアプリケーションの心臓部です。偶然にも、これらは帯域幅の最大の消費者であり、遅いアプリ/ウェブサイトの最大の理由の1つでもあります。アップロードされた画像を単純にサーバーに保存し、HTTPレスポンスで返送するだけで、大規模な最適化の機会を逃してしまいます.
私の最初の推奨事項は、イメージをローカルに保存することではありません—処理するデータ損失の問題があり、顧客がいる地理的地域によっては、データ転送が非常に遅くなる場合があります.
代わりに、次のようなソリューションに進みます 曇り その場で自動的に画像のサイズを変更して最適化します.
それが不可能な場合は、Cloudflareなどを使用して、サーバーに保存されている画像をキャッシュして配信します。.
それが不可能な場合でも、Webサーバーソフトウェアを少し調整してアセットを圧縮し、訪問者のブラウザーにキャッシュするように指示することで、大きな違いが生まれます。 Nginx構成のスニペットは次のようになります。
サーバー{
#ファイルが切り捨てられました
#gzip圧縮設定
gzipオン;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
#ブラウザのキャッシュ制御
場所〜* \。(ico | css | js | gif | jpeg | jpg | png | woff | ttf | otf | svg | woff2 | eot)$ {
1dに期限切れ。
access_log off;
add_header Pragma public;
add_header Cache-Control "public、max-age = 86400";
}
}
画像の最適化はLaravelとは何の関係もないことを認識していますが、それは非常に単純で強力なトリックであり(多くの場合、無視されています)、自分自身を助けることができませんでした。.
オートローダーの最適化
オートローディングは、PHPの古くなった機能であり、間違いなく言語を破滅から救いました。とはいえ、特定の名前空間文字列を解読して関連するクラスを見つけてロードするプロセスには時間がかかり、高いパフォーマンスが望まれる本番環境への配備では回避できます。もう一度、Laravelはこれに対する単一コマンドのソリューションを持っています:
composer install –optimize-autoloader –no-dev
キューで友達を作る
キュー それらが多数ある場合の処理方法であり、それぞれが完了するまでに数ミリ秒かかります。良い例はメールの送信です。Webアプリで広く使用されているのは、ユーザーが何らかのアクションを実行したときに通知メールを送信することです。.
たとえば、新しくリリースされた製品では、誰かが特定の値を超えて注文したときに、会社のリーダーシップ(6〜7通の電子メールアドレス)に通知を送信することができます。メールゲートウェイが500ミリ秒でSMTPリクエストに応答できると仮定すると、注文の確認が始まる前に、ユーザーを3〜4秒待つのが適切です。UXの非常に悪い部分です。きっと同意する.
解決策は、ジョブが入ってくるときにジョブを保存し、すべてがうまくいったことをユーザーに伝え、後で(数秒)ジョブを処理することです。エラーがある場合、キューに入れられたジョブは、失敗したと宣言される前に数回再試行できます.
クレジット:Microsoft.com
キューイングシステムはセットアップを少し複雑にします(そして監視オーバーヘッドを追加します)が、最新のWebアプリケーションには不可欠です。.
アセットの最適化(Laravel Mix)
Laravelアプリケーションのすべてのフロントエンドアセットについて、すべてのアセットファイルをコンパイルおよび縮小するパイプラインがあることを確認してください。 Webpack、Gulp、Parcelなどのバンドラーシステムに慣れている人は、気にする必要はありませんが、これをまだ行っていない場合は、, ララヴェルミックス 確かな推奨事項です.
Mixは、Webpackの軽量(そして正直なところ、楽しい!)ラッパーであり、CSS、SASS、JSなどのすべてのファイルを本番環境で処理します。典型的な.mix.jsファイルはこれと同じくらい小さくても動作します。
const mix = require( ‘laravel-mix’);
mix.js( ‘resources / js / app.js’、 ‘public / js’)
.sass( ‘resources / sass / app.scss’、 ‘public / css’);
これにより、本番環境でnpm run本番環境を実行する準備が整ったときに、インポート、ミニファイ、最適化、シバン全体が自動的に処理されます。 Mixは、従来のJSおよびCSSファイルだけでなく、アプリケーションワークフローにあるVueおよびReactコンポーネントも処理します.
より詳しい情報 ここに!
結論
パフォーマンスの最適化は、科学というより芸術的です。何をすべきか、そしてどれだけすべきかを知ることは、何をすべきかよりも重要です。とはいえ、Laravelアプリケーションで最適化できる量とすべてに終わりはありません。.
しかし、あなたが何をするにせよ、私はあなたにいくつかの別れのアドバイスを残したいと思います—最適化は確かな理由があるときに行われるべきです、そしてそれは良いように聞こえるか、あなたが現実に100,000人以上のユーザーのアプリのパフォーマンスに偏執しているからではありません10しかない.
アプリを最適化する必要があるかどうかわからない場合は、ことわざの巣を蹴る必要はありません。退屈に感じるが正確にそれを実行する作業中のアプリは、ミュータントハイブリッドスーパーマシンに最適化されているが、ときどき横ばいになるアプリよりも10倍望ましい.
そして、初心者がLaravelマスターになるには、これをチェックしてください オンラインコース.
アプリの実行速度が大幅に向上しますように!