Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rendering performance struggle #740

Closed
pghiuzan-phished opened this issue Nov 25, 2024 · 34 comments
Closed

Rendering performance struggle #740

pghiuzan-phished opened this issue Nov 25, 2024 · 34 comments
Assignees

Comments

@pghiuzan-phished
Copy link

We recently started developing some screens using FluxUI and we soon ran into a serious performance problem when rendering a lot of components on one page. Here is a screenshot of the Laravel debug bar. This was executed after running artisan optimize, so this is the best we can get.

Image

We understand this is a Laravel problem, but this builds up the context for what we wanted to share with the FluxUI team: the alternative we tried was to move some of the rendering to the front end, using Alpine and x-for. That didn't work out too well, as the flux:option component is not really ready for that. And even after hacking a few things to explore this solution further, it became clear that the components weren't really prepared for that:

  • everything went well with the single select, except the check icon wasn't being rendered at all
  • the multi-select closeevent doesn't seem to work anymore, and it has the same issue with the check mark now showing up on already selected options

If there's any guidance you can provide for this situation?

While we're here, we also want to point out that the components are sometimes really difficult to brand - for example, the sidebar menu colors required some hacky Tailwind variant-classes to use our own color palette. The components are really coupled to the zinc color, and it felt a lot easier to just redefine zinc in our Tailwind config than struggle with every component. Any guidance on this side would be appreciated, as well.

@Kh4os-afk
Copy link

I had the same problem here, a table with flux:select in a 10-line pagination increased the rendering time by 5 times, the solution I temporarily found was to show the flux:options only when the user opens the select, In addition to decreasing the pagination to 5 and removing the variant="listbox", this considerably increased performance despite making the select uglier

@ju5t
Copy link

ju5t commented Nov 25, 2024

I just rendered 11000 views in 500ms. This is with listbox's. It's in a real app, I haven't rendered the same field all over again, it's for dynamic products so we have a lot of dynamic input fields. Although 500ms isn't fast, it's not 3 seconds.

What Laravel version are you running? I remember there have been some improvements to Blade a while ago.

@Kh4os-afk
Copy link

I just rendered 11000 views in 500ms. This is with listbox's. It's in a real app, I haven't rendered the same field all over again, it's for dynamic products so we have a lot of dynamic input fields. Although 500ms isn't fast, it's not 3 seconds.

What Laravel version are you running? I remember there have been some improvements to Blade a while ago.

Lol are you running in laravel 11 ?

Image

5947 Views in 2.6 - 3.0 Seconds.... this is not good...

Can you show me our code ? here i am using laravel 10.48.23

@pghiuzan-phished
Copy link
Author

I had the same problem here, a table with flux:select in a 10-line pagination increased the rendering time by 5 times, the solution I temporarily found was to show the flux:options only when the user opens the select, In addition to decreasing the pagination to 5 and removing the variant="listbox", this considerably increased performance despite making the select uglier

I agree there are workarounds we can use, they are temporary solutions, and not really the ideal way to go for a library we paid for.

@pghiuzan-phished
Copy link
Author

I just rendered 11000 views in 500ms. This is with listbox's. It's in a real app, I haven't rendered the same field all over again, it's for dynamic products so we have a lot of dynamic input fields. Although 500ms isn't fast, it's not 3 seconds.

What Laravel version are you running? I remember there have been some improvements to Blade a while ago.

We're running Laravel v11.31. I wonder what makes yours so much faster. 500ms isn't great, as you said, but it's a big step forward.

@ju5t
Copy link

ju5t commented Nov 26, 2024

Our app is on 11.33.2. I can't share any code as it's rendered from the database.

I do think it would be helpful to use x-for in Flux as @pghiuzan-phished suggested, but the issue itself is unrelated to Flux. You would see this with your self-made components or other component libraries too.

@pghiuzan-phished
Copy link
Author

Our app is on 11.33.2. I can't share any code as it's rendered from the database.

I do think it would be helpful to use x-for in Flux as @pghiuzan-phished suggested, but the issue itself is unrelated to Flux. You would see this with your self-made components or other component libraries too.

Yes, I only shared the performance concern for context, my goal here was to address the problem that proper alternatives aren't available with the way Flux components work at the time.

@Kh4os-afk
Copy link

Kh4os-afk commented Nov 26, 2024

Our app is on 11.33.2. I can't share any code as it's rendered from the database.
I do think it would be helpful to use x-for in Flux as @pghiuzan-phished suggested, but the issue itself is unrelated to Flux. You would see this with your self-made components or other component libraries too.

Yes, I only shared the performance concern for context, my goal here was to address the problem that proper alternatives aren't available with the way Flux components work at the time.

I have doubts whether the way I am using the components could be interfering with performance, but it really seems to be a rendering problem with the <flux:select> component because when using a simple <select> html the performance is incredibly better.
EditarCobranca.php

<?php

namespace App\Livewire;

use App\Models\BDCCob;
use App\Models\PCCob;
use Flux\Flux;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Computed;

class EditarCobranca extends Component
{
    use WithPagination;

    public $codcobs = [];
    public $search = '';
    public $sortBy = 'cobranca';
    public $sortDirection = 'asc';

    #[Computed]
    public function cobrancas()
    {
        return BDCCob::query()
            ->whereAny(
                ['codcob', 'cobranca', 'cobranca_arquivo'],
                'LIKE',
                "%" . trim($this->search) . "%"
            )
            ->orderBy($this->sortBy, $this->sortDirection)
            ->paginate(5);
    }

    #[Computed]
    public function pccob()
    {
        return PCCob::query()
            ->where('cartao', 'S')
            ->orderBy('cobranca')
            ->get();
    }

    public function sort($column)
    {
        if ($this->sortBy === $column) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortBy = $column;
            $this->sortDirection = 'asc';
        }
    }

    public function updateCobranca(BDCCob $BDCCob, $key)
    {
        $pccob = PCCob::where('cartao', 'S')
            ->where('codcob', $this->codcobs[$key])
            ->select('codcob', 'cobranca')
            ->first();

        if ($pccob) {
            $BDCCob->update([
                'codcob' => $pccob->codcob,
                'cobranca' => $pccob->cobranca,
                'validado' => 1,
            ]);

            Flux::toast(
                variant: 'success',
                heading: 'Alterações Salvas.',
                text: 'Cobrança atualizada com sucesso.',
            );
        } else {
            Flux::toast(
                variant: 'error',
                heading: 'Alterações Não Salvas.',
                text: 'Cobrança não encontrada.',
            );
        }
    }

    public function render()
    {
        // Inicializar o array codcobs com os valores atuais das cobranças
        foreach ($this->cobrancas as $cobranca) {
            $this->codcobs[$cobranca->id] = $cobranca->codcob;
        }

        return view('livewire.editar-cobranca')->title('Editar Cobrança');
    }
}

editar-cobranca.blade.php

<div>
    <div class="flex justify-between items-center">
        <div>
            <flux:heading size="lg">Editar Cobrança</flux:heading>
            <flux:subheading size="md">Edite as Cobranças do SitefGW conforme correspondencia no Winhor</flux:subheading>
        </div>
        <div>
            <flux:input icon="magnifying-glass" placeholder="Pesquisar..." wire:model.live="search" clearable/>
        </div>
    </div>
    <flux:separator variant="subtle" class="mt-8"></flux:separator>
    <flux:card class="mt-8">
        <flux:table :paginate="$this->cobrancas">
            <flux:columns>
                <flux:column>Codigo Cobrança</flux:column>
                <flux:column sortable :sorted="$sortBy === 'cobranca'" :direction="$sortDirection" wire:click="sort('cobranca')">Cobrança</flux:column>
                <flux:column sortable :sorted="$sortBy === 'cobranca_arquivo'" :direction="$sortDirection" wire:click="sort('cobranca_arquivo')">Cobrança SitefGW</flux:column>
                <flux:column sortable :sorted="$sortBy === 'validado'" :direction="$sortDirection" wire:click="sort('validado')">Status</flux:column>
            </flux:columns>

            <flux:rows>
                @foreach ($this->cobrancas as $cobranca)
                    <flux:row :key="$cobranca->id">
                        <flux:cell class="flex items-center gap-3">
                            <flux:select variant="listbox" placeholder="Cobranças..." wire:model="codcobs.{{ $cobranca->id }}" wire:change="updateCobranca({{ $cobranca->id }}, {{ $cobranca->id }})">
                                <flux:option value="null">Cobranças...</flux:option>
                                @foreach($this->pccob as $cob)
                                    <flux:option :selected="$cobranca->codcob == $cob->codcob" wire:key="{{ $cob->codcob }}" value="{{ $cob->codcob }}">
                                        {{ $cob->codcob }} - {{ $cob->cobranca }}
                                    </flux:option>
                                @endforeach
                            </flux:select>
                        </flux:cell>
                        <flux:cell class="whitespace-nowrap">
                            <flux:input readonly variant="filled" value="{{ $cobranca->cobranca }}"/>
                        </flux:cell>
                        <flux:cell variant="strong">{{ $cobranca->cobranca_arquivo }}</flux:cell>
                        <flux:cell>
                            @php $cor = $cobranca->validado ? 'lime' : 'yellow' @endphp
                            <flux:badge size="sm" :color="$cor" inset="top bottom">
                                {{ $cobranca->validado ? 'Verificado' : 'Não Verificado' }}
                            </flux:badge>
                        </flux:cell>
                    </flux:row>
                @endforeach
            </flux:rows>

        </flux:table>
    </flux:card>
</div>

I know I should put flux:row in another component, I'm working on this.... but in the meantime, any suggestions to reduce the rendering time of this component? $this->cobrancas and a collection with around 130 results

Master @calebporzio Help Us <3

@jeffchown
Copy link

@Kh4os-afk Can you repost your code snippet using:

3 backticks on line by themselves
...code...
3 backticks on line by themselves

@Kh4os-afk
Copy link

@Kh4os-afk Can you repost your code snippet using:

3 backticks on line by themselves ...code... 3 backticks on line by themselves

Sorry for the broken code, I don't know what happened to the snnipets

@jeffchown
Copy link

@Kh4os-afk Without being able to copy/paste your example (because of database dependencies, etc.), one thing that I see is your query:

BDCCob::query()
            ->whereAny(
                ['codcob', 'cobranca', 'cobranca_arquivo'],
                'LIKE',
                "%" . trim($this->search) . "%"
            )
            ...

LIKE queries can be very slow depending upon the size of your db table and if the columns being searched are not indexed in your table.

Have you tested that query using EXPLAIN to see how efficient that query is?
(https://dev.mysql.com/doc/refman/8.0/en/using-explain.html)

@jeffchown
Copy link

jeffchown commented Nov 26, 2024

If you click on the queries icon in debugbar, you can see how long each query is taking. Have you checked that to see if the queries are slowing the page down?

Image

@pghiuzan-phished
Copy link
Author

If you click on the queries icon in debugbar, you can see how long each query is taking. Have you checked that to see if the queries are slowing the page down?

Image

Already ruled that out. But I feel like this issue is digressing. I don't think it's Flux causing the slowness, but we can't apply a proper alternative either, like using the components with Alpine. And that's our issue here.

@jeffchown
Copy link

@pghiuzan-phished I agree that I don't think Flux itself is causing the slowness. This sounds like it falls into the category of overall optimization that could involve multiple methods to decrease load time.

@pghiuzan-phished
Copy link
Author

@pghiuzan-phished I agree that I don't think Flux itself is causing the slowness. This sounds like it falls into the category of overall optimization that could involve multiple methods to decrease load time.

From our perspective, the components are missing capabilities one would be reasonable to expect. FluxUI is advertised as "The official Livewire component library", "Built by the folks behind Livewire and Alpine", so being able to properly use the component with Alpine should not be seen as an optimization, should it?

Considering that we paid for it, we thought we'd bring it up with the team that develops it. Otherwise, this issue would have immediately been followed by a PR addressing our immediate pain points.

@jeffchown
Copy link

As I'm not a member of the Flux team, @pghiuzan-phished , I'll have to leave your comment to @calebporzio or @joshhanley

@Kh4os-afk
Copy link

@Kh4os-afk Without being able to copy/paste your example (because of database dependencies, etc.), one thing that I see is your query:

BDCCob::query()
            ->whereAny(
                ['codcob', 'cobranca', 'cobranca_arquivo'],
                'LIKE',
                "%" . trim($this->search) . "%"
            )
            ...

LIKE queries can be very slow depending upon the size of your db table and if the columns being searched are not indexed in your table.

Have you tested that query using EXPLAIN to see how efficient that query is? (https://dev.mysql.com/doc/refman/8.0/en/using-explain.html)

Well, I guess this isn't a query or database issue... look at the query response time:

Image

There are 6,017 views in 1.63 seconds

If I change the code from:

                       <flux:cell class="flex items-center gap-3">
                            <flux:select variant="listbox" placeholder="Cobranças..." wire:model="codcobs.{{ $cobranca->id }}" wire:change="updateCobranca({{ $cobranca->id }}, {{ $cobranca->id }})" searchable>
                                <flux:option value="null">Cobranças...</flux:option>
                                @foreach($this->pccob as $cob)
                                    <flux:option :selected="$cobranca->codcob == $cob->codcob" wire:key="{{ $cob->codcob }}" value="{{ $cob->codcob }}">
                                        {{ $cob->codcob }} - {{ $cob->cobranca }}
                                    </flux:option>
                                @endforeach
                            </flux:select>
                        </flux:cell>

To:

                        <flux:cell class="flex items-center gap-3">
                            <select>
                                @foreach($this->pccob as $cob)
                                    <option @selected($cobranca->codcob == $cob->codcob) wire:key="{{ $cob->codcob }}" value="{{ $cob->codcob }}">
                                        {{ $cob->codcob }} - {{ $cob->cobranca }}
                                    </option>
                                @endforeach
                            </select>
                        </flux:cell>

And i had this results:

Image

@jeffchown
Copy link

jeffchown commented Nov 26, 2024

We all know that Flux components involve more code (in order to provide their enhanced functionality) than standard HTML elements and that will affect rendering time - especially when we are in the neighbourhood of 1,000s of views being rendered.

I have used other UI libraries with which I have had similar issues and had to refactor (if I wanted to benefit from the functionality offered by whichever library I was using) some of my page designs if they were too complex and led to load times like those being described here.

In the first app I 'Fluxified', I've kept my views rendered to < 1,500 per page and have found I've been able to keep load times to < 500ms (including db queries, etc.)

@ju5t
Copy link

ju5t commented Nov 26, 2024

Yes, I only shared the performance concern for context, my goal here was to address the problem that proper alternatives aren't available with the way Flux components work at the time.

That wasn't immediately clear for me from reading the subject and opening post.

In the end there are two issues that need to be looked at:

  • You cannot use Flux inside AlpineJS loops.
  • In some installations there appear to be performance issues when loading many components.

@pghiuzan-phished please correct me if I'm wrong.

It's probably best to create a new issue with just the AlpineJS issue with a reproducible code example. This issue is a bit cluttered and a lot of emphasis has been on the performance part. By creating a new issue it's easier to keep track of things. For maintainers too.

@pghiuzan-phished
Copy link
Author

Yes, I only shared the performance concern for context, my goal here was to address the problem that proper alternatives aren't available with the way Flux components work at the time.

That wasn't immediately clear for me from reading the subject and opening post.

In the end there are two issues that need to be looked at:

* You cannot use Flux inside AlpineJS loops.

* In some installations there appear to be performance issues when loading many components.

@pghiuzan-phished please correct me if I'm wrong.

It's probably best to create a new issue with just the AlpineJS issue with a reproducible code example. This issue is a bit cluttered and a lot of emphasis has been on the performance part. By creating a new issue it's easier to keep track of things. For maintainers too.

I initially contacted the support team via e-mail; they redirected me here. I think the problem, the way I described it, is clear enough; the context is there to put the right perspective on it. The fact that paying customers support is handled via open issues is not really a concern I want to address unless the support team comes back with some request in this regard.

@calebporzio
Copy link
Contributor

@pghiuzan-phished, we offer "Limited Support" as stated on the pricing page and in the terms.

We look at every single issue reported here.

If you feel that Flux is not right for you or you need a product with personal, on-demand, support - we are more than happy to refund you even if you're past the refund window. Please reach out [email protected]

Flux is a collection of Blade components.

The issue you are describing is because rendering that many Blade components is slow.

It seems you're suggesting that Flux is solely responsible for your app's slowness and I don't believe that's true.

Ok, those things aside.

Yes, we should have a way to drive large select dropdowns without rendering that many components.

This will likely involve using Alpine to render the components in the browser to keep load times fast. (although that has some of it's own tradeoffs)

I will leave this issue open while we explore some options for that.

Thanks everyone else for pitching in and helping bring clarity to the issue.

@pghiuzan-phished
Copy link
Author

@pghiuzan-phished, we offer "Limited Support" as stated on the pricing page and in the terms.

We look at every single issue reported here.

If you feel that Flux is not right for you or you need a product with personal, on-demand, support - we are more than happy to refund you even if you're past the refund window. Please reach out [email protected]

Flux is a collection of Blade components.

The issue you are describing is because rendering that many Blade components is slow.

It seems you're suggesting that Flux is solely responsible for your app's slowness and I don't believe that's true.

Ok, those things aside.

Yes, we should have a way to drive large select dropdowns without rendering that many components.

This will likely involve using Alpine to render the components in the browser to keep load times fast. (although that has some of it's own tradeoffs)

I will leave this issue open while we explore some options for that.

Thanks everyone else for pitching in and helping bring clarity to the issue.

@calebporzio I think there is a bit of a misunderstanding here. I specifically mentioned in the issue description that we understand the performance issue is a Laravel problem - or maybe something specific to our application - the point is we didn't want the Flux team to address the slow rendering times for us. That was a build-up for the problem we wanted to bring up - the components are not quite ready for Alpine use, which is more than a nice-to-have, at least in some scenarios.

That being said, what we would like to get from this issue is:

  • are there any plans to update the components to be fully Alpine-ready?
  • when could we expect such updates?

Would addressing these questions qualify for the limited support offered?

@av3nger
Copy link

av3nger commented Dec 12, 2024

I wanted to add to the performance thread. We've recently launched a project with Flux. Our real-world usage metrics came back really bad (FCP & LCP ~5 seconds). After debugging, it appears the issue is coming from flux.js. The home page uses ~ 70 flux components and the flux script does a ridiculous amount of work on the main thread (between 1.8 and 2.5 seconds on the graph below) before the browser is even able to render something.

Image

And one more issue, not related to performance. If I open up the browser console and start resizing the window, I get the following error in the console: ResizeObserver loop completed with undelivered notifications. If I remove all the component on the page, the issue is no longer there.

@joshhanley
Copy link
Member

@av3nger any chance you could share your home page? Would be nice to see if we could replicate it. If it needs to be private, I can send you my Discord username.

@av3nger
Copy link

av3nger commented Dec 12, 2024

@joshhanley , no problem. It's https://myowncdn.com. I also have a staging site, so if you want me to test something out, please let me know.

@joshhanley
Copy link
Member

@av3nger thanks! I will try and have a play with it this afternoon to see what I can come up with. Ok thanks, much appreciated 🙏

@joshhanley
Copy link
Member

@av3nger oh also, what OS and browser are you using?

@joshhanley
Copy link
Member

joshhanley commented Dec 12, 2024

@pghiuzan-phished I've got an app now that is Flux components heavy, but I am not seeing the same sort of performance numbers you are (see my screenshot below). Are you able to share some more details about what components your page contains (and a screenshot if you're willing), so we can investigate this further? Can you also clarify whether your screenshot is local or deployed? And what OS and browser are you using?

Re your questions about converting to Alpine, I'm not aware of any concrete plans at this stage (I can't speak for @calebporzio though, he might have plans to look at it).

Image

@av3nger
Copy link

av3nger commented Dec 13, 2024

@av3nger oh also, what OS and browser are you using?

I'm testing with webpagetest.org, which replicates a mobile device with a Chrome-based (v129) browser.

For the ResizeObserver error - it's replicable in the latest Safari version. I think it has something to do with flux:sidebar. If I remove that component from the page, the error goes away. And if remove everything from inside the sidebar, the error stays.

@pghiuzan-phished
Copy link
Author

@pghiuzan-phished I've got an app now that is Flux components heavy, but I am not seeing the same sort of performance numbers you are (see my screenshot below). Are you able to share some more details about what components your page contains (and a screenshot if you're willing), so we can investigate this further? Can you also clarify whether your screenshot is local or deployed? And what OS and browser are you using?

Re your questions about converting to Alpine, I'm not aware of any concrete plans at this stage (I can't speak for @calebporzio though, he might have plans to look at it).

Image

@joshhanley Thanks for looking into it.

The screenshot in the issue description (added again below) contains the details about the components getting rendered. The main culprit is <flux:option>. One thing that crossed our minds is that the component itself has several sub-components, so the rendering process might be slower, though we tried with a simple component rendered many times, and the performance problem is still there.
Image

The page in question is fully developed with Flux components, but the main difference came from rendering a relatively long list of <flux:option>s (200+). If we remove that, it goes down to similar load times you get.

About the setup:

  • it's a Docker container running Debian and PHP 8.3
  • I've been testing on Firefox and Chrome
  • tested locally and in 2 other envs deployed in Google Cloud

That being said, I go back to the conclusion I stated in the description: I don't think the degraded performance is Flux's fault. I just brought it up as context for justifying the need to consider making the components Alpine-friendly. Since this issue is getting more and more focused on the performance problem, I started a separate discussion about Alpine integration at #860.

@ju5t
Copy link

ju5t commented Dec 13, 2024

The page in question is fully developed with Flux components, but the main difference came from rendering a relatively long list of flux:options (200+). If we remove that, it goes down to similar load times you get.

We used an Alpine version before with 200+ items and it wasn't much faster unfortunately. I'm not sure if it's possible in your use-case but dynamic options is possibly the best solution for this.

@joshhanley
Copy link
Member

@av3nger great, thanks for that! Re the resizeObserver issue in Safari, can you open that as a new issue?

@pghiuzan-phished no worries! Thanks, good to know about <flux:option> and thanks for the feedback! Yep I hear what you're saying re Alpine-friendly components. At this stage, and I guess why we keep coming back to it here, we want to improve the performance for Flux overall and see if we can make it quicker before Alpine-ifying it. Using Alpine is a valid solution but that will take time and a lot of rework of the components, so we're investigating the Blade side first to make sure we cover all bases.

Thanks for opening the separate feature request though, that's a good idea 🙂

Will have a play with the select/ options components to see what I can come up with

@joshhanley joshhanley self-assigned this Dec 19, 2024
@joshhanley
Copy link
Member

We're going to close this for now as it's Blade that is slow, not Flux specifically. But Blade performance something we will be looking at to get improved in the future.

@itsgrimace
Copy link

Assume you want reduced functionality (in my example I only wanted search) then you can create a custom flux component and strip out all the conditionals. This should speed things up quite a bit. (I found that the loading indicator was a big performance win loads taking ~5 seconds reduced to ~1 second, rendering ~3000 options).

For example I created a blade "option-trim.blade.php" (I'm still version 1) put it in the folder resources/views/flux

This is my file example

I just switch flux:option for flux:option-trim

@props([
    'value' => null,
])
<ui-option
    @if ($value !== null) value="{{ $value }}" @endif
    class="group/option overflow-hidden data-[hidden]:hidden group flex items-center px-2 py-1.5 w-full focus:outline-none rounded-md text-left text-sm font-medium text-zinc-800 data-[active]:bg-zinc-100 [&[disabled]]:text-zinc-400 dark:text-white data-[active]:dark:bg-zinc-600 dark:[&[disabled]]:text-zinc-400 scroll-my-[.3125rem]"
    data-flux-option
>
{{ $slot }}
</ui-option>```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants