spicywebau / craft-neo

A Matrix-like field type for Craft CMS that uses existing fields
Other
402 stars 63 forks source link

Very slow CMS and content management (with Neo and SuperTable) #749

Closed janreges closed 1 year ago

janreges commented 1 year ago

Bug Description

Hi,

for our projects we use Craft CMS (PRO) and usually we need the client to compose the individual pages himself from the individual visual blocks. We implemented the management of the structured content of these blocks using Neo and SuperTable plugins.

Note: I am creating this ticket in the Craft CMS, Neo and SuperTable GitHub repositories. I am interested in the opinion of the authors and architects of Craft CMS and these plugins. After investigating these performance issues, it is evident that some optimizations and solutions to these problems can be implemented in the Craft CMS code and then subsequently in the individual plugins.

Facts about our project

Issues we have and perceive from the Craft CMS perspective

Our investigation of the problems and findings

craft-cms-slow

How can you help us

We would like to hear an informed opinion from the authors/architects of the existing code on the possible causes of these problems, but our investigation of the existing code shows that:

How to proceed?

We really like the Craft CMS and a number of useful plugins and we are trying to promote them. Unfortunately we have now run into some very fundamental performance issues.

So the first thing we thought of was to start investigating these causes ourselves, think of and implement some optimizations and then send pull requests.

As authors, do you please have an opinion on how to solve this situation? We'd be happy to help with optimizations, but if any of the Craft CMS architects have already had some thoughts and ideas on these improvements, we'd like to know so we don't do double work. This will also increase the chances that even if we design and implement some specific optimizations ourselves, they will be incorporated into future versions faster and without major comments.

You may also be self-aware that, based on some historical architectural decisions, some forms of efficient caching are now not applicable at all. For example, I imagine that serializing some complexly loaded objects into the cache won't work because the architecture requires that lifecycle of their initialization throws a number of events on which other functionality depends.

I would be very grateful for any thoughts and ideas on how to grasp and implement these necessary optimizations.

If needed, I can also provide a complete configuration of our CMS, including a database dump to your e-mail address.

Thank you very much for your time and support.

Patch getRootFolderByVolumeId()

--- /dev/null
+++ ../src/services/Assets.php
@@ -549,10 +549,18 @@
      */
     public function getRootFolderByVolumeId(int $volumeId): ?VolumeFolder
     {
-        return $this->findFolder([
-            'volumeId' => $volumeId,
-            'parentId' => ':empty:',
-        ]);
+       static $cache = [];
+       if (isset($cache[$volumeId])) {
+           return $cache[$volumeId];
+       }
+
+       $folder = $this->findFolder([
+           'volumeId' => $volumeId,
+           'parentId' => ':empty:',
+       ]);
+       $cache[$volumeId] = $folder;
+
+       return $folder;
     }

     /**

Craft CMS version

4.4.13

PHP version

8.1.18

Operating system and version

Debian 11 Bullseye

Database type and version

MariaDB 10.11.2

Image driver and version

GD 8.1.18

Installed plugins and versions

Steps to reproduce

See description.

Expected behaviour

No response

Neo version

3.7.9

Craft CMS version

4.4.13

What is the affected Neo field's propagation method?

No response

Does this issue involve templating, and if so, is eager-loading used?

This is not a templating issue

ttempleton commented 1 year ago

I'm going to close this as a duplicate of https://github.com/craftcms/cms/issues/13297 since most of it is not Neo-specific, but I've separated the points about the performance issues when editing Neo fields with a large number of block types, and suggestion of lazy loading of block type settings, into #752.

janreges commented 1 year ago

Hi @ttempleton - thank you for your reply and creating #752.

In the case of the division of responsibility in the code between Neo and Craft CMS, I estimated that an optimization could be made also in the Neo code, which would prevent the constantly repeating initialization of the field if it is used in several Neo blocks.

Example: for absolutely all types of blocks, we have an optional field htmlId - if it is filled, the wrapping HTML element of block on the frontend will have the HTML id attribute filled in, e.g. for the possibility of referring to part of the page with the anchor #myBlock or for similar purposes (element identification for E2E tests, JavaScript functionalities, etc.). It would therefore be appropriate for Neo to initialize this field only once and not 100 times, if it is occupied in 100 block types.

Is this my thinking wrong? Or do you think that specifically in this matter it is possible to perform optimization only in the code of the Craft CMS itself and not in the Neo plugin?

I know that in Craft CMS version 5 some improvements are also being made to Matrix, which Neo is partly based on. But we are trying to find a solution to this performance problem that we could deploy these days. I will be grateful for any ideas and quick-win solutions. Thank you :)

ttempleton commented 1 year ago

If I'm understanding correctly - e.g. if you're accessing block.someField where block is a block of any block type that has someField on its field layout - I'm not sure how feasible it would be for us to try Neo-specific optimisations, since getting the someField value involves code that runs by default for all Craft elements, so I'm guessing there would be a decent amount of duplication of that code that we would need to do to replicate the process in general. It'd be far better if any possible optimisations of that type were made in Craft itself, so that all types of elements - both internal and provided by plugins - could benefit.

janreges commented 1 year ago

@ttempleton - please accept my apology. I found and checked templates/block.twig in the Neo repository and now I understand you.

At the Twig templating and rendering level, you're really using variables directly from the Craft model, and you can't really cache much at that level.

For example, my thought was... I'm iterating through each block type of Neo block. If I find that a call in the sense of block.myField.type will search for information about the field type, I implement a simple cache at my level so that I don't ask for the myField type again when rendering the next block type with same field. In this case, however, I agree that this cache should be implemented on the model side in Craft. Thanks to this, other plugins, such as SuperTable, will also benefit from it.

I will try to do a deeper investigation into which parts of the Craft model code are called repeatedly (because lot of same fields are used in multiple blocks) and where they could potentially go to implement optimizations. I will then send these to Brandon as part of the task https://github.com/craftcms/cms/issues/13297

Thank you for your time.

ttempleton commented 1 year ago

Just released Neo 3.8.0, in which block type and block type group settings are lazy loaded, among a few other optimisations that should significantly improve the loading time for Neo field settings pages.

janreges commented 1 year ago

Hi @ttempleton.

Thank you very much - I tried version 3.8 and it works great! 😍

On AMD Ryzen 5950x, the initialization of the Neo block setup page in our project was shortened from 65 seconds to 1 second. This is great 👍

By the way, is it impossible to implement lazy-loading as part of addding/editing a page with a Neo block?

In our case, the user wants to create a page. It doesn't have any content yet, but it already has a Neo dropdown option with a selection of all available blocks. Even if you do not select a single block, or select 2-3, the forms for managing all blocks are still rendered when the page is loaded (even if they are hidden in HTML/JS). Therefore, the form for adding or editing a page is generated on the server for 5-10 seconds.

I assume that implementing lazy-loading at this level would be significantly more complex?

ttempleton commented 1 year ago

The code we have in place to render cloned/pasted blocks could probably be used to support lazy loading of block type input HTML/JS, so that's definitely worth looking into. I might not be able to look into it for a few weeks, though, but will open a new issue for it so I don't forget.

janreges commented 1 year ago

@ttempleton - that's great news! Thank you very much :hugs: This optimization will be essential for our team and our clients.

We will also deal with some related optimizations with the Craft developers. For example - below is a screenshot of Ajax profiling of the request that executes after clicking on the configuration of the Neo block. Even on the powerful Ryzen 5950x, this request takes 1.6 seconds, on the average server it will take, for example, 4-6 seconds. One of the reasons is the non-optimal lifecycle of Craft CMS architecture. For example, it is very inefficient that even with an Ajax request like in your plugin, SEOmatic and other plugins or CMS functions, which generate hundreds of DB queries and thousands of events, have to be initialized unnecessarily. A bunch of completely unnecessary overhead and load that the Ajax request itself doesn't need at all.

Ajax requests are made by other plugins as well, that's perfectly fine. With another plugin, for example, we see an Ajax request that performs 2 DB queries and returns 2 small text items in JSON. The whole request could take 10-20 ms even with authentication, instead it takes 1.5s (150x slower).

image