Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: Improve UI loading performance by removing UI nodedata script tag and using less data #3770

Draft
wants to merge 4 commits into
base: 8.3
Choose a base branch
from

Conversation

Sebobo
Copy link
Member

@Sebobo Sebobo commented Apr 24, 2024

What I did

With this change the minimal required nodedata for each node in the rendered
content is inserted as data attribute and not as inline script anymore.
This improves performance as no extra function call is executed for each node.

Additonally the reduction in rendered node attributes reduces the output filesize and again improves loading time.

To prevent just-in-time loading of nodes all incomplete nodedata is requested after the guest frame has finished loading.

One big benefit of that is that the scripts tags won't interfere with JS plugins anymore and using the Fusion Augmenter will not wrap a single object with a ContentWrapping with another tag due to the script tag.

Fixes neos/neos-development-collection#2988

How to verify it

  • Check whether content is still properly editable
  • Inspector shouldn't show a loading spinner when content is selected
  • preview document should be smaller (10-30%) than before and load quicker (10-40%)
  • No script tags with node data in the raw preview html document
  • Load nodedata async after guest frame has loaded
  • Visualise for user that backend still loads data before page is fully editable

…nodedata

With this change the minimal required nodedata for each node in the rendered
content is inserted as data attribute and not as inline script anymore.
This improves performance as no extra function call is executed for each node.

Additonally the reduction in rendered node attributes reduces the output filesize and again improves loading time.

To prevent just-in-time loading of nodes all incomplete nodedata is requested after the guest frame has finished loading.
@Sebobo Sebobo added Bug Label to mark the change as bugfix 8.3 labels Apr 24, 2024
@Sebobo Sebobo self-assigned this Apr 24, 2024
@Sebobo
Copy link
Member Author

Sebobo commented Apr 24, 2024

I'm not sure how breaking this could be right now, therefore draft for now ;)

@grebaldi I remember you started working on a replacement for the script tag, so your insights here would be very welcome.

This doesn't need to be included in 8.3 but maybe we can :)

@Sebobo
Copy link
Member Author

Sebobo commented Apr 24, 2024

Some test results with 82 nodes (haven't found a good way to measure time to interactive with the iframe):

With Script Tag + with full Properties

Document size: 245KB / 14.6KB
Document rendering time: ~220ms
Transferred: 3.3MB
Finish: ~1.7s
Load: ~620ms
Requests: 28

Without Script Tag + with MinimalProperties + ondemand node loading

Document size: 198KB / 13.3KB (~80%)
Document rendering time: ~140ms (~65%)
Finish: ~1.6s
Load: ~620ms
Requests: 28

Without Script Tag + with MinimalProperties + Node batch loading

Document size: 198KB / 13.3KB (~80%)
Document rendering time: ~140ms (~65%)
Finish: ~2.1s
Load: ~660ms
Requests: 29

*
* @var Locale
*/
protected $rememberedUserLocale;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those changes related?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a 100%, just some optimisation as the locale is switched twice for every node triggered by the augmentation

@Sebobo
Copy link
Member Author

Sebobo commented Apr 24, 2024

I tested a variant of this change in my customers project and the document size was reduced from 1.8mb to 1.2mb.
Without any nodedata it's 700kb. So I think we should invest further time in the future to really only include what we need.

Also there are two child nodes queries for each node to get document and content child nodes. Maybe we also don't need those for the initial rendering with some adjustments. Right now it would break some parts of the ui.

@Sebobo
Copy link
Member Author

Sebobo commented Apr 24, 2024

OK optimised a bit more and completely removed nodedata from the guest frame:

Without NodeData in augmenter

Document size: 108KB / 7.5KB (~44%)
Document rendering time: ~123ms (~55%)
Finish: ~2.1s
Load: ~610ms
Requests: 30

When clicking through the demo site each page is about 50% quicker to load which actually feels much nicer.

@bwaidelich
Copy link
Member

Great initiative, thanks for taking care.
Just one consideration: additional HTTP requests are fast locally, but they can be a bottleneck on remote servers. Not suggesting, that it is in this case but we should keep it in the back of our heads and test with throttled bandwidth

@Sebobo
Copy link
Member Author

Sebobo commented Apr 24, 2024

Yes of course. At the end we need the same amount of nodedata. But we kill the overhead of the function calls and document parsing.
Even if the combined loading time is the same as we load the nodedata, it feels faster as the page is already displayed much earlier while the data is fetched and the JS doesn't block the rendering. We send a bunch of queries at the initial load, so while the user starts moving the mouse to an element ideally the data is already there and simply be injected into the store instead of being parsed.
This makes especially a difference when you quickly navigate between pages.
The inspector also shows a loading spinner if the node is not fully loaded yet. So we have to check whether other places might need some visual feedback that we are still loading something if the connection is slow.

@Sebobo
Copy link
Member Author

Sebobo commented Apr 25, 2024

@bwaidelich I tested the loading performance a while yesterday with "Fast 3G" connection + Caching in my local Dev Context.

Result: The page loads much faster, but if you try to immediately edit text in a CKEditor, the first click only selects the element instead of showing the caret to start editing. The second click then works.

I want to test this on an actual remote server in Production Context to get a feeling for it.

I also checked whether I can adjust the LoadingBar to wait for the GuestFrame to be initialised, but this is not a simple change right now. I would make sense to have a loading indicator of some kind for the guest initialisation too. This is already currently problem, when initializeGuestFrame takes a bit longer. Just now with my change it takes a bit longer depending on the connection.
But the request runs in parallel with other requests like fetching node policies etc. So it's not simply adding to the loading time. But of course this depends a bit on the setup too.

We currently also load some nodes twice as the structure tree needs them too. I want to check whether I can then skip some of them.

@Benjamin-K
Copy link

Thanks for taking care of this. As a side effect, this will also help with some CSS selectors, as with the current approach selectors like .element:last-child don't work as expected in the backend.

@Sebobo
Copy link
Member Author

Sebobo commented May 6, 2024

Realised today this will also fix the issue neos/neos-development-collection#2988 with the Fusion Augmenter when augmenting a single content element. Previously it added another tag as it counted the element itself and the script tag instead of modifying the outside tag of the content element.

@Sebobo
Copy link
Member Author

Sebobo commented May 6, 2024

The two main performance hogs which makes the async request for the nodedata slow is in \Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper::renderChildrenInformation which queries each nodes children twice.
In my tests this takes at least 50-60% of the duration.
The other one is the very inefficient conversion of each node context path from the query to an actual node.

It seems this was less of a problem in the previous version as during rendering the same first-level-node-cache was automatically used where possible.
I think this could be optimised quite a bit in a separate PR.

Right now I'm getting great results by optimising \Neos\Neos\Ui\ContentRepository\Service\NodeService::getNodeFromContextPath in skipping the creation of a completely new context with site and domain for each requested node.
For the child node query I don't have a great idea right now. This could be easier with Neos 9 maybe.

@Sebobo Sebobo force-pushed the bugfix/remove-ui-script-tag branch from 5242e1d to c439582 Compare May 7, 2024 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8.3 Bug Label to mark the change as bugfix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants