kuon

kuon

Performances when using live view with large table

I am using live view to render a table:

  <table>
    <thead>
      <tr>
        <th>#</th>
        <%= for c <- @columns do %>
        <th>
          <%= tr(c.display_name, 32) %>
        </th>
        <% end %>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <%= for {r, rnum} <- @rows_index do %>
      <tr class="<%= if rnum == @selected_y do "selected" end %>">
        <td><%= rnum + 1 %></td>
        <%= for {val, cnum} <- r  do %>
        <td phx-click="select_cell"
          phx-value-x="<%= cnum %>"
          phx-value-y="<%= rnum %>"
          class="<%= if cnum == @selected_x && rnum == @selected_y do "selected" end %>">
          <%= val %></td>
        <% end %>
        <td></td>
      </tr>
      <% end %>
    </tbody>
  </table>

I have about 150 columns and 10 000 rows.

The problem is that when I do the select_cell call, and update selected_x and selected_y the update takes a few seconds.

Is there a way to optimize this with live view, or should I move to JS?

First 10 of 18 Posts Switch mode

chrismccord

chrismccord

Creator of Phoenix

You should use temporary assigns for the rows and you can either use phx-update="append" and append the two updated rows (the old row which is now unselected and the new selected row), or you can use a component for reach row, and do a send_update to the old selected row, and newly selected row. Both options will send the minimal diff down and not require holding 10k rows in your server state.

kuon

kuon OP

If I get this right, I should:

  • move data to temporary assigns
  • create a row component
  • put phx-target to myself on the row component and handle the event in the row component
  • in the event handler, I use pubsub to broadcast the row change selection
  • in handle_info of the live view, I send_update to the two affected rows

Is that the way to go?

chrismccord

chrismccord

Creator of Phoenix

You don’t need the pubsub step and instead of targeting the component, let the parent LV handle the event, then it performs the send_update to both the component children. The parent LV has the selected state, so it can use that to send_update the selected component, then the handle_event params can contain the desired selected row, which you will use to send_update the newly selected component. Make sense?

kuon

kuon OP

Yes, it is working with send_update from the live view.

I did create a component for each rows, but with large tables (>100 columns), even updating a single row takes a noticeable time.

Is it possible to nest components? Like having a component for the row that would just render <tr class="..."> and only handle the switch of class on the the row while using a for loop to render sub components? Would that just work?

Also, I have a “tab” selector above my tables, and when I change tab, I need to re-render the whole table. Right now, live view is sending a “huge” diff with every rows when changing tabs. Is it possible to tell live view to just re-render the container and replace the whole container content in one operation?

50kudos

50kudos

I have quite similar application, using send_update can get hairy at some point (like jQuery approach, sometimes it needs other components updated too e.g. resetting [ui] state for the rest, and only update a component)

I have a very big tree component (recursively rendered), so yes nested components is possible. However, the way out often is rethinking of state design. It’s quite tricky to get minimal diff payload for large DOM nodes.

I am moving towards more pagination, less DOM nodes is better. For the “tab”, you could possibly remodeling your assigns, moving assigns that make it marked as “changed” out, when switching tab 1 → tab 2 → tab 1 (this stays unchanged), but if you want always fresh data, I don’t think there is a way around other than reducing DOM nodes. (And I think toString on javascript side is not that performant .. something around Array.from that is slow)

Also be aware of memory usage per process (per browser tab) too!

kuon

kuon OP

I tried to have a top component, like this:

... here html for a tab selector that change @table assign

  <%= live_component @socket,
        CardioDataWeb.DBLive.TableComponent,
        id: :main,
        table: @table,
        sel: @sel
  %>

This above component render a

tag with thousands of cells.

Now, when I change the @table in the top component, instead of just having 1 single big diff like wrapper.innerHTML = "some big chunk of html" I have thousands and thousands of diffs which takes tens of seconds to be applied. (if I render the HTML normally without live view, rendering in the browser is instant)

My tab selector could be regular links, but I want to keep the state between tab changes.

My use case is some sort of spreadsheet editor. I could use some JS lib, but I tested native HTML rendering, and everything is butter smooth with thousands of rows, browsers are really good at managing large pages and I thought I could leverage this with live view, but live view needs to have minimal diff.

kuon

kuon OP

I also tried nesting live views, but when the child live view is unmounted/remounted, there is also a huge diff which takes a very long time to be processed (much longer than just doing similar to innerHTML = newdata).

50kudos

50kudos

My untested idea is that you could do something like what infinite twitter feed does, only render rows fit within screen viewport, not sure if it would be clunky or not. For example, 300 rows. The downside is native search on browser will suck.

That’s the only solution (pagination) I stick with, I gave up minimizing diff payload in other way since it’s going to have at least a bunch of one character keys (a lot of them alone without content is huge)

Or just give up view layer on server for those cells, just push_event with pure data and use whatever lib to render the cells on client.

50kudos

50kudos

Also I guess your sel: @sel sit on every row (somewhere like <%= if @sel, do: "" %>), its change always mark the whole table (i.e. every row) as change and produce diff payload of every row even if @table data is unchanged.

kuon

kuon OP

Well “smart partial client side rendering” is possible, but I want to avoid that because rendering the whole table works very well even with huge table.

And I don’t want to minimize diff size, what I’d like is to have a single large diff for the table when the table change, not a million small diffs for all element in the table.

As I said if I re-render the whole table, I’d like the container’s content to be swapped with the new content, in one single operation. I don’t want a million diff be applied, which is very slow.

So I am wondering if this is possible with live view.

Where Next?

Trending in Questions Top

jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
silverdr
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated! To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead. Sta...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
michallepicki
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
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
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement