Fork me on GitHub
#fulcro
<
2024-02-07
>
magra14:02:06

I tuned a page with {:only-refresh [stuff]} but when I send data to a remote I get two extra root renders. The page is optimistic and an error from the remote will retract changes so this specific page should not render from root as it has a very large and slow query. What would be an idiomatic way to disable remote and ok-action to cause two extra root renders or at least honor {:only-refresh [stuff]}?

magra15:02:31

I can prevent the first root render by disabling the update-handler in http-remote. I see that it takes this update-handler from send-node. How do I controll the update-handler in send-node?

magra15:02:04

I can prevent the second root render by pulling the [:com.fulcrologic.fulcro.algorithms.tx-processing/options :only-refresh] from the the mutation remote env and swapping it into the runtime-atom as :com.fulcrologic.fulcro.application/only-refresh.

tony.kay18:02:49

Advice number 1: Check to see if it is really a performance issue in production. Usually Fulcro Inspect is your real slowdown. If so, you can stop worrying about it. . In production the performance will be fast enough that the render debounce will elide the extra render…plus, given that the props prob didn’t change the components probably (mostly) didn’t render anything anyway. side effects to the DOM didn’t happen, etc. If you use syncrhonous transactions or even sub out the tx processing for sync processing, you may find that sufficient. This removes the async nature of things as much as possible, though it behaves a bit differently. It will tend, however, to issue render request less frequently. Advice number 2: Use dynamic routers. They split the query up into just what is on-screen. Your query should not be that large in most cases. Of course this only helps if you don’t actually need the entire mess all at once. Advice number 3: If your query is that large, why? If it is a blob of data for some kind of graphical, can it change via local action? In other words: Does it need to be normalized for any practical reason? You can store an arbitrary data structure of any kind under a single prop, and the query performance on that will be quite fast. Denormalization is what is costing you, and if it is a large query on immutable data, don’t normalize it. If you still need component queries for running the server query, then do your loads with an component that has the query you want, and then use a UI component that has a query looking for just the prop.

tony.kay18:02:20

If you’re saying the only-refresh has a bug (it seems like you’re saying the kw has the wrong ns internally), then I’d gladly fix that

tony.kay18:02:35

but the other advice all still stands

magra18:02:49

I do use dynamic routers and most of my pages in the SPA are super fast. There is a few pages though where I sometimes expand everything so people can use F3 to search everything. How do I check get-query manually though? When I surf to a huge page and call get-query on registry-key->class this query seems to query for all routes at once. How do I manually get the routed query the app would get to measure it? Or where in the code would I insert (time) to measure the actual query time? Or am I thinking wrong? As I understand the root render will get the whole query-tree from root before this gets passed to react. The root render seems to take 2500+ ms while the react profiler says it took 70 ms. I suspect the root query to take 2000+ ms. The targeted refresh takes 4-5 ms in react profiler and is super fast. Is it a design decision, that a transaction that uses only-render and has an action, a remote and an ok-action (and error-action) renders 3 times? And is is a design decision that keyframe-render2 gets called 3 times and sees only-refresh as the #{[ident]} the first time and #{} the second and third time?

tony.kay19:02:53

In order to see dynamic queries you have to pass the current state map to get-query. It uses a dynvar at runtime to do this. But you can do it manually (get-query C state-map) where (app/current-state app) will give you the current state.

tony.kay19:02:50

I would recommend using the Google Chrome Performance tab in dev tools, you can get a flame chart of CPU usage and narrow it down that way. You’ll see the client inspect communications, and calls to db->tree. If you wanted to time the “current root query” manually, then just run db->tree using the current query (see above) and state map.

tony.kay19:02:27

(let [state-map (app/current-state the-app)
       query     (comp/get-query root-class state-map)]
   (fdn/db->tree query state-map state-map))

magra20:02:49

Thank you!!! I had missed the two arity variant of get-query. And I miswrote. When I wrote get-query takes up the time I meant (db->tree query state-map state-map) and I just checked, it takes up 80% of the flame graph. I know that my data-tree should be smaller. Most of the time it is. This is a worst case of users using the software in a way I had not foreseen. I am measuring the production worst case here. I speculate, that only-refresh is put into the runtime-atom and gets taken out the next render because there would otherwise be no telling when to take it out. So it will only work once and a mutation with remote calls render thrice. In the ok-action I can work around this by swapping only-refresh as set into the runtime-atom again, which is good for one render. This still calls render though but targeted with only-refresh. Would it be possible to pass in an option to transact! or in the remote action, so that tx-nodes do not call render at all or refuse to render if there is no only-refresh found? Would I break anything, if I took the call to schedule-queue-processing! out of progress-handler* in txn/add-send! This does eliminate the second render.

tony.kay20:02:50

So, the calls in there are necessary. Progress would not refresh. If you want to make your own copy of the tx processing and take that out you can certainly do so. That’s why I left hooks so you can plug in your own. I am surprised you’re managing to hit 2s of db->tree. that’s some extreme data. In terms of only-refresh, yes, it is the case that that tx option should carry through the entire operation (full stack), so an improvement would be to carry it along in the tx processing queues so that it gets honored on all refreshes.

tony.kay20:02:57

The tx processing, as you might guess, is the most sensitive area in Fulcro. Minor changes there can have unforseen side-effects to downstream users, so I’m very careful about what I change. Usually I’d prefer to add a new “version” of tx processing that you can opt into (like the batch processing or synchronous stuff). The only-refresh even meets that criteria: someone might be using it expecting that it only has a limited effect, so changing it to carry further could easily break existing code

magra23:02:54

You are right. I have modified kfr2/render-stale-components so that it checks for an extra key :skip-root-render? in the runtime-atom. route-to! removes this key so it is temporary until one routes away. I put that key in there when I enter a monster page and have to be meticulous to specify :only-refresh on mutations I use while on the monster page.