Skip to content

Conversation

@kt-12
Copy link
Member

@kt-12 kt-12 commented Jun 11, 2024

Trac ticket: https://core.trac.wordpress.org/ticket/57789

GB PR - WordPress/gutenberg#62794


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@kt-12 kt-12 changed the title Caching with get_theme_data Caching inside get_theme_data Jun 11, 2024
@kt-12 kt-12 marked this pull request as ready for review June 11, 2024 14:58
@github-actions
Copy link

github-actions bot commented Jun 11, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props thekt12, joemcgill, stellastopfer.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copy link
Member

@joemcgill joemcgill left a comment

Choose a reason for hiding this comment

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

Thanks @kt-12. I think loading the initial theme data from a transient could be a nice improvement, but at present, the potential gains are offset by the fact that transients with an expiration time do not get autoloaded, which results in this PR adding additional DB queries to each request.

Given that we really only need to invalidate the transient if the theme's theme.json data changes, I wonder if we can include a hash of the data just before it's passed to the wp_theme_json_data_theme in the cached data to use for invalidation, instead of the functional args and avoid the transient timeout alltogether?

Aside, this change should happen first in the Gutenberg repo and then get synced back to WP core.


$can_use_cached = ! wp_is_development_mode( 'theme' );
if ( $can_use_cached ) {
$cache_key = 'get_theme_data_cache_' . md5( wp_json_encode( func_get_args() ) );
Copy link
Member

Choose a reason for hiding this comment

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

I don't think using func_get_args here is necessary, because the theme data get's cached prior to when the with_supports option is calculated.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have used $options instead in the new update as we don't quite need other parameters. At the moment set_transient is getting called on every run which is the reason why we don't see any benefit. Comparison of two static variables is not working quite well also other interesting find (off topic) I am able to serialize those variables but not convert into JSON

Copy link
Member Author

Choose a reason for hiding this comment

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

@joemcgill You were correct we don't need $options too.

Copy link
Member

@joemcgill joemcgill left a comment

Choose a reason for hiding this comment

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

@kt-12 I've left a bunch of comments inline, about the specifics of this PR, but overall I have a few concerns:

First, the scope of this PR has now grown well beyond the original purpose, which was to make Theme JSON data from the current theme persistent, and seems to extend it to core data and block data as well. I'm not opposed to trying to add persistence to all three origins, but it does make cache invalidation a bit trickier, which is why we were originally talking about trying to handle these one case at a time—starting with just core and theme data. If there is value in handling all of these cases in the same PR, can you please update the PR title and provide some more context explaining why the scope of the PR has changed?

Second, I think that you can probably simplify a lot of the implementation and instead convert many of the static properties in this class to transients rather than duplicating cached data as a private static property and also as a transient. The easiest way to go about that might be to convert the *_from_persistent_cache() methods to general use get|set_cached_data() methods that populate the static values from persistent cache when $can_use_cached() is true.

Finally, one of the original reasons that these caches were made non-persistent is because it would cause the wp_theme_json_data_default and wp_theme_json_data_theme filters to get skipped. It seems like this approach will still suffer from that problem. I would love to see what the performance benefit would be of storing the theme.json config after it has been processed, but before they are passed to the relevant filters.

if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) {
$can_use_cached = ! wp_is_development_mode( 'theme' );
if ( $can_use_cached ) {
$cache_key = 'get_theme_data_cache_' . md5( wp_json_encode( $options ) );
Copy link
Member

Choose a reason for hiding this comment

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

I still don't think you need to include the options in the cache key here. $options are only used in this function to check for whether theme data should be returned with_supports, meaning whether classic theme theme supports should be merged into the final theme data. However, we're only caching the data from theme.json before it gets merged with theme supports data, so this options shouldn't affect our cache.

}

$has_theme_changed = false;
$has_same_registered_blocks = $can_use_cached ? static::has_same_registered_blocks( 'theme', true ) : static::has_same_registered_blocks( 'theme' );
Copy link
Member

Choose a reason for hiding this comment

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

This could be simpler

Suggested change
$has_same_registered_blocks = $can_use_cached ? static::has_same_registered_blocks( 'theme', true ) : static::has_same_registered_blocks( 'theme' );
$has_same_registered_blocks = static::has_same_registered_blocks( 'theme', $can_use_cached );

* @return mixed The cache value.
*/
private static function get_from_persitent_cache( $cache_key ) {
if ( null === static::$persistent_all_cache ) {
Copy link
Member

Choose a reason for hiding this comment

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

The need to back transients with a static cache should be unnecessary because options get saved to the site-options cache after being fetched from the DB so calling get_site_transient() for the same key multiple times during the same pageload should only hit the DB once.

}

/**
* Retrieves persistent cache data of this class for given key.
Copy link
Member

Choose a reason for hiding this comment

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

Update docblock description.

* @return bool True on success, false otherwise.
*/
protected static function has_same_registered_blocks( $origin ) {
protected static function has_same_registered_blocks( $origin, $persistant_cache = false ) {
Copy link
Member

Choose a reason for hiding this comment

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

Juggling two different caches for registered blocks seems overly complex to me. What if we just converted the static $blocks_cache to an autoloaded transient (no expiration) that only gets used if $can_use_cached is true? We're already validating the cache at the point where this is called by checking the current WP_Block_Type_Registry for all registered blocks.

Copy link
Member Author

@kt-12 kt-12 Jun 14, 2024

Choose a reason for hiding this comment

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

@joemcgill It was a bit of a design decision. I wanted theme data to be checked against the persistent list as we assume the blocks from the theme will not change before the expiry, whereas blocks from user can change dynamically at any instance.

$blocks_cache is just a static cache, and it contains blank arrays of origins in the first call.
If we use the original $blocks_cache the first time has_same_registered_blocks is called, it will always return false as initially $blocks_cache is set to blank arrays, and has_same_registered_blocks sees all blocks as new blocks.

Original has_same_registered_blocks can't be used with persistent cache inside get_theme_data as it will assume the block has changed and will update static::theme, thereby also calling set_site_transient().

Now I can't update $blocks_cache with precipitant value either, w.r.t value of $can_use_cached; as updating
$blocks_cache with persistent value will affect other origins, too, like user or block, which depend on change based on has_same_registered_blocks or not.

Let me check again to see if there is a scope for using just one variable.

static::$user = null;
static::$user_custom_post_type_id = null;
static::$i18n_schema = null;
delete_persitent_cache();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
delete_persitent_cache();
static::delete_persitent_cache();

'theme' => array(),
'user' => array(),
);
static::$persistent_blocks_cache = null;
Copy link
Member

Choose a reason for hiding this comment

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

If we don't backup the persistent cache in a static property of this class, then we can remove this as well.

@kt-12
Copy link
Member Author

kt-12 commented Jun 14, 2024

@joemcgill, the ticket's scope is still within what we initially decided. I am only persistently caching get_theme_data() and get_core_data(). Performance benefit from the core was less than 1%.

My efforts were directed towards maintaining the integrity of wp_theme_json_data_default and wp_theme_json_data_theme. However, the majority of the regression occurred between the static return and the filter call, giving no room to include filters before the cache. It's important to note that values are statically cached only after the filter, ensuring they reflect any changes from the filter. Persistent cache should not pose a problem if wp_theme_json_data_default and wp_theme_json_data_theme are used as pure filters and not as actions.

One workaround was to call wp_theme_json_data_default and wp_theme_json_data_theme like actions when we initialize from a persistent cache. This way, both hooks will be available for people who use them as actions. The filter version will be available when the cache expires.

@kt-12 kt-12 changed the title Caching inside get_theme_data Caching inside get_theme_data and get_core_data Jun 14, 2024
@kt-12
Copy link
Member Author

kt-12 commented Jun 18, 2024

@joemcgill, I have updated this PR with your feedback (I'll shift it to GB shortly). I saw I accidentally pushed 'block' origin cache that I used to get metrics. I have fixed that now.
Now, caching is only added for theme_data and core_data.
In the new update,

  • I created a cache just before the filter, so I was able to add the filter even in the cached version.
  • Removed all the additional static variables.
    I have kept a common expiry of 10 minutes. My main aim was to have a single database call for all the cache rather than multiple database calls with different expiries.
  • Another point is we can't have no expiration for has_same_registered_blocks as it only takes new blocks into consideration. Since we are updating $block_cache itself, we are at the moment caching blocks from all origin. This means if a block that is present is removed at some point and then added back (very rare ) chances are we won't record that change.

@kt-12
Copy link
Member Author

kt-12 commented Jun 24, 2024

@joemcgill GB issue for this task - WordPress/gutenberg#62794

@sstopfer
Copy link

sstopfer commented Jul 8, 2024

@joemcgill Putting this on hold.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: On Hold ⛔

Development

Successfully merging this pull request may close these issues.

3 participants