petelacey
Here is a dummy LV that replicates some strange behavior I’m seeing in my production application. As you can see, the app renders a mildly complicated HTML paragraph for every number from 0 to 1,000. It then allows you to filter that list to numbers evenly divisible by a user-entered value. That’s it.
defmodule ReadyroomWeb.Speriment do
use Phoenix.LiveView
def render(assigns) do
use Phoenix.HTML
~L"""
<form id="filter-form" phx-change="filter">
<%= text_input(:filter, :value, value: @filter_value, class: "form-control", placeholder: "filter by", phx_debounce: "100") %>
</form>
<%= for i <- @renderable do %>
<p>The number <b><%= i %></b> is <i>evenly</i> divisible by the <strong>number</strong> <code><%= @filter_value %></code>.<p>Here's a nested paragraph</p><div>This is also nested</div><p></p></p>
<% end %>
"""
end
def mount(_params, _session, socket) do
data = 0..1000
{:ok, assign(socket, filter_value: 1, data: data, renderable: data)}
end
def handle_event("filter", %{"filter" => %{"value" => ""}}, socket) do
handle_event("filter", %{"filter" => %{"value" => "1"}}, socket)
end
def handle_event("filter", %{"filter" => %{"value" => filter}}, socket) do
renderable = Enum.filter(socket.assigns.data, &(rem(&1, String.to_integer(filter)) == 0))
{:noreply, assign(socket, renderable: renderable, filter_value: filter)}
end
end
If you run this, it initially renders very quickly. As you type filters, e.g., 10, 100, 1000, 10000, it continues to render quickly. But as you back over the digits in the filter, making it more and more inclusive, (1000, 100, 10, 1) it renders slower and slower. If you simply delete the filter, changing it from 10000 to 1 or blank, it takes (on my machine) nearly seven seconds to render.
Even more unusual, it slows down appreciably every time I stick a junk tag in the output. That’s what those noisy HTML tags are all about. If, for instance, I add an empty div tag to the beginning of the paragraph, the 7 seconds increases to 22 seconds!
I suspect I am missing something fundamental above LV and Morphdom. Any ideas?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
cmo
Not sure if it is important but you cannot nest
<p>tags, put divs inside p tags etc.petelacey
While I don’t have
tags in production, changing them for this example made a marked improvement. While it’s still slow-ish when the filter is deleted, it’s no longer pathologically slow. That is, 7 seconds has fallen to 1. Maybe, I have violated another HTML constraint and that’s why I’m seeing 30 second redraw times in prod. I’ll let everyone know if I find anything. And thanks!
BartOtten
Is it server side or client side delay? If the server sends a diff within milliseconds, morphdom
/js is having issues. If the server responds slow, we can start looking at LiveView/the elixir part
petelacey
Sorry, I meant to mention that. This is purely client side. Using the dev tools I can see the response come back nearly immediately, then the thread visibly freezes while it updates the DOM.
While the above code does reproduce the problem, keep in mind that making the HTML syntactically correct improves things dramatically. I reviewed my actual code, and don’t see any obvious HTML syntax problems. I will try and fine tune the example and see if I can get it to go pathological even with correct syntax.
chrismccord
Your for comprehension is going to render 1000 rows on the server, then send that over the wire, to re-render all 1000’s of DOM elements. The delay you see is probably 99% client side, but in general this is not the way to go about rendering such a large collection. Look into
phx-update="append"for handling this kind of operation efficientlyBartOtten
To add: Browsers in general do not like thousands of elements. If your lists’ root element has 10 children you are looking at 10.000 elements. Any browser will lag when you feed it such numbers without static positions and fixed sizes.
As relative position forces the browser to first compute the height of list element 1 (say 20px), then place the second element below (start 20px) and compute it’s height (again 20px) go on to the third and place it at 20+20=40 px and so on and on. Now this is an example with fixed height of 20px. Imagine if it is variable and it needs to include the calculations of child elements and you start to see the number of calculations which must be performed.
Browsers are highly optimized for this task but only when you match the specs. Once you nests a Paragraph in a Paragraph that optimization breaks and will cause even more render delay.
The solutions? None fits all but here are some candidates:
1.) ensure you keep the amount of elements to the minimum
2.) ensure only those within the visual space are rendered. This can be done with pagination and/or virtual lists. (Like in React with react-virtualized)
3. Use fixed sizes for elements
4. Use static positioning
petelacey
I appreciate the feedback, and I will adjust my approach accordingly. However, the production issue that prompted this does not actually have 1,000 “entities,” it has just 200. To paint a picture, it’s a filterable list of requests for documents. And while I’ve used
phx-update="append"for the simple chat interface that’s also in the product, I’m not sure how I would use it for a filter, that is, for a list the shrinks and grows.Now, each “request” is made up of a number of elements, divs, and checkboxes, and whatnot, but not excessively so. In fact, the page initially renders very quickly, which leads me to believe the browser can handle it reasonably well. Even as the user types in the filter field and the list shrinks, it continues to render quickly.
It’s when the filter is cleared that the page becomes unresponsive for 20-60 seconds as it is rebuilt. It’s that excessive delay, combined with the quick initial render, that leads me to suspect morphdom/liveview and not page complexity or browser rendering challenges.
That said, I’m more than prepared to hear that I’m doing something stupid or complex, I just don’t know what it is right now. Filtering a list of 200 entities, should not be that taxing.
derek-zhou
Can you check your browser console to see how much data it is receiving from the web socket? Are the number of messages and the size reasonable close to what you were assuming?
It still should not b that bad. I sent a very large nested list of 650 items that total about 500KB, my browser struggled for maybe 1~2 seconds. In both Firefox and Safari. On a phone, it only feels slightly slower.
petelacey
Okay, I have deployed an accessible version of this issue here: https://staging.readyroom.net/speriment
This version reasonably faithfully replicates my production issue in that the (dummy) HTML is structured the same as in prod and there are 200 rows (which is not an unreasonable page size). As you can see, it renders quickly at first.
There’s a filter field on top with a 500ms debounce setting. If you type a number in the field, it will filter the list down to just those rows that have an “ID” evenly divisible by that number, as before. Slowly typing 1 then 12 then 123 then 1234 will work as you expect. However, if you back over the filter (123, 12, 1), it will render progressively slowly. But simply deleting the filter is the most dramatic. In that case the 23K response returns in approximately 500ms, but the page doesn’t finish rendering until some 20-30 seconds later!
While I do appreciate the work browsers go through to render a page, in this case it seems clear that the issue is not due to page size or complexity, but it’s somewhere in LV/morphdom.
I do have a workaround, however. If I wrap that list in an element with an ID tied to the filter assign, then LV/morphdom doesn’t merge each node, but just chunks in a whole new list, which renders quickly. IOW, if I change the wrapping div to something like the following, it all becomes snappy again:
<div id="released-tasks-<%= @filter_value %>"Hopefully, this will help people in tracking things down.
cmo
Can we see the code for it?
Do you put IDs on the rows?