Make WordPress Core

Opened 13 months ago

Last modified 4 weeks ago

#64066 accepted enhancement

Speculative Loading: Change default eagerness from conservative to moderate when caching is detected

Reported by: westonruter Owned by: westonruter
Priority: normal Milestone: Future Release
Component: General Version: 6.8
Severity: normal Keywords: 2nd-opinion has-patch has-unit-tests
Cc: Focuses: performance, sustainability

Description (last modified by westonruter)

This is a follow up to #62503 which introduced Speculative Loading (the Speculation Rules API) to core.

As described in the ​Speculative Loading in 6.8 dev note:

This default of prefetch with conservative eagerness is used as a reasonable starting point to enable speculative loading at the scale of WordPress. It is in line with the configuration that Cloudflare uses in its speculative loading feature, and it minimizes the chance of any speculative loads without a subsequent navigation to the URL.

The default prefetch and mode in core are set to auto:

For both configuration parameters, the value auto signifies that WordPress Core will decide on the configuration, which as of today effectively leads to a mode of prefetch and an eagerness of conservative. Depending on various criteria such as the state of the Speculation Rules API and ecosystem support, the behavior may change in a future WordPress release.

In the Speculation Rules API, the eagerness of conservative means that the browser will only start to prefetch the URL when the user starts to click/tap on a link (i.e. mousedown/pointerdown). This gives the navigation about a 50 millisecond head start versus waiting for the click event to finish. With the eagerness of moderate, however, the browser can start to prefetch the URL after the user has hovered over a link for 200 ms on desktop, or (newly) on mobile when a link is visible in the viewport along with some additional heuristics. See Chrome's ​Speculation rules eagerness improvements. I did a ​comparison on the impact that the different eagerness levels have on a WordPress site with a normal TTFB (for a WP site) of 1 second: While conservative shaves off 50 ms from that 1-second TTFB, an eagerness of moderate can reduce the perceived TTFB to zero milliseconds.

Nevertheless, a key reason for being conservative is to minimize the chance of unused speculative loads. This is to guard against the increased server load, while also being a sustainability concern. On many shared hosts, the additional traffic incurred by moderate may overtax their limited CPU resources. Nevertheless, this is also an issue when such a site sees an influx in visitors. In order to deal with such traffic spikes, the go-to solution is to use a ​page caching solution. In fact, WordPress 6.1 introduced a Site Health test via #56041 which specifically checks for the presence of a page cache.

As an additional safeguard, moderate eagerness can be contingent based on whether a persistent object cache is present, that is, whether wp_using_ext_object_cache() returns true. Note that a Site Health test for persistent object cache was introduced in #56040; it could be updated to note that enabling persistent object cache can increase the eagerness in speculative loading, which can also be noted in the Site Health test for page caching.

As noted in the dev note that the default eagerness “may change in a future WordPress release”, I'm proposing that the Site Health test for page cache could be leveraged as a signal to change the default eagerness from conservative to moderate.

Change History (35)

This ticket was mentioned in ​PR #10123 on ​WordPress/wordpress-develop by ​@westonruter.


13 months ago
#1

  • Keywords has-patch added

#2 @westonruter
13 months ago

  • Description modified (diff)
  • Summary Speculative Loading: Change default eagerness from conservative to moderate when page cache is detected → Speculative Loading: Change default eagerness from conservative to moderate when caching is detected

​@westonruter commented on ​PR #10123:


13 months ago
#3

Site Health tests for page cache and persistent object cache have been updated to notify that moderate becomes the default when both are present:

https://github.com/user-attachments/assets/13cee95a-88ef-4d2e-9810-7bf62fcc7160

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


12 months ago

#5 @westonruter
12 months ago

  • Milestone Awaiting Review → 6.9

As discussed during our Core Performance chat today: milestoning for 6.9 for visibility, but I realize this may likely need to get punted to Future Release to allow for additional considerations.

#6 @westonruter
12 months ago

  • Keywords needs-unit-tests added

#7 @westonruter
12 months ago

Here's some plugin code that a host could, for example, drop into an mu-plugin to enable moderate eagerness by default unless a plugin (e.g. ​Speculative Loading) had set it to be something else:

<?php
add_filter(
        'wp_speculation_rules_configuration',
        /**
         * @param array<string, string>|null|mixed $config Config.
         * @return array<string, string>|null Filtered config.
         */
        static function ( $config ): ?array {
                // A plugin turned off Speculative Loading.
                if ( null === $config ) {
                        return null;
                }

                // In case a plugin returned a bad value to a filter.
                if ( ! is_array( $config ) ) {
                        $config = array();
                }

                // See <https://github.com/WordPress/wordpress-develop/blob/e1008a31dfcf6e9c395fa7f744139d11b2c1d9a7/src/wp-includes/speculative-loading.php#L21-L24>.
                $config = array_merge(
                        array(
                                'mode'      => 'auto',
                                'eagerness' => 'auto',
                        ),
                        $config
                );

                // Use a default eagerness of moderate if auto remains after all filters applied.
                if ( 'auto' === $config['eagerness'] ) {
                        $config['eagerness'] = 'moderate';
                }

                return $config;
        },
        PHP_INT_MAX // Allow other plugins (e.g. Speculative Loading) to easily override.
);

#8 @westonruter
12 months ago

  • Milestone 6.9 → Future Release

Looks like this won't make it for 6.9, but let's discuss.

#9 @westonruter
11 months ago

  • Milestone Future Release → 7.0

Milestoning for visibility.

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


10 months ago

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


10 months ago

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


9 months ago

This ticket was mentioned in ​Slack in #hosting by westonruter. ​View the logs.


9 months ago

This ticket was mentioned in ​Slack in #hosting by crixu. ​View the logs.


9 months ago

This ticket was mentioned in ​Slack in #hosting by amykamala. ​View the logs.


9 months ago

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


9 months ago

#17 @westonruter
9 months ago

See ​discussion in core dev chat.

This was also discussed in the bugscrub today.

The feedback I got from @jorbin and @desrosj was:

  1. There needs to be clear communication to hosting providers and documentation in the field guide.
  2. Data needs to be gathered on what the expected impact there will be on LCP as well as on the increase in page loads (potentially for wasted speculations).

A couple questions for @gilbertococchi (which I also left in #core-performance Slack):

  1. I know you have data on LCP passing rates for sites with conservative prefetch versus moderate prefetch. What is the latest relative improvement you've seen? Granted, it would be better to look at a set of sites all on conservative and then only switch to moderate after a month to see what their respective passing rates are. (See ​Slack thread for replies.)
  2. Also, is there any Chrome data for what is expected for the rate of wasted/unused speculations? The only way I'm aware of to measure this otherwise would be to look at a site that has very stable monthly traffic, and then flip from conservative to moderate after a month, and then to obtain the difference in the server hits in the access logs. (Although this also may not be reliable, if there is a random bot slamming a site one month but not the next.) (See ​Slack thread for replies.)
Last edited 9 months ago by westonruter (previous) (diff)

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


9 months ago

#19 @westonruter
8 months ago

  • Keywords early added
  • Milestone 7.0 → Future Release

Punting since we're too close to beta1 to land this. We need data to justify. So let's work on gathering that data for the next major release, starting in April.

#20 @westonruter
8 months ago

  • Owner set to westonruter
  • Status new → accepted

This ticket was mentioned in ​PR #11501 on ​WordPress/wordpress-develop by ​@pratiknawkar94.


6 months ago
#21

  • Keywords has-unit-tests added; needs-unit-tests removed
  • In production, when a persistent object cache is in use and Site Health has detected page caching (via advanced-cache.php / WP_CACHE or caching response headers), wp_get_speculation_rules_configuration() now resolves eagerness auto to moderate instead of conservative.
  • WP_Site_Health::check_for_page_caching() stores its raw detection result in the health_check_page_cache_detail transient (including a caching_response_headers list) so speculative loading can read it.
  • Page cache and persistent object cache Site Health descriptions include a short note linking to the Speculative Loading dev note and explaining the moderate vs conservative default.
  • PHPUnit coverage: wpGetSpeculationRulesConfiguration (production/staging/development, object cache, transient shapes) and wpSiteHealth (transient populated, descriptions mention speculative loading).

#22 @westonruter
5 months ago

Some key Shopify data that @gilbertococchi shared: ​Faster storefront navigations with moderate speculation rules

The desktop result is the one that stands out. Across the curve, the median gain is -285ms TTFB, -224ms FCP, and -228ms LCP. Median TTFB for a speculated navigation is now close to zero, which means the HTML response often arrives before the buyer even commits to clicking. For roughly 10% of speculated navigations, TTFB is exactly 0: the response is already sitting in the cache when the navigation fires.

Gains for mobile are less, as expected, due to the lack of a link hover heuristic.

As for the increase in pages served due unused speculations:

Prefetching earlier means the browser occasionally fetches a page the buyer never ends up visiting. At the same volume of actual page views, moderate triggers nearly 4x as many speculated requests as conservative on desktop, while on mobile the volume barely moves. In our case, that translated to about a 14% increase in the total number of HTML requests. Those numbers will depend on multiple factors: share of same-site navigations and browser mix, to name a few. The desktop-versus-mobile gap is also wide enough to raise a question: should the API let authors set a different eagerness per device type?

For us, the tradeoff is acceptable from both infrastructure and user perspectives. Still, your mileage may vary.

This ticket was mentioned in ​Slack in #hosting by crixu. ​View the logs.


5 months ago

#24 @westonruter
5 months ago

Note: #65232 proposes caching all Site Health results in a transient instead of just the aggregate summary. This could be leveraged by this PR instead introducing a new health_check_page_cache_detail transient.

This ticket was mentioned in ​Slack in #core-performance by westonruter. ​View the logs.


4 months ago

#26 @westonruter
4 months ago

Perhaps the right first step for this is to provide a way for hosts to change what the ​default value that eagerness refers to. If a host is configured with object caching and page caching, then they could set an environment variable to opt-in to a moderate default instead of conservative.

With that opt-in in place, we can ask hosts to turn on that environment variable and provide feedback on the resulting TTFB & LCP metric on their hosted sites, as well as the measured increase in server load due to wasted speculations.

If the hosts report back positively that this is a net improvement, we can consider using the Site Health test results for object caching and page caching as the heuristic for whether to make the default moderate as well. A host would be able to use the environment variable then to opt-out of this behavior.

#27 @westonruter
4 months ago

  • Milestone Future Release → 7.1

This ticket was mentioned in ​PR #12308 on ​WordPress/wordpress-develop by ​@yaghoot.


4 months ago
#28

https://core.trac.wordpress.org/ticket/64066

Adds PHPUnit coverage for the dynamic conservative → moderate default eagerness promotion gated by persistent object cache and a detected page cache.

These tests are expected to fail against trunk until ​https://github.com/WordPress/wordpress-develop/pull/10123 is merged; they pass when the speculative-loading.php and class-wp-site-health.php changes from that PR are applied.

#29 @wildworks
3 months ago

  • Milestone 7.1 → 7.2

As this ticket has been inactive for a while, I will move the milestone to 7.2. To move this ticket forward, we first need to review ​https://github.com/WordPress/wordpress-develop/pull/10123, but it is still in draft status.

Please feel free to move it back if it can be completed by 7.1 beta 1.

#30 @westonruter
3 months ago

This has been blocked in part by #65232 as it provides a way for the Speculative Loading logic to detect at runtime whether page caching is enabled.

Nevertheless, the 14% increase in server hits due apparently to wasted speculations, which I mentioned above, would likely mean this is not safe to enable this by default or that hosts should be determining whether it is enabled (e.g. via an environment variable, as I also mentioned above).

But stepping back, I also commented above about taking an incremental approach where we provide a way for hosts to opt-in via environment variables or constants. So I'm putting a PR together for that now. I'll create a new ticket for that to milestone for 7.1.

#31 @westonruter
3 months ago

I've opened #65624 and milestoned it for 7.1.

#32 @westonruter
3 months ago

In 62752:

General: Allow configuration of speculative loading defaults via env variables and constants.

Introduce two overrides that let a site or hosting provider change the default speculative loading configuration that the auto value resolves to, without having to ship an mu-plugin:

  • WP_SPECULATIVE_LOADING_DEFAULT_MODE (prefetch or prerender)
  • WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS (conservative, moderate, or eager).

Each may be supplied as an environment variable, read via getenv(), or as a constant of the same name that takes precedence over it, mirroring how wp_get_environment_type() resolves WP_ENVIRONMENT_TYPE. An unrecognized value falls back to the core default.

These overrides only change what auto resolves to, so an explicit mode or eagerness supplied through the wp_speculation_rules_configuration filter still wins. An eagerness of immediate is rejected because WordPress does not permit it for the document-level rules it generates; accepting it would cause WP_Speculation_Rules::add_rule() to reject the rule and leave the page with no speculation rules at all.

Also relax WP_Speculation_Rules::is_valid_mode() and WP_Speculation_Rules::is_valid_eagerness() to accept mixed, so an arbitrary value from the filter is validated and rejected rather than raising a TypeError.

Fix PHPStan errors in speculative loading functions resulting from insufficient typing.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/12514.
Follow-up to r59837.

Props westonruter, mukesh27, adamsilverstein, swissspidy.
See #64066, #62503, #64896, #64898.
Fixes #65624.

This ticket was mentioned in ​Slack in #core by audrasjb. ​View the logs.


4 weeks ago

#34 @audrasjb
4 weeks ago

As per today's 7.2 bug scrub:

Reported and owned by @westonruter
The ticket was punted from 6.9 to 7.2.
Weston, if I understand correctly your last comments and the commit referencing the ticket, this one is on standby for now, but other tickets are opened to deal with related tasks.
Should we move it to Future Release of do you prefer to keep it in the milestone, even if it ends up punted early October?

#35 @westonruter
4 weeks ago

  • Keywords early removed
  • Milestone 7.2 → Future Release

Punting this for future until we have data from hosts enabled by #65624.

Note: See TracTickets for help on using tickets.