mononym

mononym

LiveView append and performance degradation with large number of appended elements

I’m noticing quite a bit of slowdown when appending messages and replacing elements on a LiveView webpage which has an ever-increasing number of chat messages being displayed.

Each incoming message is wrapped in a p tag, and there are multiple spans within the text inside that p tag. As I start getting up to several hundred messages, replacing the input/form with a new one by using a new changeset starts to take some significant time, and the insertion of messages into the window starts creeping up to ~50ms just because of a reflow being triggered. The process of fully handling the form/input replacement can work its way up to 500ms or more.

Using some manual editing, I took those hundreds of messages and modified them so they were all part of a single huge p element on the page. Same number of text characters and ‘lines’, but just in one p tag instead of hundreds. This dropped the time to swap out the input field and insert new messages considerably, to a level which (while noticed) is at least acceptable. If I remove all the messages and have a blank slate, the insertion time is sub 15ms.

Does anyone have any tips/tricks they use with having a large amount of text and doing a lot of appending? Do I need to create some javascript hooks that go in and shuffle around the text LiveView pushes to the page so that each incoming message ends up in a single p element with all the other messages? Should I just accept having even less text available and only allow ~100 messages (this really isn’t workable for me honestly) to live at a time? Should I switch to channels and cut out LiveView dealing with sending messages to and from the client, leaving LiveView to handle other parts of the UI that aren’t chat related?

Most Liked

chrismccord

chrismccord

Creator of Phoenix

Components would solve your issues in this case. The issue is we have to patch the entire LiveView DOM container for any change, and for large DOM trees, this requires traversing the tree as you alluded to. If you wrap the paragraphs in a component and/or the form in a component, it will allow LiveView to patch only those areas when they change, and also skip walking those areas when the parent LiveView needs to patch a DOM a non-component node that it owns.

LostKobrakai

LostKobrakai

It seems you are. LiveView does not process the resulting markup after assigns are interpolated. It only processes the templates. .leex templates are processed, so that static parts of the template are taken apart from dynamic parts. The dynamic parts also have dependency tracking, which allows to update only the parts, which actually depend on the data being updated.

<div class="<%= "foo" %>">

becomes something akin to (pseudo-code)

%{static: ["<div class=\"", 1, "\">"], dynamic: %{1 => %{depends_on: fn -> @foo end}}

where ["<div class=\"", 1, "\">"] can be sent once to the client and only the value of @foo is sent on updates.
… while

<%= raw(foo) %>

would be represented like this:

%{static: [1], dynamic: %{1 => %{depends_on: fn -> raw(@foo) end}}

where the whole raw(@foo) would be sent on each update.

If you now render a .leex template you don’t get back a plain string, but a struct, holding those details, where the runtime can now decide to send only the parts that matter to the client, significantly reducing the amount of traffic on the wire (imagine the above nested a few handful of layers).

Non .leex templates essentially result in the same unoptimized sending as the latter example.

mononym

mononym

Apparently I don’t need to do anything fancy. Setting a max height for the div did the trick.

Apparently the browser (Chrome anyway) is smart enough to know how to show a scroll bar the proper size for a div with a max height and can properly ignore all of the elements that are not in view, which brings down the time it takes to append down to 50-80ms consistently which is entirely within reason for me.

But you toss a div at the browser that doesn’t have a max height and is going to need to scroll it chokes because it cannot, for some reason, determine that most of the elements are not visible and so it does a ton more work. TIL.

Once I include batching of messages from the server side this will work perfectly well for my needs.

Thank you everyone for helping to expand my understanding and for helping me piece together where the issue was.

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New

We're in Beta

About us Mission Statement