Updated: Comment #0

Problem/Motivation

Cache handling is split between the AliasManager and AliasManagerCacheDecorator and the AliasManagerInterface and CacheDecoratorInterface.

The current caching has various bugs and as a result, does not work at all. Multiple languages are not properly supported, paths without aliases aren't cached. With the current API's, it is not possible to fix that.

Proposed resolution

Merge the two classes together, handle caching internally.

Remaining tasks

Needs reviews.

User interface changes

API changes

- AliasManagerCacheDecorator and the corresponding service is removed.

Comments

visabhishek’s picture

Assigned: Unassigned » visabhishek
visabhishek’s picture

Assigned: visabhishek » Unassigned
slashrsm’s picture

Title: Move all cache related logic from AliasManager to AliasManagerCacheDecorator » Merge AliasManagerCacheDecorator into AliasManager
Status: Active » Needs review
StatusFileSize
new19.04 KB

It turns out that AliasManager becomes just a simple wrapper around AliasStorage if we move all cache-related code out of it. It makes more sense to merge AliasManager and AliasManagerCacheDecorator.

dawehner’s picture

  1. +++ b/core/core.services.yml
    @@ -375,9 +375,6 @@ services:
    -  path.alias_manager.cached:
    -    class: Drupal\Core\CacheDecorator\AliasManagerCacheDecorator
    -    arguments: ['@path.alias_manager', '@cache.data']
    

    +1 as you can easily forget to use the cached version.

  2. +++ b/core/lib/Drupal/Core/Path/AliasManager.php
    @@ -80,11 +103,39 @@ class AliasManager implements AliasManagerInterface {
    +  public function writeCache() {
    

    It is a bit odd that writeCache is part of the public API but yeah this is not the fault of the patch itself.

  3. +++ b/core/lib/Drupal/Core/Path/AliasManager.php
    @@ -202,26 +268,19 @@ public function getPathLookups() {
    +   * @param $path
    

    If we change this line we can use @param string directly. In general this could be seen as somehow out of scope.

slashrsm’s picture

StatusFileSize
new19.05 KB
new466 bytes
  • It is a bit odd that writeCache is part of the public API but yeah this is not the fault of the patch itself.

    writeCache() is called from outside in this case (from \Drupal\Core\EventSubscriber\PathSubscriber).

  • In general this could be seen as somehow out of scope.

    It could, but as part of the parent issue we also try to standardize terminology. There was no specific ticket to do that, it was do as part of other tickets. I am more than happy to move this to a separate ticket if desired so.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

My comment sounded maybe a bit pessimistic, but I really like this refactoring.

berdir’s picture

Noticed during profiling that something is going wrong right now with alias caching and languages.

Looks like the AliasManager right now can only write back caches of a single language, the first in the lookup map?

In my case, I was importing nodes with langcode not specificed (und), they now explicitly pass along the language context. The result is that they remain uncached. I was displayed 100 of them in a view and I have 100 queries due to that right now.

And even after changing them to en, caching still doesn't seem to be working, because it never ends up in the cache... this seems to be because there are no aliases, so they're added to noAlias, which isn't added to the cache.

This might not be the correct issue, but with all the refactoring, we could possibly fix at least some of the issues here? What about caching the alias hits and misses separately in the cache, so that we don't have to attempt to load the alias for them? Why are we not caching the aliases btw, should be possible to do that by language?

That said, merging this seems to make a lot of sense and it can go even further, the cache write method still uses getPathLookups(), which is a public method on the interface, the only reason for that seems to be that the cache wrapper can do the caching? The method is very limited, because it is not possible to solve the problems above while still using it as it has no understanding of language and if an alias existed. So we can probably just remove that and use $this->lookupMap and $this->noAlias directly in writeCache() ?

slashrsm’s picture

Yah... one thing that is definitely broken ATM is this (wrong CID being used to load from cache - $path instead of $this->cacheKey). This should be fixed with this patch.

+++ /dev/null
@@ -1,128 +0,0 @@
-    // We need to pass on the list of previously cached system paths for this
-    // key to the alias manager for use in subsequent lookups.
-    $cached = $this->cache->get($path);
-    $cached_paths = array();
-    if ($cached) {
-      $cached_paths = $cached->data;
-      $this->cacheNeedsWriting = FALSE;
-    }
-    $this->preloadPathLookups($cached_paths);
-    return $path;
-  }
-
-  /**

I was asking myself two cache-related questions when working on this:
1. Why we cache only paths (as also mentioned by @berdir). It seems that we could cache entire path-alias mapping instead of preloading it fro DB.
2. Why are we preloading only in getAliasByPath()? Wouldn't also getPathByAlias() benefit from preloaded mappings? - I suspect that we want to prevent preloading SQL queries in case only one "path from alias" is needed (for current url/page).

catch’s picture

Why we cache only paths (as also mentioned by @berdir). It seems that we could cache entire path-alias mapping instead of preloading it fro DB.

Caching only the paths means that it's not necessary to invalidate the cache when path aliases are added/updated. That decision pre-dates having cache tags available so we could maybe look at that again.

Why are we preloading only in getAliasByPath()? Wouldn't also getPathByAlias() benefit from preloaded mappings? - I suspect that we want to prevent preloading SQL queries in case only one "path from alias" is needed (for current url/page).

getAliasByPath() is needed for URL generation, getPathByAlias() is needed only for the incoming request - so there's no real saving on that side.

Haven't reviewed the latest patch yet.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Also looks like this needs more discussion, or a follow-up.

catch’s picture

Priority: Normal » Critical
Issue tags: +Performance

Discussed with berdir in irc, let's fix the caching here. This is completely broken atm so bumping to critical.

Anonymous’s picture

yes please to fixing caching.

over at #2231595: Add a cache backend that checks an inconsistent cache, then falls back to a consistent cache backend, any needless cache writes show up pretty much straight away, and the path system sure does love it some silly writes.

berdir’s picture

Assigned: Unassigned » berdir

Going to try and work a bit on this.

xjm’s picture

Category: Task » Bug report

(Title needs a correction too)

xjm’s picture

berdir’s picture

Assigned: berdir » Unassigned
StatusFileSize
new39.63 KB
new23.6 KB

Ok, this should be a much better and has a ton of unit test coverage :)

Relevant changes:

- cacheNeedsWriting defaults to FALSE, no need to set it to FALSE then when loading from cache.
- Killed getPathLookups(), it is useless now (it was only necessary when the caching was in the decorator) and it was way too limit for the other improvements
- path lookup cache is now keyed by language and also includes noAlias misses.. the logic was there in preload but they were not cached :(

Unit tests can possibly be refactored a bit, I focussed on covering all scenarios that I could think of and not to optimize lines of code ;)

Did also manual testing of a page of nodes in 3 different languages, everything went through preload, with and without aliases.

I think we can look into caching actual aliases in a follow-up because that will then be an internal implementation detail.

What I still don't like is that whole CacheDecoratorInterface business: It's a weird split of methods, writeCache() is on decorator, clearCache() on aliasManager and has very generic/misleading method names but I can possibly live with it. Don't really see the point in that, but so be it.

Generic method names: setCacheKey() is actually the path, and this should be more like a cache key suffix, because there's no longer a cache_path bin that is only used for this, but the generic cache_data bin. This means that you could access site/some-existing-other-cid and that would then seriously mess up things :) Another bug that needs to be fixed.

What I didn't change is that preloading happens in getPathFromAlias(), the reason is that otherwise we can't expect that the cache key is set as that happens at the same time (Which is *not* checked and another example while the generic naming is problematic).

Anonymous’s picture

Status: Needs review » Needs work

played with this patch a bit, one thing i noticed:

  public function onKernelRequestConvertPath(GetResponseEvent $event) {
    $request = $event->getRequest();
    $path = trim($request->getPathInfo(), '/');
    $path = $this->pathProcessor->processInbound($path, $request);
    $request->attributes->set('_system_path', $path);

    // Set the cache key on the alias manager cache decorator.
    if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
      $this->aliasManager->setCacheKey($path);
    }
  }

so the call to pathProcessor->processInbound() ends up in the AliasManager code, and runs a cache get before the cache key is set. that seems bad because of this:

    if ($this->preloadedPathLookups === FALSE) {
      $this->preloadedPathLookups = array();
      $cached = $this->cache->get($this->cacheKey);
      if ($cached) {
        $this->preloadedPathLookups = $cached->data;
      }
    }

the cache get will fail (because no cacheKey), and we'll always end up with an empty preload list?

berdir’s picture

Yeah, that needs to change, not sure why it worked for me. I'm actually wondering now if I tested it before I moved it back into getPathFromAlias() ;)

Not sure how exactly we want to do that? Move it back and make it conditional on having a cache Key? That would also avoid loading the cache in case no aliases URL's are displayed?

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new39.7 KB
new6.05 KB

Can you check this? also fixed the cache key prefix problem.

Status: Needs review » Needs work

The last submitted patch, 19: 2233623_19.patch, failed testing.

Anonymous’s picture

yep, #19 fixes the issue i mentioned in #17, nice work :-)

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new40.78 KB
new1.08 KB

This should fix the test fails.

berdir’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
Anonymous’s picture

Status: Needs review » Needs work

yay, this is looking good. a couple of small nits, otherwise i think this is RTBC:

+   * happens if the laias manager is used in the context of a request.

s/laias/alias/

+      // Set the path cache to expire in 24 hours.
+      $expire = REQUEST_TIME + (60 * 60 * 24);

maybe just use a descriptive variable name instead:

      $twenty_four_hours = 60 * 60 * 24;
      $expire = REQUEST_TIME + $twenty_four_hours;
slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new23.23 KB
new1.19 KB

Great job! Thank you!

This last two nitbits fixed.

berdir’s picture

If you want to be nitpicky, then you could just put that inline now, a separate variable for that seems unnecessary and not what we usually do?

slashrsm’s picture

Honestly, I don't care how we do it. Until it is clear what those numbers mean I'm fine with it. Another possibility would be to create a constant on class.

Anonymous’s picture

yep, me either. berdir - please reroll with whatever you want for that block.

berdir’s picture

StatusFileSize
new23.19 KB
new697 bytes

What makes you think I do? (Just care about this getting in) :)

Here's a patch with my suggestion, feel free to RTBC whatever you prefer ;)

berdir’s picture

Actually, you have to RTBC #29 or re-roll the previous patch, because it didn't apply anymore :p

Anonymous’s picture

Status: Needs review » Reviewed & tested by the community

yay.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/lib/Drupal/Core/Path/AliasManager.php
@@ -78,13 +102,49 @@ class AliasManager implements AliasManagerInterface {
+    if ($this->cacheNeedsWriting && !empty($path_lookups) && !empty($this->cacheKey)) {

Couldn't se do the $this->cacheNeedsWritingCheck at the start of the method? No point building the array otherwise.

Looks good apart from this.

slashrsm’s picture

StatusFileSize
new23.25 KB
new2.07 KB

Fixed.

catch’s picture

Status: Needs review » Reviewed & tested by the community
xjm’s picture

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.x, thanks!

  • Commit 09702f8 on 8.x by catch:
    Issue #2233623 by Berdir, slashrsm, xjm: Fixed Merge...
wim leers’s picture

Yay, thanks for this!

berdir’s picture

Status: Fixed » Needs review
StatusFileSize
new18.59 KB

Urks, what's up with those variable names...

Wow, why did my unit tests not catch this?

Double-Urks.

:)

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Confirmed the tests catch the bug

phpunit --filter AliasManagerTest
PHPUnit 3.7.21 by Sebastian Bergmann.

Configuration read from /Volumes/devdisk/dev/sites/drupal8alt.dev/core/phpunit.xml.dist

....EE.E.EE

Time: 2 seconds, Memory: 69.00Mb

There were 5 errors:

1) Drupal\Tests\Core\AliasManagerTest::testGetAliasByPathNoMatch
Undefined variable: twenty_four_hours

/Volumes/devdisk/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Path/AliasManager.php:148
/Volumes/devdisk/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php:229
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:176
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:129

2) Drupal\Tests\Core\AliasManagerTest::testGetAliasByPathMatch
Undefined variable: twenty_four_hours

/Volumes/devdisk/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Path/AliasManager.php:148
/Volumes/devdisk/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php:267
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:176
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:129

3) Drupal\Tests\Core\AliasManagerTest::testGetAliasByPathCachedMissLanguage
Undefined variable: twenty_four_hours

/Volumes/devdisk/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Path/AliasManager.php:148
/Volumes/devdisk/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php:367
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:176
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:129

4) Drupal\Tests\Core\AliasManagerTest::testGetAliasByPathUncachedMissNoAlias
Undefined variable: twenty_four_hours

/Volumes/devdisk/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Path/AliasManager.php:148
/Volumes/devdisk/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php:468
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:176
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:129

5) Drupal\Tests\Core\AliasManagerTest::testGetAliasByPathUncachedMissWithAlias
Undefined variable: twenty_four_hours

/Volumes/devdisk/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/Path/AliasManager.php:148
/Volumes/devdisk/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Path/AliasManagerTest.php:523
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:176
/usr/local/php5-5.5.8-20140112-185654/lib/php/PHPUnit/TextUI/Command.php:129

FAILURES!
Tests: 11, Assertions: 38, Errors: 5.
catch’s picture

Status: Reviewed & tested by the community » Fixed

Extra tests look great, not sure how we wall missed $twenty_four_hours_four_hours.

Committed/pushed to 8.x, thanks!

Status: Fixed » Closed (fixed)

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

effulgentsia’s picture

From the #35 patch committed in #36:

+++ /dev/null
@@ -1,128 +0,0 @@
-  public function getAliasByPath($path, $langcode = NULL) {
-    return $this->aliasManager->getAliasByPath($path, $langcode);
-  }
...
+++ b/core/lib/Drupal/Core/Path/AliasManager.php
@@ -162,12 +237,14 @@ public function getAliasByPath($path, $langcode = NULL) {
     // Try to load alias from storage.
     if ($alias = $this->storage->lookupPathAlias($path, $langcode)) {
       $this->lookupMap[$langcode][$path] = $alias;
+      $this->cacheNeedsWriting = TRUE;
       return $alias;
     }

This does not look like a no-op. Looks to me like this meant that prior to this patch, the decorator's getAliasByPath() didn't trigger a new cache write, but after this patch, we started triggering that. Patch in comment #3 also has this hunk.

#2582219: Preload Cache in AliasManager can get huge is now trying to revert that, so if anyone here has feedback about that, please comment on that issue.