#61451 closed defect (bug) (fixed)
Improve performance of block style variation registration and retrieval
Reported by: |
|
Owned by: | |
---|---|---|---|
Milestone: | 6.6 | Priority: | normal |
Severity: | normal | Version: | 6.6 |
Component: | Editor | Keywords: | gutenberg-merge has-patch has-unit-tests |
Focuses: | Cc: |
Description
This ticket tracks the backporting of PHP files for the following Gutenberg updates:
- https://github.com/WordPress/gutenberg/pull/62529
- https://github.com/WordPress/gutenberg/pull/62610
These changes prevent running expensive tasks around parsing theme.json partial files unnecessarily.
Change History (49)
This ticket was mentioned in PR #6837 on WordPress/wordpress-develop by @aaronrobertshaw.
11 months ago
#1
- Keywords has-patch added
@oandregal commented on PR #6837:
11 months ago
#2
I've scoped down this backport to only the changes in https://github.com/WordPress/gutenberg/pull/62529 The other PR needs more time to clarify questions/address feedback.
@oandregal commented on PR #6837:
11 months ago
#4
Committed at https://core.trac.wordpress.org/changeset/58423
This ticket was mentioned in Slack in #core-performance by nosolosw. View the logs.
11 months ago
This ticket was mentioned in PR #6843 on WordPress/wordpress-develop by @oandregal.
11 months ago
#6
Trac ticket https://core.trac.wordpress.org/ticket/61451
## What
Read only the theme.json files stored in the styles/
folder once.
## Why
I've realized that, unlike the theme.json
files defined by core & theme, we don't cache these — hence we end up reading them a few times from the filesystem.
## How
By using the existing read_json_file
function instead of using wp_json_file_decode
directly.
This ticket was mentioned in PR #6844 on WordPress/wordpress-develop by @oandregal.
11 months ago
#7
Trac ticket https://core.trac.wordpress.org/ticket/61451
## What
Register the block style variation coming from the different places (theme or user theme.json
& theme.json partials) in the WP_Theme_JSON_Resolver
.
## Why
The init
hook has proven problematic, as it's also fired during installation, see slack conversation cc @swissspidy.
## How
The main advantage of this approach is that we register them when the data is read, so we don't have to do any extra data reads:
theme
&custom
origins:- theme.json partials:
The disadvantage is that these block style variations are not registered everywhere, so we have to do that in the specific places that are not yet covered:
- In the global styles endpoint:
## Commit
Do not use init to register block style variations defined via theme.json. Props oandregal. See #61451.
@aaronrobertshaw commented on PR #6843:
11 months ago
#8
I wonder if we can also avoid the potential of processing the same decoded files multiple times as well?
This PR was an initial step given issues encountered in an original attempt to also cache the additional processing of those files in: https://github.com/WordPress/gutenberg/pull/62610
@aaronrobertshaw commented on PR #6844:
11 months ago
#9
I'm not sure I like the idea of adding side effects to the methods that are intended to return the parsed WP_Theme_JSON objects from each origin.
Agreed, this aspect isn't ideal.
I think the previous approach of having a dedicated function that has the responsibility for registering style variations is a much better design
This was the thinking behind the original switch to using the init
action to register block style variations. As you noted though it is too broad and ironically has come with a number of its own side effects 😅
I think adding an early return to wp_register_block_style_variations_from_theme() if wp_installing() returns true is probably a better initial improvement.
This could be a good fallback option. One of the problems with leveraging init
is that we need to know all the scenarios where we don't want this registration to occur and that is proving difficult. In my view, there's a much smaller set of use cases where the registration needs to happen than not.
Leaving the side-effect issue aside, the approach in this PR suffers the flip side of the above issue. We need to know the scenarios in which block style variations should be registered. This is much more easily reconciled and reasoned about. The happy path for the feature can easily tested.
Can you provide more context about how this variation data gets used?
The block style variations need to be registered to the block styles registry so that their data is not incorrectly stripped during the sanitization of theme.json data. In this context the two are fairly closely linked. This sanitization of variations was deemed important to maintain from a security perspective.
If it's mainly for ensuring styles are enqueued, perhaps we could do this on the enqueue_block_assets hook at priority 9
Unfortunately, there is a little more to it than that. As noted, the registration is also required for the sanitization of theme.json data not just ensuring stylesheets are enqueued. André and I have at different times in the implementation of the feature explored different actions and filters to hook onto but haven't found a workable alternative to init
.
---
I'd like to stress that I agree that having registration as a side effect of processing the theme.json data isn't ideal. However, I think for beta 3, we are best served to proceed with the approach in this PR.
This is on a couple of grounds:
- It doesn't introduce any public method/new API/etc.
- It could be considered an implementation detail which leaves the door open to switch back to
init
if desired once all the edge cases and unknowns can be methodically worked through - It limits the work done to only where and when we need it
The downside other than the design involving a side effect is that we need to ensure the registration occurs everywhere needed. This can be offset by ensuring the happy path for the feature is fully functional. I believe the testing of the feature prior to the switch to using init
provides some confidence here.
Ultimately, this PR's approach helps lower our exposure to unexpected consequences of leveraging init
and reduces the potential impact of bugs this late in the release cycle. We are still free to iterate further, even post 6.6, if there are no public functions and APIs added.
@andrewserong commented on PR #6844:
11 months ago
#10
On balance, I agree, I think registering block variations as a side effect of retrieving resolved theme JSON data is preferable to using init
as the catch all for registration. I also like the idea of it being an implementation detail so it can continue to be refactored if we think it warrants it after the fact.
@ramonopoly commented on PR #6844:
11 months ago
#11
Ultimately, this PR's approach helps lower our exposure to unexpected consequences of leveraging init and reduces the potential impact of bugs this late in the release cycle. We are still free to iterate further, even post 6.6, if there are no public functions and APIs added.
My reading is that the pros of this PR's approach outnumber the current, demonstrated pros of using the init
or any other hook.
Furthermore, the limitations — the introduction of side-effects to these methods — are recognized, and are also isolated to specific methods, which appears to me to be more information than thus far is known about the consequences of registering variations in the 'init'
hook.
Therefore it sounds like a viable path to document these limitations and come up with a plan to iterate if folks want this feature to make it for 6.6.
I'd like to stress that I agree that having registration as a side effect of processing the theme.json data isn't ideal.
Just so I understand, in the case of the Global Styles controller, is the processing of theme.json data _required_ to generate an accurate REST transaction?
@aaronrobertshaw commented on PR #6844:
11 months ago
#12
Just so I understand, in the case of the Global Styles controller, is the processing of theme.json data required to generate an accurate REST transaction?
What's really needed for an accurate REST transaction is for block style variations to be registered at the time the global styles post type is saved as well as prepared for response. Without being registered the block style variation's data will be sanitized and stripped out, creating the issue/inaccuracy.
@ramonopoly commented on PR #6844:
11 months ago
#13
Without being registered the block style variation's data will be sanitized and stripped out, creating the issue/inaccuracy.
Thanks for the explainer. I was just trying to square it in my brain whether, in the case of the controller, it's really
side-effect given in the input and output values.
@ramonopoly commented on PR #6843:
11 months ago
#14
@ramonopoly commented on PR #6843:
11 months ago
#15
I wonder if we can also avoid the potential of processing the same decoded files multiple times as well?
Very good question! I'll keep that in mind when testing https://github.com/WordPress/gutenberg/pull/62638, maybe there's an way to test in the plugin first.
@aaronrobertshaw commented on PR #6844:
11 months ago
#16
I've created a matching Gutenberg PR for these changes: https://github.com/WordPress/gutenberg/pull/62640
Initial smoke tests look good. I'll start giving it a thorough run and review shortly.
@aaronrobertshaw commented on PR #6843:
11 months ago
#17
Might be worth adding a note about the caching to the @since comment?
I've taken then liberty to push this minor tweak.
@isabel_brison commented on PR #6843:
11 months ago
#18
Just gave this a test run and switching styles and variations works as expected!
@isabel_brison commented on PR #6843:
11 months ago
#21
committed in r58428.
@aaronrobertshaw commented on PR #6844:
11 months ago
#22
After the latest tweaks I've given this another run. It tests well still and the comment tweaks are a nice touch. Thanks!
LGTM 🚢
@oandregal commented on PR #6844:
11 months ago
#23
The different alternatives have pros/cons, none is perfect. Given what people has shared and the current time-constraints, I agree we should go ahead with this approach.
@oandregal commented on PR #6844:
11 months ago
#25
Committed at https://core.trac.wordpress.org/changeset/58429
#26
follow-up:
↓ 27
@
11 months ago
We've done a couple of things for this ticket:
- The feature now only registers block style variations when the theme.json is processed (no extra theme.json parsing, no extra query to retrieve user-data, etc.) + specific paths (user updates the styles of the block style variations). Ref: https://github.com/WordPress/wordpress-develop/pull/6844
- Cache the reading of files stored in
styles/
as per https://github.com/WordPress/wordpress-develop/pull/6843
I'm not sure what's the definition of "done" for this ticket. I believe the two things above could be good enough for the 6.6, though we should keep investigating.
Further opportunities / work mentioned or in current investigation are:
- Cache the partial theme.json files _after_ they are processed (translated). One approach could be https://github.com/WordPress/gutenberg/pull/62610
- Create a performance test case that includes block style variations defined in a partial theme.json file, so every PR has that info available. Also including the
theme.json
file or theme style variations would be good, though it seems more impactful to test the partials (new standalone file, the source of variations that are always present, etc.). - The styles generation for the block style variations use the
wp_theme_json_data_*
filters. This requires that we re-create some theme.json structures. We could look at the whether absorbing them as part of theWP_Theme_JSON_Resolver
and/orWP_Theme_JSON
merge is impactful.
#27
in reply to:
↑ 26
@
11 months ago
Replying to oandregal:
I'm not sure what's the definition of "done" for this ticket. I believe the two things above could be good enough for the 6.6, though we should keep investigating.
I'm not certain either. I agree this can be considered "done" for 6.6.
I'm happy to create a separate GB issue or ticket to track further investigations and improvements if it helps.
This ticket was mentioned in Slack in #core-performance by nosolosw. View the logs.
11 months ago
This ticket was mentioned in Slack in #core-performance by joemcgill. View the logs.
11 months ago
This ticket was mentioned in PR #6857 on WordPress/wordpress-develop by @oandregal.
10 months ago
#30
Trac ticket https://core.trac.wordpress.org/ticket/61451
Follow-up to https://github.com/WordPress/wordpress-develop/pull/6843
Backports https://github.com/WordPress/gutenberg/pull/62610
## What
This PR caches the post-processed style variation files (theme.json within the styles/
folder).
## Why
To improve performance, so we don't have to process them more than once.
## How
Introduces a static $style_variations_cache
variable.
@oandregal commented on PR #6857:
10 months ago
#31
@joemcgill what are your thoughts on doing this?
I was looking at the performance results for https://github.com/WordPress/wordpress-develop/pull/6843 (cache reading the contents from the filesystem), and I'm a bit confused.
I'm not familiar with what we are testing for the "Admin" test, so I don't know if it should have been impacted. However, for the "Front End › Theme: twentytwentyfour" I would have expected to be more impacted. Instead, I see timid or conflicting results:
wpMemory
: increased, which makes sense given we now cache those files.wpTotal
: it has been reduced minimally and increased (also minimally) in others.timeToFirstByte
: increased minimally
The results for this PR are similarly confusing:
wpMemory
: increased, as expected.wpTotal
&timeToFirstByte
are both higher (I'd expect the contrary).
This is my current understanding of the new feature:
- In the front-end, we may or may not need to enqueue new styles related to block style variations defined by the theme.
- This requires inspecting all the files within
styles/
which is done in theget_style_variations
method at some point.
Unless the tests are not triggering the file reads from the filesystem for some reason, I'd have expected the numbers to have improved with the cache (previous PR & this one).
@oandregal commented on PR #6844:
10 months ago
#32
@joemcgill commented on PR #6857:
10 months ago
#33
In theory, adding a cache here should be more performant. However, it seem that the refactor of WP_Theme_JSON_Resolver::get_style_variations
has also added a lot more processing to that method, and the cache (if it's even being used) is not offsetting the additional processing.
It seems odd to me that in trunk, none of the WP_Theme_JSON
construction calls were actually running. I've investigated this by setting a breakpoint in this method to see the code execution via XDebug, and it seems like in each of the 4 times this method is called on the TT4 homepage, it's always passing 'block' as the scope, but all of the variations are from the 'theme' scope, so in trunk
this is all being skipped and is returning an empty array, whereas now this PR is just checking to see whether there is a scope at all, not just that it matches the scope being passed to the method.
This seems like a likely bug.
#34
@
10 months ago
While spending time looking at possible performance implications of this feature, I noticed that the current implementation results in a large increase in the creation of WP_Theme_JSON
objects, which has been known to be a source of performance overhead in the past (see #61112).
Since [58264] the number of calls that method has increased significantly (profiling a request to the homepage of a default site running Twenty Twenty-Four):
WP_Theme_JSON::__construct
increased from 18 to 53
Additionally:
WP_Theme_JSON_Resolver::get_merged_data
increased from 5 to 21WP_Theme_JSON_Resolver::get_theme_data
increased from 8 to 24
Some of this is probably due to the way this data is being merged into resolved WP_Theme_JSON
origins through the use of wp_theme_json_data_theme
and wp_theme_json_data_user
filters, rather than directly within the WP_Theme_JSON_Resolver
class. I wonder if there are ways we can streamline this more to avoid the need to construct so many extra WP_Theme_JSON
objects. Given the size of these objects, I also am concerned that just adding caching will increase memory usage, which can also lead to significant performance bottlenecks.
This ticket was mentioned in PR #6868 on WordPress/wordpress-develop by @oandregal.
10 months ago
#35
- Keywords has-unit-tests added
Trac ticket https://core.trac.wordpress.org/ticket/61451
Follow-up work for https://core.trac.wordpress.org/ticket/61312
## What
This PR tries to optimize the data flow for operating with section styles.
## Why
It has proven less optimized than we wanted.
## How
At some point, the code transforms the following:
{ "styles": { "blocks": { "variations": { "myVariation": { "title": "My variation", "blockTypes": [ "core/paragraph", "core/group" ], "color": { "background": "yellow" } } } } } }
into:
{ "styles": { "blocks": { "core/paragraph": { "color": { "background": "yellow" } }, "core/group": { "color": { "background": "yellow" } } } } }
The existing code executes that transformation using the wp_theme_json_data_*
filters. This is, after any WP_Theme_JSON
structure is processed.
The optimization proposed in this PR explores moving that transform into the WP_Theme_JSON
constructor directly, following what we do with appearanceTools
settings.
## Test
TBD.
@oandregal commented on PR #6868:
10 months ago
#36
This is not finalized, but I wanted to share the approach as soon as possible to gather feedback and gauge the impact on performance.
@oandregal commented on PR #6857:
10 months ago
#37
This seems like a likely bug.
The feature is working correctly in trunk
as well as the related one (theme style variations).
I haven't had the time to look at this using xdebug today, as I was investigating a proof of concept to optimize the data flow.
@joemcgill commented on PR #6868:
10 months ago
#38
I like the idea of trying to move this processing directly into the parsing of a WP_Theme_JSON
object, rather than needing to normalize the JSON before passing it to the constructor of a WP_Theme_JSON
object. If we are expecting styles.blocks.variations.{variationKey}
to be a valid part of the schema, passing that shape to the WP_Theme_JSON
constructor should result in a correctly parsed object, IMO.
I ran some initial profiles on this PR compared with the parent commit (c1c8d30398) and can confirm that it currently has very little impact, but that is mostly because the processing being skipped, wp_resolve_block_style_variations_from_primary_theme_json()
is currently not doing much in trunk
:
Looking further into the profiling data, it looks like when wp_merge_block_style_variations_data
is being called, it's being passed an empty array, so no new WP_Theme_JSON_Data
is created, nor is anything merged. I'm guessing this is due to the bug I observed in https://github.com/WordPress/wordpress-develop/pull/6857#issuecomment-2178985546.
@joemcgill commented on PR #6857:
10 months ago
#39
Thanks, I'll do some more testing. I'm just surprised that currently no variations are being processed while running TT4 in trunk, but perhaps it's because those code paths aren't being triggered without adding some variations manually?
@oandregal commented on PR #6868:
10 months ago
#40
https://github.com/WordPress/wordpress-develop/pull/6873 takes the ideas from this PR and does a few other things.
This ticket was mentioned in Slack in #core by nhrrob. View the logs.
10 months ago
#44
@
10 months ago
- Resolution set to fixed
- Status changed from new to closed
Closing this as fixed now that we're at RC1. From this point on, only critical issues found during RC should be fixed and can be done in new tickets to keep the scope of those changes clear.
@aaronrobertshaw commented on PR #6857:
9 months ago
#45
@oandregal and @joemcgill what are the next steps that can be taken here?
There were also some changes recently around the theme.json resolver's functions retrieving style variations (see https://github.com/WordPress/gutenberg/pull/63318). Would it be easier to get those backported sooner or can we also approach this from the Gutenberg side?
@joemcgill commented on PR #6857:
9 months ago
#46
@aaronrobertshaw I think that this got abandoned last time because we were not able to validate that this cache actually had a measurable improvement in WP::trunk
and possibly even would have led to performance. Before adding this or a similar cache, it would be good to verify that it will result in a measurable performance improvement.
One of the challenges is that the code paths relative to WP_Theme_JSON_Resolver
are inconsistent between the GB plugin and when the same code is ported to WP Core, due to the way these systems currently get integrated via filters – See https://github.com/WordPress/gutenberg/issues/62594 for some context.
@aaronrobertshaw commented on PR #6857:
9 months ago
#47
Thanks for extra info @joemcgill 👍
I was following up as I'd been advised that TwentyTwentyFour was seeing a noticeable increase in TTFB when running WP6.6.1 without any plugins.
Some initial digging there apparently flagged the wp_render_block_style_variation_support_styles
filter as the likely culprit. This makes some sense as that filter calls WP_Theme_JSON_Resolver::get_merged_data()
which when retrieving the theme data, calls get_style_variations
that has to read and parse theme.json partials.
For the TT4 home page, the wp_render_block_style_variation_support_styles
filter is run more than a dozen times. Given there's only a simple static variable cache on the read JSON files at present, it seems like there's an opportunity for further optimization, such as caching the translated theme json data as proposed in this PR etc.
I'm not well-versed in debugging performance issues. Is there a recommended approach to profiling locally to establish a benchmark before tinkering with different caching strategies and other optimizations?
@oandregal commented on PR #6857:
9 months ago
#48
I'm not well-versed in debugging performance issues. Is there a recommended approach to profiling locally to establish a benchmark before tinkering with different caching strategies and other optimizations?
For measuring the "real" impact any change may have, one thing you can do is preparing a pull request — this is great if the change you want to test is trivial.
## Local setup
For a local setup, in the past, I prepared a production site and timed curl requests to the homepage. This is the TLDR of my process back then:
- Clone the wordpress-develop repository and switch to the corresponding version to benchmark.
- Set it as production:
WP_DEBUG
,WP_DEBUG_LOG
,WP_DEBUG_DISPLAY
,SCRIPT_DEBUG
tofalse
in .env. - Start the environment:
npm install && npm run build:dev && npm run env:start && npm run env:install
. - Time a request to the homepage. I used this command:
seq 1000 | xargs -Iz curl -o /dev/null -H 'Cache-Control: no-cache' -s -w "%{time_starttransfer}\n" http://localhost:8889 | xclip -selection clipboard
. Instead of dispatching thousand requests you can use 20/100/250, any number of requests that provides consistent results or a mean that's representative enough. - Paste the results in a spreadsheet to analyze further.
- Switch to a different scenario (theme, with/without a plugin, etc.). Go to step 4.
One additional step that can help spot issues is preparing the site with a variety of data. You can do so by importing the theme-test-data. Some old recipe I have:
npm run env:cli -- plugin install wordpress-importer --activate --path=/var/www/build curl -O https://raw.githubusercontent.com/WordPress/theme-test-data/master/themeunittestdata.wordpress.xml npm run env:cli -- import themeunittestdata.wordpress.xml --authors=create --path=/var/www/build
Testing locally may end up being very fast and the numbers won't really correspond to any real production environment as it doesn't account for network requests, servers are not usually M1/M2 machines, etc. However, the important part is whether results are consistent. If you are able to have them within a threshold, then pushing to a PR that runs the core benchmarks should display the same the difference you saw locally — even if the numbers are different. That's my experience at least :)
## Drill down into the full request
Sometimes, it may be useful to analyze how specific parts of the request react to some code changes — specially if the impact may be masked by the setup. For example, a change that scales with the number of plugins you have. The impact may seem minimal if you have X plugins active, but will be bigger if you have X+Y.
For this, I use server-timing headers. I have this old recipe (hope it still works 🤞) inspired by some gists shared by Felix:
ob_start();
$count = 0;
foreach( [ 'plugins_loaded', 'after_setup_theme', 'init', 'wp_loaded', 'template_redirect' ] as $hook_name ) {
add_action(
$hook_name,
function() use ( $hook_name ) {
global $server_timing_values, $timestart, $count;
if ( ! is_array( $server_timing_values ) ) {
$server_timing_values = array();
}
$count++;
$server_timing_values[ $count . '-' . $hook_name ] = microtime( true ) - $timestart;
},
-9999
);
}
foreach( [ 'template_include' ] as $hook_name ) {
add_filter(
$hook_name,
function( $passthrough ) use ( $hook_name ) {
global $server_timing_values, $timestart, $count;
if ( ! is_array( $server_timing_values ) ) {
$server_timing_values = array();
}
$count++;
$server_timing_values[ $count . '-' . $hook_name ] = microtime( true ) - $timestart;
return $passthrough;
},
-9999
);
}
add_action(
'shutdown',
function() {
global $server_timing_values, $timestart, $count;
if ( ! is_array( $server_timing_values ) ) {
$server_timing_values = array();
}
$count++;
$server_timing_values[ $count . '-shutdown'] = microtime( true ) - $timestart;
$output = ob_get_clean();
$header_values = array();
foreach ( $server_timing_values as $slug => $value ) {
if ( is_float( $value ) ) {
$value = round( $value * 1000.0, 2 );
}
$header_values[] = sprintf( 'wp-%1$s;dur=%2$s', $slug, $value );
}
header( 'Server-Timing: ' . implode( ', ', $header_values ) );
echo $output;
},
-9999
);
In the browser's console, you'd see something like:
Though I usually was interested in pasting this into a spreadsheet. So, similar to the above, I'd do something like this to copy the data and paste it into the sheet:
seq 100 | xargs -Iz curl -H 'Cache-Control: no-cache' -sD - localhost:8889|grep Server-Timing|sed 's/Server-Timing: //'|sed 's/wp-1-plugins_loaded;dur=//'|sed 's/wp-2-after_setup_theme;dur=//'|sed 's/wp-3-init;dur=//' | sed 's/wp-4-wp_loaded;dur=//'|sed 's/wp-5-template_redirect;dur=//'|sed 's/wp-6-template_include;dur=//'|sed 's/wp-7-shutdown;dur=//' | xclip -selection clipboard
Hope this helps!
@oandregal commented on PR #6857:
9 months ago
#49
btw, I'm going to close this PR as I won't pursue it, but the conversation can continue :)
This PR backports the PHP updates from:
The changes avoid repeatedly searching
/styles
directories, parsing theme.json partials, and translating them. The registration of custom block style variations will now exit early if the theme doesn't have a theme.json or partials directory.Trac ticket: https://core.trac.wordpress.org/ticket/61451