Problem/Motivation

Inspired by @berdir in #3340999: The path-alias preloading is not used if the "Enforce clean and canonical URLs" option is enabled.

We currently have a path preload cache, added by me and others in #456824: drupal_lookup_path() speedup - cache system paths per page.. It records all the system paths found on a page when the cache is cold, writes them to a per-URL cache object, then when that page is requested again, if a path alias is requested, uses that cache item to preload all the path aliases.

It saves a lot of queries but is less effective in 11.x than it was in 7.x because we now have render caching and dynamic_page_cache, meaning the amount of times we're generating the same path aliases on the same page will be lower (if still possibly more than once every 24 hours). Also it takes up quite a lot of cache space.

In #3340999: The path-alias preloading is not used if the "Enforce clean and canonical URLs" option is enabled @berdir suggested looking at potentially removing it - this would be fine on sites with good render cache hit rates, but sites with lots of content changes (comment posting on the same URL, node list cache tag constantly invalidated) might still do a fair bit of extra queries.

Steps to reproduce

Proposed resolution

When we look up an alias, just before we get to the point where we need to look it up from the database, we can check if we're inside a Fiber and if so suspend. Before doing so, we add the system path to a class property.

Alias lookups happen via Url::toString(), which is called when URLs are printed in twig templates, this means that we need twig rendering to happen inside a Fiber.

This relies on #3437499: Use placeholdering for more blocks which puts more blocks into placeholders and related issues to use placeholdering more widely. This is necessary to have more rendering happen within a single loop we control (e.g. placeholder rendering), although the technique also works when rendering views rows within fibers (see below).

This enables a code flow like the following:

Fiber 1 - hits a path alias lookup, adds to list, suspends.

Fiber 2 - hits a path alias lookup, adds to list, suspends.

Fiber 3 - hits a path alias lookup, adds to list, suspends.

Fiber 1 - resumes, loads all three path aliases, adds them to the static cache.
Fiber 2 - gets the alias from the static cache
Fiber 3 - gets the alias from the static cache.

This doesn't rely on anything async, it's more that we can force parallel execution of path lookups instead of serial, by backing out of one, starting another, backing out etc. then when we get back to the first one, load everything we need into the static cache and save the individual lookups.

This sounds complicated, and conceptually it maybe is, but the actual code is very simple and isolated to AliasManager, it should be an improvement for all requests (cold and hot caches) because we remove a high cardinality/low hit rate cache item and also reduce the number of database queries on cache misses - potentially by dozens on some pages once we start to optimize for this a bit more.

This allows us to completely remove the path alias preload cache (net reduction of ~500 lines of code) while improving cold cache performance.

Remaining tasks

Was previously blocked on #2511330: Deprecate RendererInterface::render()'s sole $is_root_call parameter and #3518179: Renderer::executeInRenderContext() needs to handle nested calls and suspended fibers.

Independent of this issue, but #3518662: Use a lazy builder/placeholder for the top bar and #3518668: Use Fibers for rendering views rows both significantly add to the performance improvements from this issue by allowing more path aliases to be pre/bulk loaded.

User interface changes

Introduced terminology

API changes

The method ::setCacheKey() on AliasManager becomes a no-op and is deprecated for removal in 12.x. This was only used by modules that manipulate path aliases like workspaces (see the changes in there). Otherwise all the old and new logic is internal.

Data model changes

Release notes snippet

Issue fork drupal-3496369

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catch created an issue. See original summary.

catch’s picture

Issue summary: View changes
catch’s picture

Instead of doing this in the url generator we can probably do it in AliasManager::getAliasByPath() instead.

Add the system path to a class property ready for preloading, Fiber::suspend(), when we get resumed, look for any paths to multiple/preload that haven't already been, multiple load, continue.

The main question is how many times can we ::suspend() before we resume, but we only need to pick up two path aliases each time to reduce the number of queries by 50% so the odds might be decent. The existing solution has built in staleness where subsequent page requests will individually lookup paths that weren't found when the preload cache was set.

catch’s picture

Put up a proof of concept here but it doesn't do anything useful on standard yet - almost nothing is rendered in a placeholder and that's the only inside-fiber rendering we do.

So this will need #3437499: Use placeholdering for more blocks - at least a rebase of the MR there and some combined testing, in order to see whether it can be viable.

I did add some views blocks (recent comments, recent content) to the footer along with adding some alias for things like comment/reply, and saw this working (i.e. it would multiple four aliases) - so in theory it has potential just not in practice yet.

catch’s picture

Title: Try to replace path alias preloading with lazy generation » Try to replace the path alias preload cache with Fiber trickery
catch’s picture

Title: Try to replace the path alias preload cache with Fiber trickery » Try to multiple load path aliases without the preload cache

Another attempt at a better issue title.

catch’s picture

Status: Active » Needs work
StatusFileSize
new313.74 KB
new444.27 KB

OK this actually works.

Manual testing with https://git.drupalcode.org/issue/drupal-3496369/-/tree/3496369-with-rend...

HEAD:
xhprof after screenshot

MR:
xhprof before screenshot

With a completely cold cache on an Umami recipe page, there are 38 calls to Drupal\path_alias\AliasRepository::lookupBySystemPath + the cache get and set for the preload cache.

With the MR, there are 31 calls to Drupal\path_alias\AliasRepository::preloadPathAlias and no cache get/set.

With a warm render cache, there will be zero path alias gets at all because the resultant URLs are in the render cache anyway.

So the worst case scenario is improved, and the best case scenario is the same.

When the path alias cache is warm but the render cache is not (multiple roles viewing the same path for example), then there will probably be more database queries - but what's in the cache will depend on which role visits the page first, so it's not like preloading gets the queries down to one, there's always some stragglers.

catch’s picture

StatusFileSize
new450.01 KB

Then I added #3437499: Use placeholdering for more blocks locally and got down to 14 calls to preloadPathAlias() - this is nearly a 2/3rds reduction in path lookup queries on a cold cache. Because the cache is per-URL it will be a similar reduction across all the first visits to a URL on a site after a cache clear or each time the cache expires if the render cache for that page isn't warm."

xhprof screenshot

catch’s picture

catch’s picture

Issue summary: View changes
catch’s picture

This issue + #3437499: Use placeholdering for more blocks seems to be enough without #3496835: [PP-2] Render children in fibers, which is good because changing the renderer like that is a bit alarming.

However when testing I ran into the exception that #2511330: Deprecate RendererInterface::render()'s sole $is_root_call parameter is trying to remove, so we will need that issue too to make this all work together.

The actual MR here still has a couple of test failures in path alias tests, but it'll be reviewable by itself without the other two issues - just won't be a performance improvement (or a very marginal one) until those also land.

Not exactly sure what test coverage looks like for this - we can probably set up some Fibers in a kernel test that call getAliasByPath() and check that the aliases are still returned correctly.

#3476421: Add assertions to OpenTelemetryNodePagePerformanceTest::testNodePageCoolCache() would hopefully cover performance testing once it's in, alongside the other two issues.

catch changed the visibility of the branch 3496369-with-renderer-support to hidden.

catch’s picture

Title: Try to multiple load path aliases without the preload cache » [PP-1] Try to multiple load path aliases without the preload cache
Status: Needs work » Postponed
Related issues:

However when testing I ran into the exception that #2511330: Deprecate RendererInterface::render()'s sole $is_root_call parameter is trying to remove, so we will need that issue too to make this all work together.

Not entirely proven yet, but I think this might be ::executeInRenderContext() which relies on a (static) stack to track cacheability metadata from code that returns strings, some discussion in #3507959: Refactor the render context stack to avoid a static property.

I think we might need to do something like use a fiber in executeInRenderContext() so that we can 'trap' any fibers underneath - e.g. if they suspend, resume them again, so that any metadata doesn't leak into the stack.

Explicitly postponing this on #3437499: Use placeholdering for more blocks.

catch’s picture

Title: [PP-1] Try to multiple load path aliases without the preload cache » Try to multiple load path aliases without the preload cache
Status: Postponed » Needs work

berdir made their first commit to this issue’s fork.

berdir’s picture

Worked a bit on this to clean up the unit tests so we can see the other tests now that they are blocking. I adjusted some and remove several that were testing the interaction with the cached preload routes.

Two performance tests then failed, one with the is root exception, the other changed how the query looked but otherwise pretty minimal impact. It did drop one query though, not exactly sure what happened to that.

Missed that we we need [#3476421: Add assertions to OpenTelemetryNodePagePerformanceTest::testNodePageCoolCache()] for the scenario where this matters.

Note that #3408713: Add database and cache assertions to OpenTelemetryFrontPagePerformanceTest and OpenTelemetryNodePagePerformanceTest is kind of a duplicate of that one, should clean up those issues a bit to avoid duplicates.

catch’s picture

The is root exception is going to be a hard blocker here (and potentially for async queries too since I think it will hit the same thing).

catch’s picture

Title: Try to multiple load path aliases without the preload cache » [PP-1] Try to multiple load path aliases without the preload cache

Pretty sure this (and potentially any other fiber suspension within placeholder rendering) is blocked on #2511330: Deprecate RendererInterface::render()'s sole $is_root_call parameter.

catch’s picture

If you combine the changes here with #2511330: Deprecate RendererInterface::render()'s sole $is_root_call parameter you get a new error:

AssertionError: Bubbling failed. in assert() (line 634 of /var/www/html/core/lib/Drupal/Core/Render/Renderer.php) #0                    
                                         /var/www/html/core/lib/Drupal/Core/Render/Renderer.php(634):

But that gave me an idea on how to workaround it, pushing a new branch to see if it helps with the test failures.

catch’s picture

Title: [PP-1] Try to multiple load path aliases without the preload cache » [PP-2] Try to multiple load path aliases without the preload cache
Status: Needs work » Postponed

MR up on #3518179: Renderer::executeInRenderContext() needs to handle nested calls and suspended fibers now. That makes this PP-2, but pretty sure those two issues are the only hard blockers and we mostly need to adjust test expectations here once they're in. Per #18 #3408713: Add database and cache assertions to OpenTelemetryFrontPagePerformanceTest and OpenTelemetryNodePagePerformanceTest is necessary to see the improvement, although the existence of the test means we can run a combined MR to see the difference even if it's not actually committed yet.

catch’s picture

With #3408713: Add database and cache assertions to OpenTelemetryFrontPagePerformanceTest and OpenTelemetryNodePagePerformanceTest there is hardly any visible improvement or regression.

However manually testing with umami + the navigation module enabled, and then truncating cache_render, I do see a couple of path aliases loaded together (but several loaded individually).

If we were to render individual entities in views listings in placeholders (which we theoretically could), I think we'd see a significant saving though.

catch’s picture

Title: [PP-2] Try to multiple load path aliases without the preload cache » [PP-2] Multiple load path aliases without the preload cache

Removing 'try to' from the issue title because this definitely works.

Right now, with the three issues, it works, but only a couple of path aliases get multiple loaded.

To make it more effective, the following two issues would help a lot:

#3518662: Use a lazy builder/placeholder for the top bar
#3518668: Use Fibers for rendering views rows

The reason those should make a big difference is because the way this works is by picking up the first link it finds when rendering a placeholder, suspending the Fiber, and then doing that for each available placeholder. When it reaches the first placeholder again (e.g. the Fiber is resumed), then it will preload any path aliases picked up from the first iteration through the placeholders and load them at once. #3437499: Use placeholdering for more blocks did similar for several blocks already, but a lot of blocks don't necessarily have unique path aliases to look up.

So if you have five placeholders (at the same level of nesting), and each placeholder has one alias to load, you get five aliases loaded in one loookup.

But if you have a single placeholder with 50 URLs, then it will only multiple load the first one or two, then the rest individually, because it only has one placeholder with that many links in it.

For e.g. Umami views showing lists of recipes or taxonomy terms, if each of those is a placeholder, then we're looking at 10, 20, 50 path alias lookups at once instead of individually, and crucially, this will work on both render cache hits and misses. Also allows us to remove a high-cardinality, low-hit rate cache item at the same time. The entity/navigation placeholder issues make sense in their own right and could land either before or after this one, but it's another case where an improvement in one issue amplifies improvements in another.

catch’s picture

Cross-linking the original issue that added the system lookup cache for multiple loading of path aliases back in 2009 #456824: drupal_lookup_path() speedup - cache system paths per page.. The code has moved around a lot but the logic hasn't really changed, pretty nice to be in a position to remove it with something better, umm, 16 years later.

catch’s picture

Issue summary: View changes
catch’s picture

In combination with the two blocking issues, and #3518668: Use Fibers for rendering views rows, and also #3408713: Add database and cache assertions to OpenTelemetryFrontPagePerformanceTest and OpenTelemetryNodePagePerformanceTest, it's possible to see a real reduction in queries on cold caches now - about 15 less on the Umami front page and node pages. Combined MR is https://git.drupalcode.org/project/drupal/-/merge_requests/11784

    Open Telemetry Front Page Performance (Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformance)
     ✘ Front page performance
├ Failed asserting that two arrays are identical.
       ┊ ---·Expected
       ┊ +++·Actual
       ┊ @@ @@
       ┊  Array &0 [
       ┊ -····'QueryCount'·=>·376,
       ┊ +····'QueryCount'·=>·361,
 Open Telemetry Node Page Performance (Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformance)
     ✘ Node page
     ┐
       ├ Failed asserting that two arrays are identical.
       ┊ ---·Expected
       ┊ +++·Actual
       ┊ @@ @@
       ┊  Array &0 [
       ┊ -····'QueryCount'·=>·458,
       ┊ -····'CacheSetCount'·=>·441,
       ┊ +····'QueryCount'·=>·443,
       ┊ +····'CacheSetCount'·=>·440,

catch’s picture

catch’s picture

Priority: Normal » Major
catch’s picture

Title: [PP-2] Multiple load path aliases without the preload cache » [PP-1] Multiple load path aliases without the preload cache

catch changed the visibility of the branch 3496369-with-more-placeholdering to hidden.

catch’s picture

Combined MR with #3518179: Renderer::executeInRenderContext() needs to handle nested calls and suspended fibers is green now and removes about 500 lines of code.

To see a larger reduction in the number of database queries on cold caches, we need #3518662: Use a lazy builder/placeholder for the top bar and #3518668: Use Fibers for rendering views rows - but those can happen independently of this issue.

catch’s picture

Title: [PP-1] Multiple load path aliases without the preload cache » Multiple load path aliases without the preload cache
Status: Postponed » Needs work

Will need a rebase here now the blocker is in.

The current MR only shows a very small reduction in queries in performance tests, this is because the specific URL aliases being rendered for anonymous users in those tests aren't in a good position to be preloaded.

However I opened an extra MR combined with #3518668: Use Fibers for rendering views rows (https://git.drupalcode.org/project/drupal/-/merge_requests/12470) which starts to show the potential on cold caches:


    $expected = [
-      'QueryCount' => 381,
 +     'QueryCount' => 366,
      'CacheGetCount' => 471,
  -    'CacheSetCount' => 467,
  +    'CacheSetCount' => 466,

The difference there is that all the URL aliases from all the views rows can be preloaded, so the Umami block listing taxonomy terms is able to get all the aliases for all the taxonomy term URLs at once instead of individually.

catch changed the visibility of the branch 3496369-plus-3518179 to hidden.

oily changed the visibility of the branch 3496369-preload-without-cache to hidden.

catch’s picture

Status: Needs work » Needs review

This should be ready for review now.

The draft MR at https://git.drupalcode.org/project/drupal/-/merge_requests/12470 is there to show the preloading working a bit better with one additional core issue applied, but shouldn't actually be reviewed.

catch changed the visibility of the branch 3496369-preload-without-cache to active.

catch’s picture

Issue summary: View changes
Issue tags: +fibers

Updated the issue summary.

catch’s picture

Issue summary: View changes
needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

oily’s picture

Fix merge conflict.

joachim’s picture

> This doesn't rely on anything async, it's more that we can force parallel execution of path lookups instead of serial, by backing out of one, starting another, backing out etc. then when we get back to the first one, load everything we need into the static cache and save the individual lookups.

This is ingenious, but what if there were another place in code where we wanted to do the same sort of trick? Is it the case that once we're doing this technique here, we can't do it anywhere else?

catch’s picture

This is ingenious, but what if there were another place in code where we wanted to do the same sort of trick? Is it the case that once we're doing this technique here, we can't do it anywhere else?

The answer here is... yes and no.

I've got #1237636: Lazy load multiple entities at a time using fibers open which works almost exactly the same as this, except for the fact that it breaks something horribly in placeholder rendering so does not actually work yet.

If we take the two together, you can see how it might or might not work, it will all depend on exactly what is going on with particular pages.

If you have multiple blocks that load one entity, then render a link to that entity, the process will look something like this:

Block 1 - entity 1 'preload' and suspend
Block 2 - entity 2 'preload' and suspend
Block 3 - entity 3 'preload' and suspend

Block 1 - loop returns and entity 1-3 are loaded - path alias 1 preload and suspend
Block 2  - path alias 2 preload and suspend
Block 3  - path alias 3 preload and suspend

Block 1 - loop returns, path aliases 1-3 preloaded, rendering finishes
Block 2 - rendering finishes
Block 3 - rendering finishes

So in the first scenario, three path aliases are loaded in one round trip, and three entities are loaded in one round trip, whereas prior to this change they'd each have been loaded one by one.

Overall 1/3rd the round trips.

But it could also look like this, in this case block 2 renders one path alias but doesn't load an entity either before or after.

Block 1 - entity 1 'preload' and suspend
Block 2 - path alias 1 'preload' and suspend
Block 3 - entity 3 'preload' and suspend

Block 1 - loop returns and entity 1 and 3 are loaded - path alias 2 preload and suspend
Block 2 - path alias 2 and 1 are loaded 
Block 3 - path alias 3 'preload' and suspend

Block 1 - rendering finishes
Block 2 - rendering finishes
Block 3 - path alias 3 loaded and rendering finishes

In this case, two entities are loaded in one round trip, three path aliases are loaded in two round trips - 3/5ths the round trips.

So because the paths and entities are out of sequence, it's not able to reduce the round trips as much - but the important thing is that it's still reducing them a bit, and the reduction isn't worse than if we only did path aliases or only did entities.

Also, if a single block/placeholder has multiple links in it, this approach only works when other blocks have the same number of links.

So if three blocks have one link each, three links will be loaded in one round trip. But if one block has three links, it will require three round trips - because the first link has to be loaded before the second.

There are situations where we have structures that are usually within a block, which predictably deal with multiple of the same thing, so we can make it work for those cases too:

#3518668: Use Fibers for rendering views rows would render views rows in fibers (even loop once we have that) and in general views rows are rendering exactly the same type of thing for each row (card view mode with an image and link, or a table row with links etc.)

Local tasks and similar places too where it's always a list of links. If we event-loop those, then we'll get a higher hit rate most of the time.

But also if two blocks have two links each, then those can be loaded in two round trips without any special handling withing the blocks (it works as long as the loop finds the aliases at the same point while it's progressively going through the blocks in the loop).

Overall, it does have limitations - we're never going to be able to load every single path alias on a page in a single round trip using this method, it will load 'batches' of path aliases that are rendered in similar code paths. But also the technique has almost zero overhead (apart from conceptually being quite complex, but the code itself is simple and lightweight), and the worst case is that it doesn't save much in particular scenarios.

catch’s picture

Status: Needs work » Needs review

This has been rebased since the bot marked it needs work.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

darvanen’s picture

As requested giving this one a review.

I'm new to Threads but the loop makes sense to me from the examples given. If I have understood it correctly then the answer to my question on the MR should be 'none'. If that's the case then I'd say the logic here is solid (and we can remove a couple of unset commands).

My only other concern is I don't think we're testing the non-thread use-case? I know they were introduced in 8.1 and 8.0 is out of support, but what if someone is running a NTS binary or something?

berdir’s picture

> So if three blocks have one link each, three links will be loaded in one round trip. But if one block has three links, it will require three round trips - because the first link has to be loaded before the second.

For routes, we have \Drupal\Core\Routing\PreloadableRouteProviderInterface::preLoadRoutes(), which is used by the global preloader that I'm trying to change, but we also call it in \Drupal\Core\Routing\RouteProvider::getRoutesByNames(), which used for example by \Drupal\Core\Menu\MenuLinkTree::load to preload all routes for a given menu.

Maybe as a follow-up, we can think of something similar for specific cases where we know that we have multiple links/url objects, we can explicitly request those to be preloaded? We need to make some assumptions and path aliases did move into a technically optional module now but maybe we can think of something, could go through the UrlGenerator and an event?

So just like my route preloader issue, we still have the concept of preloading, but we move from global preloading to per-block/element preloading, which fits better with partial/render caching.

catch’s picture

#3518668: Use Fibers for rendering views rows is one such case where we can assume it's likely that multiple links/urls are involved. However we don't easily have a way to explicitly request preloading there or not that I can think of.

Another one which doesn't have an issue yet is local tasks/actions.

Those would definitely pick a lot more paths up and be less dependent on what's going on in the rest of the page.

catch changed the visibility of the branch with-3518668 to hidden.

catch changed the visibility of the branch 3496369-preload-without-cache to hidden.

catch’s picture

There was a merge commit in the branch history which made things impossible to rebase. I started a new branch, reverted to the last known good commit, then rebased from there - have hidden the other branches for now.

catch’s picture

Status: Needs work » Needs review

Back to needs review. https://git.drupalcode.org/project/drupal/-/merge_requests/13252 is the one to review now.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review

Bot is understandably confused by the multiple branches. Closed all MRs except the active one.

catch’s picture

Added an administrator performance test to Umami because I realised just being logged in as an admin is enough to show this working - it shaves 7 database queries off hitting a node page when all caches are empty. Pretty sure this is because contextual links are rendered via big pipe, and therefore happen inside fibers.

catch’s picture

Test only job should hopefully show the diff https://git.drupalcode.org/issue/drupal-3496369/-/jobs/6679091

oily’s picture

Test-only output is:

PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
...                                                                 3 / 3 (100%)
Time: 00:15.570, Memory: 8.00 MB
OK (3 tests, 101 assertions)
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
....F.FFF                                                           9 / 9 (100%)
Time: 00:00.045, Memory: 8.00 MB
There were 4 failures:
1) Drupal\Tests\path_alias\Unit\AliasManagerTest::testGetAliasByPathNoMatch
Expectation failed for method name is "preloadPathAlias" when invoked 1 time.
Method was expected to be called 1 time, actually called 0 times.
2) Drupal\Tests\path_alias\Unit\AliasManagerTest::testGetAliasByPathMatch
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'zmy5ftyv'
+'/btvca5ai/z1vdkxvv'
/builds/issue/drupal-3496369/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php:240
3) Drupal\Tests\path_alias\Unit\AliasManagerTest::testGetAliasByPathCachedMissLanguage
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'ps3dog2l'
+'/lxty5dwo/pupjuu3x'
/builds/issue/drupal-3496369/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php:272
4) Drupal\Tests\path_alias\Unit\AliasManagerTest::testCacheClear
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'/alias'
+'/path'
/builds/issue/drupal-3496369/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php:295
FAILURES!
Tests: 9, Assertions: 39, Failures: 4.
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
...                                                                 3 / 3 (100%)
Time: 00:21.185, Memory: 8.00 MB
OK (3 tests, 119 assertions)
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
.F                                                                  2 / 2 (100%)
Time: 01:41.178, Memory: 10.00 MB
There was 1 failure:
1) Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryAuthenticatedPerformanceTest::testNodePageAdministrator
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
 Array &0 [
-    'QueryCount' => 542,
-    'CacheGetCount' => 565,
+    'QueryCount' => 549,
+    'CacheGetCount' => 566,
     'CacheGetCountByBin' => Array &1 [
         'config' => 221,
         'bootstrap' => 23,
         'discovery' => 113,
-        'data' => 71,
+        'data' => 72,
         'dynamic_page_cache' => 2,
         'default' => 45,
         'entity' => 23,
@@ @@
         'render' => 39,
         'menu' => 28,
     ],
-    'CacheSetCount' => 474,
+    'CacheSetCount' => 475,
     'CacheDeleteCount' => 0,
     'CacheTagInvalidationCount' => 0,
     'CacheTagLookupQueryCount' => 47,
/builds/issue/drupal-3496369/core/tests/Drupal/Tests/PerformanceTestTrait.php:689
/builds/issue/drupal-3496369/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php:121
FAILURES!
Tests: 2, Assertions: 26, Failures: 1.
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
F                                                                   1 / 1 (100%)
Time: 00:48.511, Memory: 10.00 MB
There was 1 failure:
1) Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryFrontPagePerformanceTest::testFrontPagePerformance
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
 Array &0 [
-    'QueryCount' => 380,
-    'CacheGetCount' => 471,
-    'CacheSetCount' => 466,
+    'QueryCount' => 381,
+    'CacheGetCount' => 472,
+    'CacheSetCount' => 467,
     'CacheDeleteCount' => 0,
     'CacheTagLookupQueryCount' => 49,
     'CacheTagInvalidationCount' => 0,
/builds/issue/drupal-3496369/core/tests/Drupal/Tests/PerformanceTestTrait.php:689
/builds/issue/drupal-3496369/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php:66
/builds/issue/drupal-3496369/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php:31
FAILURES!
Tests: 1, Assertions: 10, Failures: 1.
HTML output directory sites/simpletest/browser_output is not a writable directory.
PHPUnit 11.5.39 by Sebastian Bergmann and contributors.
Runtime:       PHP 8.4.13
Configuration: /builds/issue/drupal-3496369/core/phpunit.xml.dist
F                                                                   1 / 1 (100%)
Time: 00:36.928, Memory: 10.00 MB
There was 1 failure:
1) Drupal\Tests\demo_umami\FunctionalJavascript\OpenTelemetryNodePagePerformanceTest::testNodePage
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
 Array &0 [
-    'QueryCount' => 457,
-    'CacheSetCount' => 440,
+    'QueryCount' => 458,
+    'CacheSetCount' => 441,
     'CacheDeleteCount' => 0,
     'CacheTagLookupQueryCount' => 43,
     'CacheTagInvalidationCount' => 0,
/builds/issue/drupal-3496369/core/tests/Drupal/Tests/PerformanceTestTrait.php:689
/builds/issue/drupal-3496369/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:68
/builds/issue/drupal-3496369/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php:32
FAILURES!
Tests: 1, Assertions: 10, Failures: 1.
Exiting with EXIT_CODE=1

I do not fully understand it but it looks good to me.

catch’s picture

The important bit from those test failures is this:

-    'QueryCount' => 542,
-    'CacheGetCount' => 565,
+    'QueryCount' => 549,
+    'CacheGetCount' => 566,

e.g. this allows us to multiple load 7 path aliases on cold cache (Umami node page) requests, that previously had to be requested individually.

It also allows us to remove the per-page path alias cache - it doesn't work when it's cold, but also it doesn't do anything when render caches are warm because path aliases don't need to be resolved at all when the dynamic page cache / render cache can retrieve cached HTML.

This way we're getting an improvement on cold cache requests, no regression on warm caches, and by removing a per-page cache item we're also reducing overall cache storage requirements. There are some pages in the middle (mixture of warm and cold render caches) where it may result in more queries, but the current system doesn't perfectly deal with those anyway (e.g. if a non-admin warmed the path alias cache, an admin's path aliases have to be retrieved individually anyway) so even in those cases it could end up being neutral or an improvement.

berdir’s picture

Status: Needs review » Needs work

Fully agreed with #67, to elaborate on "but also it doesn't do anything when render caches are warm because path aliases don't need to be resolved at all". It's not just that it doesn't do anything, but also that if there is one to look up, it loaded a possibly large amount of aliases that aren't actually needed because those parts are still render-cached.

And, due to #3340999: The path-alias preloading is not used if the "Enforce clean and canonical URLs" option is enabled, the whole feature didn't do work at all on a vast amount of sites for a probably very long time.

Just some minor remarks on the MR, like the deprecated version, then this is RTBC for me.

oily’s picture

RE: #67 and #68 Thank you for the interesting insight into caching!

catch’s picture

Status: Needs work » Needs review

The two threads on the MR should be resolved now.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, I think this is ready now. This will conflict with #1237636: Lazy load multiple entities at a time using fibers and we'll need to rebase one of the issues once the first lands as they both change the same performance assertions.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

#1237636: Lazy load multiple entities at a time using fibers has landed so this needs work to resolve the conflicts.

catch’s picture

In rebasing, it looks like once this issue is added (e.g. more fiber suspensions) there's an additional undiscovered bug with #1237636: Lazy load multiple entities at a time using fibers although fortunately one that was (I think) introduced by last minute refactoring which made it easy to find and would explain why it didn't surface before when the two issues were combined.

I've committed a fix here and will see if it gets tests back to green, but we might need to do a follow-up or revert and recommit in that issue or something.

catch’s picture

Opened #3549380: Fiber entity multiple loading tweaks.

Cherry-picked the bugfix over here to try to get back to green in this issue.

catch’s picture

Title: Multiple load path aliases without the preload cache » [PP-1] Multiple load path aliases without the preload cache
Status: Needs work » Postponed

#3549380: Fiber entity multiple loading tweaks needs to land before this one now, so explicitly marking postponed.

alexpott’s picture

Title: [PP-1] Multiple load path aliases without the preload cache » Multiple load path aliases without the preload cache
Status: Postponed » Needs work

Unblocked...

catch’s picture

Status: Needs work » Reviewed & tested by the community

Rebased - only resolved merge conflicts / performance test changes since this was last RTBC, so I think I'm good to re-RTBC.

catch’s picture

Discussed the removal of ::writeCache() with @alexpott and he suggested leaving it as a no-op with a deprecation just in case someone somewhere is calling it.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 4ba82cd and pushed to 11.x. Thanks!

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

  • alexpott committed 4ba82cdc on 11.x
    Issue #3496369 by catch, berdir, oily, darvanen, alexpott: Multiple load...
catch’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.