Benjamin Benjamin 21.10.2019

Improving Magento 2 Performance

This summer we worked together with David on the performance of a Magento 2 project and found some general improvements that the whole Magento community will benefit from.

Plugins querying the current Magento Version

Many plugins support multiple minor versions of Magento (2.0, 2.1, 2.2 or 2.3) and may need to check the version of core, to invoke slightly different behavior that changed over the different versions.

As a plugin developer, this is possible by calling ProductMetadata::getVersion() inside your code or plugin. However, this method is not just a "getter" like its name suggests, but it actually performs a quite expensive operation.

See in this Tideways callgraph, that the call takes 174ms for the request of our customers shop:

The reason is here that the method actually uses the Composer API to query the version of Magento, instead of returning a fixed constant. The Composer API used loads all package versions, this can include packages that get their version set by Git branch, which causes Composer to do a shell call to git to retrieve the version.

Again naming strikes as a hard problem of software engineering here. Especially for public APIs of frameworks that are used by different developers than the authors, it is important to set expectations about the performance by naming methods well. If the name would have been extractVersionFromComposer(), then plugin developers would maybe have realized the negative performance implications much earlier.

Now, thanks to David's Pull Request to Magento, the return value of this method will be cached indefinitely across requests since version 2.3.4, fixing the bottleneck after 4 years in a stable Magento releases.

If you still run lower minor versions of Magento, you can fix that problem by overwriting getVersion() to return a harcoded value, for example 2.2.2.

Implicitly Resetting Magento Compile Step

What we realized looking at the callgraphs was that a lot of code was using development-environment factories, even though the container was built with the command setup:di:compile and should be in production environment.

This was caused by the command module:enable -all executed after the compile step and resetting the cache partially. In hindsight it makes sense that the compile step generates the config cache for the current configuration, and when you enable modules then the compile step needs to be re-executed.

As for the specific shop we worked on, response times dropped as much as 700ms from 1 second to 300ms by caching the call to getVersion() and fixing the DI cache.