tmbb

tmbb

I’m writing a pretty basic application where users will be able to create “projects” for data entry purposes. A user can create a “project”, which contains a number of “tables”, where each table has a number of “columns” and columns can be of type choice, in which case each table will have a number of “choices”.

To simplify implementation and because I think that is a genuinely good UI, I want to be able to edit everything in the same page. I have the fields for the project, then I use (a customized version of) to create an editable list of tables, then another <.inputs_for/> to create a list of columns for each table, and then a list of choices for each column.

I expect each project to have 1-2 tables, and each table to have up to 60 columns, with some columns having up to 10-20 choices. For these numbers, the number of elements in the webpage is about 10.000. This creates a 4 levels deep stack of neste inputs, which seems like LiveView can’t optimize in any way. The result is that when I edit any of the hundreds of input components in the form, the changeset is recomputed and a rather large message is sent to Morphdom on the client. The elixir code doesn’t struggle at all with any of this, but Morphdom takes up to 1500ms to patch the updates (I’ve gotten these numbers from logs enabled by liveSocket.enableProfiling();).

Is this the kind of performance one is expected to get? Is there any way I can try to restrict which parts of the HTML dom morphdom actually tries to patch? I could break the tables and columns into different pages (with different liveviews, even), but that is not how I want the page to look like… One of the problem of breaking things up is that you lose the ability to render errors that depend on parts of the form interacting with each other (say we create two tables with the same name: my approach makes it trivial to tag that as an error in the changeset and display it to the user using the normal Phoenix components).

I’m honestly very surprised that morphdom’s performance is so bad on what doesn’t look like a very big page (~10.000 HTML elements, < 200 input elements). It seems like I’ve bought into the hype of thinking that LiveView would scale to moderately sized dynamic webpages, when in fact all examples I see of actual LiveViews are render extremely small web pages… Although Elixir definitely scales up to lots of events, the javascript seems like a serious bottleneck!

Showing Posts 1 to 10

olivermt

olivermt

Throw everything into live components. Little bit more memory, much better diffs.

kevinschweikert

kevinschweikert

See also Any suggestions on a HTML/CSS/JS TreeView that fits into the LiveView environment. The use case was different but the performance optimisation should be the same

rvnash

rvnash

A while back I was having a problem with an app of mine similar to this. I was rendering a very large list of small elements, and a few items in that list were changing. This was causing a complete re-render. Someone suggested using LiveView streams, and that did the trick. In the end I wrote a little function to take the before and after lists, and come up with the set of inserts/deletes to the stream. Look at this thread and the function at the end.

bartblast

bartblast

Creator of Hologram

Try adding stable HTML id attributes to your table containers and cells. I’m not sure exactly how LiveView will treat this underneath, but it may help both LiveView’s diffing and Morphdom’s DOM reconciliation process, even when dealing with large updates.

garrison

garrison

To expand on the above answers (which are all valid), the problem you’re running into is that LiveView is not capable of properly diffing collections. So when you do something like <div :for={row <- @rows} />, LiveView has to send down the entire list of rows if a single one is changed.

If you wrap your rows in LiveComponents, LiveView is able to diff them individually, so only one row will be sent when it’s updated. If you need further optimization you could then also wrap the columns in LiveComponents.

The LiveComponents obviously have some overhead but they are in-process so they share memory with the parent LiveView (the assigns won’t be copied).

You can also look into using Streams but I wouldn’t recommend that for this use-case unless you really need to optimize performance. LiveComponents will be enough here, I think.

garrison

garrison

Unfortunately I don’t think this will make much difference. And the diffs on the wire would still be large anyway.

tmbb

tmbb OP

I did try this, and as I expected I didn’t get any benefit. Having stable IDs doesn’t really make much of a difference because Morphdom has to inspect the children anyway to see if anything has changed. I did look up whether there were any attributes I could use to tell Morphdom that the children of that node didn’t change, but there aren’t. What one could do would be to use (for example) the phx-ignore attribute and set it conditionally if the children haven’t changed. I did not try that yet, as that would require inspecting changesets for changes. And anyway, IDK exactly how to do that if the changeset has already been converted to a form. But I guess looking that up would be the next step.

sodapopcan

sodapopcan

Was following your long thread about streams and whatnot and I think this was mentioned there too, but is this really true? Because assigns are shared why would there be any additional overhead? Like, there’s a single additional BEAM file created per LiveComponent model, but that isn’t something I would consider “overhead”? Is there something else I’m missing? I curious as I don’t have the best grasp on internals here and want to learn. I’ve always found way the docs are worded to be a bit confusing as well: To paraphrase: “Don’t pass assigns wholesale because they are all passed even if some aren’t used but also it doesn’t matter because memory is shared” :sweat_smile: It makes sense from a maintainability standpoint but not from a performance one (again, unless I’m wrong).

tmbb

tmbb OP

Hm… It turns out this doesn’t seem to work because if we set the phx-update="ignore" attribute at any point, conditional on the fact that the changeset for that “subform” doesn’t have any changes, Phoenix will keep ignoring it when the subform does have changes in the future.

garrison

garrison

Well first you may have noticed that I’m essentially borrowing that line from the docs :slight_smile:

But in a general sense, any code has overhead. What I (and the docs) am trying to clarify is that LiveComponents have much less overhead than embedding a child LiveView, which is what most people probably think LiveComponents are when they see the name.

Elixir is functional (immutable) and data structures like maps are essentially what are called “persistent data structures”, where the old version of the map stays around somewhere and the new version borrows from it. Since everything is immutable this is easier in some ways and harder in others. I’m not an erlang core dev obviously (and there are some lurking around on here), so I don’t want to speak past my expertise. But the point I’m making is:

If you have a map %{foo: "bar", hello: "world"} and then you call Map.put(...) on it, you get back a new map. But the contents of the old map are not (necessarily) copied, they’re usually just kept around as references. In practice this is more complicated, obviously.

That doesn’t mean the new map has no overhead, but it’s less than you might naively think. When you pass your data around to all of the LiveComponents the same thing is happening, and so there is a bit of overhead (keeping track of all those assigns maps and so on), but it’s not literally duplicating everything.

Of course, if you used a child LiveView (which has its own process), then you would be literally duplicating everything.

I have read some of the code recently (I think you know why…), but I don’t have a perfect grasp on it either. But essentially, there is some bookkeeping going on - have a look at diff.ex and channel.ex and look for LiveComponent bits, that’s where I found some of it.

There is also some extra code to handle them on the client. I found it but I’ve already forgotten where…

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews