0

Một Vài Cách Tăng Performance Cho Project Laravel PHP (P1)

Hiện nay với các PHP developers có lẽ không có gì xa lạ với Laravel, một framework nổi tiếng và phổ biến nhất nhì với ngôn ngữ PHP. Laravel mang đến cho dev những tiện ích và sự thuận tiện rất nhiều so với code PHP thuần. Tuy nhiên, vì Laravel phát triển để làm cho mọi thứ trở nên dễ dàng với devs nên điều đó có nghĩa là bên dưới nó đang làm rất nhiều việc để đảm bảo bạn có một cuộc sống thoải mái với tư cách là một nhà phát triển. Trong bài viết này chúng ta sẽ cùng tìm hiểu một số cách để nâng cao performance của các project Laravel nhé.

1. Hạn chế nhiều nhất N + 1 queries database

Vấn đề truy vấn n + 1 là một vấn đề phổ biến khi ORM được sử dụng. Laravel có ORM mạnh mẽ được gọi là Eloquent, rất đẹp, rất tiện lợi, đến nỗi chúng ta thường quên xem những gì đang diễn ra. Hãy xem xét một tình huống rất phổ biến: hiển thị danh sách tất cả các đơn hàng được đặt bởi một danh sách khách hàng nhất định. Điều này khá phổ biến trong các hệ thống thương mại điện tử và bất kỳ giao diện báo cáo nào nói chung, nơi chúng tôi cần hiển thị tất cả các thực thể liên quan đến một số thực thể.

class OrdersController extends Controller 
{
    // ... 

    public function getAllByCustomers(Request $request, array $ids) {
        $customers = Customer::findMany($ids);        
        $orders = collect(); // new collection
        
        foreach ($customers as $customer) {
            $orders = $orders->merge($customer->orders);
        }
        
        return view('admin.reports.orders', ['orders' => $orders]);
    }
}

Nhìn qua thì thấy rất tiện lợi và đẹp đúng không ạ ? Tuy nhiên đây là một sai lầm cơ bản gây ra lỗi N + 1 queries trong Laravel. Chúng ta cùng xem những câu queries đang chạy phía sau đoạn code đó nhé :

SELECT * FROM customers WHERE id IN (22, 45, 34, . . .);

đây là câu query lấy ra danh sách customers. Bây giờ chúng tôi lặp lại từng khách hàng một và nhận đơn đặt hàng của họ. Điều này thực hiện truy vấn sau đây.

SELECT * FROM orders WHERE customer_id = 22;

tương ứng với mỗi customer sẽ chạy 1 câu query để lấy danh sách order. Nói cách khác, nếu chúng ta cần lấy dữ liệu đơn hàng cho 1000 khách hàng, thì tổng số truy vấn cơ sở dữ liệu được thực hiện sẽ là 1 (để tìm nạp tất cả dữ liệu của khách hàng) + 1000 (để tìm nạp dữ liệu đơn hàng cho mỗi khách hàng) = 1001. Điều này là tên n + 1 bắt nguồn từ đâu.

Để khác phục vấn đề này bạn có thể tìm hiểu Eager Loading trong Laravel nhé. Cơ bản câu query sẽ được sửa thành như này :

$orders = Customer::findMany($ids)->with('orders')->get();

Câu query sẽ chạy như sau :

SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id WHERE customers.id IN (22, 45, . . .);

2. Cache the configuration!

Một trong những lý do giải thích cho sự linh hoạt của Laravel là rất nhiều tệp cấu hình là một phần của framework. Bạn muốn thay đổi cách thức / nơi lưu trữ hình ảnh? Chà, chỉ cần thay đổi tệp config / filesystems.php (ít nhất là khi viết). Bạn muốn làm việc với nhiều trình điều khiển hàng đợi? Vui lòng mô tả chúng trong config / queue.php. Tôi vừa đếm và thấy rằng có 13 tệp cấu hình cho các khía cạnh khác nhau của framework, đảm bảo bạn sẽ không thất vọng cho dù bạn muốn thay đổi điều gì. Với bản chất của PHP, mỗi khi có yêu cầu Web mới, Laravel sẽ thức dậy, khởi động mọi thứ và phân tích cú pháp tất cả các tệp cấu hình này để tìm ra cách thực hiện mọi thứ khác nhau trong lần này. Ngoại trừ việc thật ngu ngốc nếu không có gì thay đổi trong vài ngày qua! Việc xây dựng lại cấu hình theo mọi yêu cầu là một sự lãng phí có thể (thực sự, phải tránh) và cách thoát ra là một lệnh đơn giản mà Laravel cung cấp:

php artisan config:cache

Điều này làm là kết hợp tất cả các tệp cấu hình có sẵn thành một tệp duy nhất và bộ nhớ cache ở đâu đó để truy xuất nhanh. Lần tới khi có yêu cầu trên Web, Laravel sẽ chỉ đọc một tệp này và bắt đầu.

3. Giảm thiếu auto-load services

Để trở nên hữu ích, Laravel tải rất nhiều services khi nó hoạt động. Có một số services được thiết lập sẵn trong file 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,

    ],

Như bạn thấy Laravel cung cấp rất nhiều services có sẵn và luôn luôn bật lên. Tuy nhiên không phải service nào chúng ta cũng cần dùng ví dụ như chúng ta muốn xây dựng một REST API điều đó nghĩa là các services như Session Service Provider, View Service Provider... là vô nghĩa. Vậy nên hãy thử kiểm tra lại project của bạn và comment bớt những service thực sự không cần thiết nhé. Tuy nhiên hãy test thật kỹ lưỡng trước khi làm việc đó nhé 😃.

4. Cẩn thận với các lớp middleware

Middleware là một phần tuyệt vời mà Laravel mang lại, nó giúp chúng ta dễ dàng xử lý các incoming request đến ứng dụng.

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];

Việc chúng ta là thêm một middleware và sau đó khai báo chúng vào danh sách middleware trên là xong. Mọi request sẽ đều qua middleware đó. Tuy nhiên khi app của bạn phát triền và danh sách global middleware càng ngày càng phình to ra thì đó sẽ bắt đầu là vấn đề về performance cho app. Hãy tưởng tượng khi 1 request gọi đến app nó sẽ phải đi qua hàng loạt các middleware trong danh sách trong khi nó thực sự ko cần thiết. Vì vậy hãy cân nhắc và áp dụng đúng middleware cho từng request hoặc nhóm request thay vì áp chúng vào global middleware nhé. Chúng ta có thể khai báo middleware cho từng loại web hay api :

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

hoặc cụ thể hơn khai báo middleware riêng và sử dụng chúng cho từng routes :

 /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ]

Các bạn có thể tìm hiểu rõ hơn về middleware tại https://laravel.com/docs/8.x/middleware .

5. Tránh lạm dụng ORM

Chúng ta không thể phủ nhận rằng ORM là một thứ tuyệt vời trong Laravel, nó làm cho nhiều khía cạnh của tương tác DB trở nên thú vị, nhưng nó phải trả giá bằng tốc độ. ORM không chỉ lấy dữ liệu từ trong database mà nó còn khởi tạo đối tượng và điền các data vào cho đối tượng đó. Với 1 câu query đơn giản $users = User::all() và database có khoảng 10000 records. Khi đó framework sẽ lấy 10000 records từ database và khởi tạo 10000 new User(), điền các thông tin liên quan vào đó . Đây là một lượng lớn công việc đang được thực hiện ở hậu trường và nếu cơ sở dữ liệu nơi ứng dụng của bạn đang trở thành một nút thắt cổ chai, đôi khi bỏ qua ORM là một ý tưởng hay. Điều này rất đúng với những câu query phức tạp qua nhiều bảng và nhiều vòng lặp. Để giải quyết vấn đề này hãy thử sử dụng DB::raw() đây là một cách rất hay xử lý cho những query phức tạp đòi hỏi lặp qua nhiều bảng nhiều vòng.

Trên là một số cách để nâng cao performance của ứng dụng Laravel PHP. Hy vọng sẽ giúp ích được mọi người.

Bài viết được tham khảo:

https://laravel.com/docs/5.2/eloquent-relationships#eager-loading

https://laravel.com/docs/8.x/middleware

https://www.bacancytechnology.com/blog/laravel-app-performance-optimization-tips


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí