Laravel livewire, render performance issue. How to manage hundreds of components?

Viewed 2624

I have a performance issue with Livewire, when i use many times the same component. I already had this issue in the past, and each time I managed this issue by switching off the framework and use Vuejs/api directly. Maybe I do not use Livewire correctly ... or maybe I missed something somewhere.

Question: How to manage the Livewire usage of many identical components with parameters and model data, without exploding the rendering time and explode the performance ? Did I miss something ? Any tips ?

Thanks in advance.

Issue:

I have a webpage with several hundreds of entries. For each of them, i call a Livewire component. For 105 components, the page take 8-11 seconds to load.

I used debugbar, and discover that the render livewire function is very very consuming (8.29s) comparing to the application time to (8.46). I audited the functions inside, and it's only 205.53ms. Ok, 400ms if we take the part that fetch the data.

debugbar performance

The conclusion i got after some reading, is that the root cause is the Lazy Loading of models. And indeed, I use two models (User & Item) on my component:

  <livewire:matrix-power-slider-resume-group :type="$type" :user="$user" :item="$item" aspect="T"></livewire:matrix-power-slider-resume-group>

User & Item have of course some relationships, but when I debug them the relationships are not loaded/used (mean I should have a little serialized 'json' object). So I don't suspect the big-lazy-model-loading issue.

I tried to pass the IDs instead of the model, and ask to my component to get again the data. But here, I explode the number of queries (120 with model in parameters versus +1800 with IDs), and I still have very bad performances (7-11s) due to the number of queries.

I used a debug measure before the html <livewire:... /> code, and this is this part that consume all the time. Not the render() function of the component, not the mount(), but the livewire render from the blade file :(

I also tried to use the model declaration in the class:

  public function mount($type, $userId, $itemId, $aspect = null, User $user = null, item $item = null)

or not:

  public function mount($type, $userId, $itemId, $aspect = null, $user = null, $item = null)

Code:

<?php

namespace App\Http\Livewire;

use App\Models\Matrix\Item;
use App\Models\User;
use Livewire\Component;

class MatrixPowerSliderRange extends Component
{
    public $type;
    public $userId;
    public $user;
    public $itemId;
    public $item;
    public $aspect;

    public $value; // data used into component

    public function mount($type, $userId, $itemId, $aspect = null, User $user = null, Item $item = null)
    {
        $this->type = $type;
        $this->itemId = $itemId;
        $this->item   = $item;

        $this->userId = $userId;
        $this->user   = $user;
        $this->aspect = $aspect;
    }

    public function load()
    {
        // Fetch data if only Ids
        $this->user = is_object($this->user) ? $this->user : (is_numeric($this->userId) ? Job::find($this->userId) : $this->user);
        $this->item = is_object($this->item) ? $this->item : (is_numeric($this->itemId) ? Item::find($this->itemId) : $this->item);

        // Get my data == measure 'MatrixPowerSliderRange.load.returnPower'
        $this->value = $this->user
                ->returnPower($this->item, $this->aspect, $this->kind);
    }

    public function render()
    {
        $return = null;
        $this->load(); // tried to load into render, into mount, ...

        // ==> measure 'MatrixPowerSliderRange.render'
        return view('livewire.matrix-power-slider-range');
    }
}

Versions:

  • livewire/livewire (v2.4.3)
  • Laravel Framework 8.38.0
2 Answers

First of all you don't need to create a whole component to encapsulate your logic and instantiate it every time you need that rendered.

For example, you could implement the logic in the main component and use layout components for the render where you pass the compiled data. Inside the main component, you could create an function called loadRow and put the load logic inside of there, and on the view you could do something like this:

<x-matrix-power-slider-range :data="loadRow($row->type, $row->userId, ...)" />

And inside the x-matrix-power-slider-range, you can just take the array that contains the info and use it. If you want to add something like a hover functionality, then you can implement hoverRow() where you can pass an identifier and do your logic.

To further improve this, you can also fetch all data at the beginning and use it from the array in the loadRow(); also, data caching helps.

First of all you don't need to pass anything inside mount. This may not completely solve your problem. But it will remove a mount function call. You can do;

public $type,$value,$userId,$itemId,$aspect;
public User $user;
public Item $item;

public function mount()
{
    //Other things
}
Related