Problem/Motivation

The render system has \Drupal\Core\Render\RenderCache(Interface). This is capable of doing cache redirects, based on bubbled cache contexts. I.e. compare the originally known cache contexts with those that the final-to-be-cached data depends upon, and if that set is different, cache it as a cache redirect. (See RenderCache's docs for details.)

This is useful outside of the render system too.

Proposed resolution

Abstract RenderCache into a service (name TBD, initial quick thought: RedirectingCache) that RenderCache can use, so that RenderCache is just one of many things able to use this generic concept.

Remaining tasks

TBD

User interface changes

None.

API changes

None, pure API addition.

Data model changes

None.

Release note snippet

A new VariationCache API has been added. Users of the Variation cache contributed module should review whether it is still required.

CommentFileSizeAuthor
#193 2551419-nr-bot.txt90 bytesneeds-review-queue-bot
#189 2551419-nr-bot.txt90 bytesneeds-review-queue-bot
#159 for-review-do-not-test.patch129.5 KBkristiaanvandeneynde
#156 drupal-2551419-156.patch140.26 KBkristiaanvandeneynde
#154 drupal-2551419-154.patch140.26 KBkristiaanvandeneynde
#154 interdiff-152-154.txt2.67 KBkristiaanvandeneynde
#152 drupal-2551419-152.patch139.18 KBkristiaanvandeneynde
#152 interdiff-149-152.txt1.31 KBkristiaanvandeneynde
#149 interdiff-146-149.txt2.2 KBkristiaanvandeneynde
#149 drupal-2551419-149.patch139.17 KBkristiaanvandeneynde
#146 drupal-2551419-146.patch138.45 KBkristiaanvandeneynde
#146 interdiff-144-146.txt1.38 KBkristiaanvandeneynde
#144 drupal-2551419-144.patch138.37 KBkristiaanvandeneynde
#144 interdiff-141-144.txt4.69 KBkristiaanvandeneynde
#141 drupal-2551419-141.patch138.73 KBkristiaanvandeneynde
#141 interdiff-after-rebase-135-141.txt55.66 KBkristiaanvandeneynde
#135 drupal-2551419-135.patch131.13 KBkristiaanvandeneynde
#130 drupal-2551419-129.patch130.87 KBkristiaanvandeneynde
#130 interdiff-127-129.txt3.28 KBkristiaanvandeneynde
#127 drupal-2551419-127.patch130.78 KBkristiaanvandeneynde
#127 interdiff-124-127.txt5.24 KBkristiaanvandeneynde
#124 drupal-2551419-124.patch130.42 KBkristiaanvandeneynde
#124 interdiff-122-124.txt10.04 KBkristiaanvandeneynde
#122 interdiff-104-122.txt2.84 KBkristiaanvandeneynde
#122 drupal-2551419-122.patch123.68 KBkristiaanvandeneynde
#4 2551419-4.patch2.1 KBdawehner
#20 drupal-2551419-20-POC-do-not-test.patch12.31 KBkristiaanvandeneynde
#22 drupal-2551419-22.patch20.21 KBkristiaanvandeneynde
#23 drupal-2551419-23.patch22.58 KBkristiaanvandeneynde
#23 interdiff-22-23.txt2.85 KBkristiaanvandeneynde
#24 interdiff-23-24.txt2.4 KBkristiaanvandeneynde
#24 drupal-2551419-24.patch22.95 KBkristiaanvandeneynde
#25 interdiff-24-25.txt901 byteskristiaanvandeneynde
#25 drupal-2551419-25.patch22.96 KBkristiaanvandeneynde
#31 interdiff-25-31.txt10.14 KBkristiaanvandeneynde
#31 drupal-2551419-31.patch28.39 KBkristiaanvandeneynde
#34 interdiff-31-34.txt17.81 KBkristiaanvandeneynde
#34 drupal-2551419-34.patch42.84 KBkristiaanvandeneynde
#38 interdiff-34-38.txt6.36 KBkristiaanvandeneynde
#38 drupal-2551419-38.patch46.96 KBkristiaanvandeneynde
#40 interdiff-38-40.txt10.86 KBkristiaanvandeneynde
#40 drupal-2551419-40.patch53.82 KBkristiaanvandeneynde
#42 interdiff-40-42.txt27.89 KBkristiaanvandeneynde
#42 drupal-2551419-42.patch76.33 KBkristiaanvandeneynde
#44 interdiff-42-44.txt844 byteskristiaanvandeneynde
#44 drupal-2551419-44.patch76.33 KBkristiaanvandeneynde
#46 drupal-2551419-46.patch76.03 KBkristiaanvandeneynde
#48 interdiff-46-48.txt1.33 KBkristiaanvandeneynde
#48 drupal-2551419-48.patch76.44 KBkristiaanvandeneynde
#51 interdiff-48-51.txt9.78 KBkristiaanvandeneynde
#51 drupal-2551419-51.patch78.76 KBkristiaanvandeneynde
#54 interdiff-51-54.txt4.23 KBkristiaanvandeneynde
#54 drupal-2551419-54.patch80.33 KBkristiaanvandeneynde
#56 interdiff-54-56.txt2.11 KBkristiaanvandeneynde
#56 drupal-2551419-56.patch80.47 KBkristiaanvandeneynde
#60 interdiff-56-59.txt9.85 KBkristiaanvandeneynde
#60 drupal-255141-59.patch88.48 KBkristiaanvandeneynde
#62 interdiff-60-62.txt2.85 KBkristiaanvandeneynde
#62 drupal-2551419-62.patch91.06 KBkristiaanvandeneynde
#64 interdiff-62-64.txt2.27 KBkristiaanvandeneynde
#64 drupal-2551419-64.patch93.06 KBkristiaanvandeneynde
#68 interdiff-64-68.txt40.78 KBkristiaanvandeneynde
#68 drupal-2551419-68.patch101.27 KBkristiaanvandeneynde
#70 interdiff-68-70.txt4.88 KBkristiaanvandeneynde
#70 drupal-2551419-70.patch100.13 KBkristiaanvandeneynde
#74 interdiff-70-74.txt12.62 KBkristiaanvandeneynde
#74 drupal-2551419-74.patch99.89 KBkristiaanvandeneynde
#76 interdiff-74-76.txt1.04 KBkristiaanvandeneynde
#76 drupal-2551419-76.patch99.9 KBkristiaanvandeneynde
#78 interdiff-76-78.txt6.14 KBkristiaanvandeneynde
#78 drupal-2551419-78.patch104.23 KBkristiaanvandeneynde
#81 interdiff-78-81.txt15.16 KBkristiaanvandeneynde
#81 drupal-2551419-81.patch113.55 KBkristiaanvandeneynde
#84 interdiff-81-84.txt17.65 KBkristiaanvandeneynde
#84 drupal-3027178-84.patch125.86 KBkristiaanvandeneynde
#86 interdiff-84-86.txt1.25 KBkristiaanvandeneynde
#86 drupal-2551419-86.patch125.9 KBkristiaanvandeneynde
#89 interdiff-86-89.txt4.56 KBkristiaanvandeneynde
#89 drupal-2551419-89.patch126.3 KBkristiaanvandeneynde
#97 drupal-2551419-97.patch107.04 KBkristiaanvandeneynde
#100 interdiff-97-100.txt5.11 KBkristiaanvandeneynde
#100 drupal-2551419-100.patch106.94 KBkristiaanvandeneynde
#102 interdiff-100-102.txt2.4 KBkristiaanvandeneynde
#102 drupal-2551419-102.patch108.6 KBkristiaanvandeneynde
#104 interdiff-102-104.txt17.5 KBkristiaanvandeneynde
#104 drupal-2551419-104.patch123.42 KBkristiaanvandeneynde
#113 renderer-without.png86.25 KBkristiaanvandeneynde
#113 renderer-with.png91.15 KBkristiaanvandeneynde
#113 rendercache-without.png131.48 KBkristiaanvandeneynde
#113 rendercache-with.png134.79 KBkristiaanvandeneynde

Issue fork drupal-2551419

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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Berdir’s picture

Wim Leers’s picture

dawehner’s picture

FileSize
2.1 KB

So maybe something like this?

joelpittet’s picture

Version: 8.0.x-dev » 8.1.x-dev

Bumping to 8.1.x due to feature request nature. Triaging majors.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Priority: Major » Normal
Issue tags: +DX (Developer Experience)

#4: looks like a good start. I think it'd be great to

  1. refactor \Drupal\Core\Render\RenderCache to use whatever new class we introduce
  2. refactor away DynamicPageCacheSubscriber::responseToRenderArray() + DynamicPageCacheSubscriber::renderArrayToResponse() + DynamicPageCacheSubscriber::$dynamicPageCacheRedirectRenderArray

That'd prove the benefit.

dawehner’s picture

I agree, this is by no means major.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

amateescu’s picture

The Workspace module would also benefit from this if we'd be able to make the entity storage derive its persistent cache keys from the cache contexts of the request.

Just for reference, this was discussed in #2784921-164: Add Workspaces experimental module, point 12.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

kristiaanvandeneynde’s picture

Going to take this PoC and clean it up a little to add to Group. If it works well there, I'll let you know.

My current plan is to create a ContextCacheFactory service which has a get method that takes the name of a cache backend service. That would then return a polished version ContextCache from Daniel's proof of concept, wrapping said cache backend so it works with cache contexts and redirects.

Or does anyone have a better suggestion?

Wim Leers’s picture

First: THANK YOU! 🙏

I think ContextCache is a confusing name. We already have "cache contexts" and "Context API", and probably even more uses of "contexts". I think we need a better name, but I'm not sure what that is yet.

Thinking out loud:

  • VariantCache
  • VariationCache
  • AutoVaryingCache
  • BubbleawareCache
kristiaanvandeneynde’s picture

Okay so naming things is always hard :) Will start of with VariationCache or CacheContextAwareCache as I recall a core test recently being renamed from BubblingSomething to CacheContextSomething.

The factory I had in mind would be declared as such:

  variation_cache_factory:
    class: Drupal\Core\Cache\VariationCacheFactory
    arguments: ['@cache_factory']

Then you could "create" a cache context aware version of any cache backend like so:

  variation_cache.static:
    class: Drupal\Core\Cache\VariationCacheBackendInterface
    factory: variation_cache_factory:get
    arguments: [static]

VariationCacheFactory::get($bin) would simply invoke CacheFactory::get($bin) and wrap its return value in a VariationCache object.


Please note: For this to work nicely with the recently added entity.memory_cache service, it will need to be properly declared as a cache backend. See how I handled this for now in Group:

  # Recently, core introduced a new static cache that does not serialize its
  # data, unlike the cache.static service. This entity.memory_cache service has
  # two major drawbacks: First of all, it is named confusingly as it can work
  # for almost anything, not just entities. Secondly, it is not declared the
  # way cache backend services should be declared, meaning it cannot make
  # proper use of cache tags. In order to fix this, we declare the cache and
  # its factory the right way, albeit prefixed with group_ so that we do not
  # collide with core if it ever fixes this.
  cache.backend.group_memory_no_serialize:
    class: Drupal\group\Cache\MemoryCacheFactory
  cache.group_static_no_serialize:
    class: Drupal\Core\Cache\CacheBackendInterface
    tags:
      - { name: cache.bin, default_backend: cache.backend.group_memory_no_serialize }
    factory: cache_factory:get
    arguments: [group_static_no_serialize]

But there are other issues that (sort of) handle that:

Shall I create a dedicated issue that handles the above?

catch’s picture

Status: Active » Needs work

Just reflecting that there's some code here, I keep thinking this is a duplicate of the issue with a patch on it.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
12.31 KB

Here's the relevant part of the huge patch found here: #3041087: Update the new permission layer to be alterable.

Ignoring the obvious fact that the classes are namespaced for Group right now, it should give you a general idea of the approach I've taken. Oh and the best part is: It actually seems to be working :)

kristiaanvandeneynde’s picture

If you're looking for a concrete example, scour the massive patch in the other issue for the ChainGroupPermissionCalculator class.

kristiaanvandeneynde’s picture

FileSize
20.21 KB

Let's see what this does while I'm away for lunch. Should largely work, but I need to do some further investigation whether I got all bases covered and the following line in ::set() needs love as the old CID-comparing didn't take cache tags into account:

    if ($elements['#cache'] !== $pre_bubbling_elements['#cache']) {
kristiaanvandeneynde’s picture

FileSize
22.58 KB
2.85 KB

Forgot to actually inject the new cache factory.

kristiaanvandeneynde’s picture

For working on such a complex issue I sure am making a lot of rookie mistakes here...

kristiaanvandeneynde’s picture

Oh for the love of...

Sorry for all the noise.

Status: Needs review » Needs work

The last submitted patch, 25: drupal-2551419-25.patch, failed testing. View results

kristiaanvandeneynde’s picture

518 test failures :o

Well, at least we have something to work with now :)

tstoeckler’s picture

Really nice work. The patch itself looks pretty good. Didn't play around with it yet, though.

Noticed one thing:

+++ b/core/lib/Drupal/Core/Cache/VariationCache.php
@@ -0,0 +1,171 @@
+      return FALSE;

It seems the callers of this method do not account for this case.

tstoeckler’s picture

pretty good

Wow, I guess today is understatement day. What I meant to say instead: really impressive (!) and surprisingly elegant for such a complex topic

Sorry for the glibness...

kristiaanvandeneynde’s picture

Re #28 Which return value would that be? It's not entirely obvious from the context.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
10.14 KB
28.39 KB

This should fix some tests, I also type-hinted the cache keys and cleaned up the cache key comparing logic to do exactly the same as before through use of a public method on the variation cache.

Status: Needs review » Needs work

The last submitted patch, 31: drupal-2551419-31.patch, failed testing. View results

tstoeckler’s picture

Re #30: I was talking about VariationCache::createCacheId():

+++ b/core/lib/Drupal/Core/Cache/VariationCache.php
@@ -0,0 +1,180 @@
+      $cid = $this->createCacheID($redirect_cache->data['keys'], $redirect_cache->data['cacheable_metadata']);
...
+    return $this->cacheBackend->get($cid);
...
+    $cid = $this->createCacheID($keys, $cacheable_metadata);
+    $this->cacheBackend->set($cid, $data, $this->maxAgeToExpire($cacheable_metadata->getCacheMaxAge()), $cacheable_metadata->getCacheTags());
...
+  protected function createCacheID(array $keys, CacheableMetadata &$cacheable_metadata) {
...
+      return FALSE;

Here it is with some more context

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
17.81 KB
42.84 KB

I've refactored parts of RenderCache to account for the changed approach to how it should call the cache. It was currently using the same cache keys for both the redirect and the actual cache item.

Also made sure the test case calls the cache correctly, no longer tries to test a method that was removed and some other minor test fixes. Hopefully this will make fewer tests fail now.

kristiaanvandeneynde’s picture

Re #33 ah, you're right. That was for a short-circuit at the top of RenderCache::get() and ::set(). It seems to be unused now but maybe that served a purpose: If we can't create a cache ID, we shouldn't try to get or set the cache entry.

I should update VariationCache to include checks for whether the cache ID is not FALSE so the behavior remains unchanged. The only difference being that this won't short-circuit RenderCache as early as it used to because it now first needs to call the cache.

Status: Needs review » Needs work

The last submitted patch, 34: drupal-2551419-34.patch, failed testing. View results

kristiaanvandeneynde’s picture

Over a 100 failures fewer 🎉

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
6.36 KB
46.96 KB

@tstoeckler Looking at the test failures, it seems what you were on about in #28 is causing a lot of failures. So we definitely need to refactor that part too. But I'm out of time this week.

Attached is a patch that should fix some more misuse of the new cache.

Status: Needs review » Needs work

The last submitted patch, 38: drupal-2551419-38.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
10.86 KB
53.82 KB

Fixed a few more test cases and fixed the render cache calling the backend when it didn't use to.

The VariationCache::createCacheId() method now always expects valid keys and no longer returns FALSE as that was behavior unique to the RenderCache (keys might be missing) and cache backends should be able to rely on the fact that they're being called correctly.

Introduced RenderCache::isElementCacheable() to reintroduce the short-circuiting behavior to RenderCache.

Status: Needs review » Needs work

The last submitted patch, 40: drupal-2551419-40.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
27.89 KB
76.33 KB

Added some more test fixes and added ::delete() and ::validate() to the variation cache while also changing ::set() to only set a redirect if need be.

It does seem like an awful lot of core tests call the render cache bin directly rather than going through RenderCache, making this a tough nut to crack.

Status: Needs review » Needs work

The last submitted patch, 42: drupal-2551419-42.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
844 bytes
76.33 KB

Well if we're going to make silly typos, of course tests are gonna fail. Let's see what happens next.

Status: Needs review » Needs work

The last submitted patch, 44: drupal-2551419-44.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
76.03 KB

Wait, so I reran the tests without the change from the interdiff? What a nice way to spend my time.

Status: Needs review » Needs work

The last submitted patch, 46: drupal-2551419-46.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
1.33 KB
76.44 KB

Found another culprit! We used to store the cache redirect by the pre-bubbling CID and now I simply passed the keys and the cacheability of the post-bubbling element, effectively caching it under the post-bubbling CID.

I realized we can actually store the redirect indefinitely, so I did exactly that but did ensure we use the pre-bubbling contexts (and thus CID).

Status: Needs review » Needs work

The last submitted patch, 48: drupal-2551419-48.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Hmm only 5 fixes :/ Back to the drawing board I guess.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
9.78 KB
78.76 KB

So the things we were caching got influenced by the cache context folding, which is no longer the case. To fix that, I made sure the potentially updated cache tags and max-age from the actual variation cache got merge back into the cache object's #cache data when retrieving a render array from the cache.

Status: Needs review » Needs work

The last submitted patch, 51: drupal-2551419-51.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

🎉🎉🎉

I still have some ideas as to what might be wrong with the last few tests, but this is some amazing progress already.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
4.23 KB
80.33 KB

Fixed a regression regarding the rendered cache tag and a bug where I would only update the cache keys for the redirect if the redirect got updated.

Status: Needs review » Needs work

The last submitted patch, 54: drupal-2551419-54.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
2.11 KB
80.47 KB

Make sure the cache tags we retrieve from the cache have a proper incremental index after filtering out the 'rendered' tag as some tests were doing exact matches. You could argue that PluginTest should have used assertEquals() instead of assertEqual() as I believe the former ignores numeric indexes and would thus not have caused a test fail.

Also set all of the combined cache tags and max-age on the cache redirect like the old system used to. I first figured we could cache the redirect indefinitely, but it seems the tests disagree. I can think of one edge case where this would make sense, so trying things out with the old logic.

Status: Needs review » Needs work

The last submitted patch, 56: drupal-2551419-56.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

I think I know what broke TrackerTest (and potentially others). RenderCache::set() used to update $elements cache tags because it folded the cache contexts and got $elements by reference. The new version doesn't, but properly folds the cache contexts only when setting the cache item.

So the X-Drupal-Cache-Tags header no longer contains cache tags that were only added after cache contexts were folded. The X-Drupal-Cache-Contexts header remains unaffected.

I don't know about you, but I find it rather dirty that a cache's set method affects the thing it's caching for the rest of the runtime. That said, the whole #cache property needing to be a live copy of the cache metadata -even though the actual cache should be responsible for anything cache related- is the real culprit here.

I'm thinking of not allowing ::set() to affect the object it's caching, but to add the cache tags it finds during cache context folding directly to the response. That should allow the tests to pass, without needing every variation cache invocation to account for what might happen when they call ::set().

It should also fix most remaining test fails. The only thing it won't do is affect the #cache metadata during the current runtime. I don't know how much code depends on that being updated as you go, but I'm hoping none as it's a rather nasty behavior to expect in the first place.

Edit: The question then becomes whether we should also add the tags from ::get() to the response rather than back to the render array. Will investigate that last (after fixing ::set()).

kristiaanvandeneynde’s picture

Status: Needs work » Needs review

I've removed the render cache (ab)use from DynamicPageCacheSubscriber to see what that does. I've also fixed a test case.

The idea here is that we should not set the cache's optimized tags in RenderCache during ::get() or ::set(). So if this subscriber thingy works out well, the last move would be to update FinishResponseSubscriber to consistently set X-Drupal-Cache-Tags and X-Drupal-Cache-Contexts.

I say consistently because now it shows you the contexts after optimization, but cache tags before optimization unless the cache tags came from optimization in RenderCache and bubbled up to the response. Which makes no sense at all because anything coming from other places such as route access checks are not properly represented.

So my next patch will aim to set X-Drupal-Cache-Tags to the response's tags plus the ones that come out of cache context optimization and the X-Drupal-Cache-Contexts to the optimized contexts (remains unchanged).

Setting the cache's tags on the render array during ::get() did fix 285 test failures but I'm hoping the above will do the same.

kristiaanvandeneynde’s picture

Status: Needs review » Needs work

The last submitted patch, 60: drupal-255141-59.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
2.85 KB
91.06 KB

This should remove the clutter from the changes in the last patch.

Status: Needs review » Needs work

The last submitted patch, 62: drupal-2551419-62.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
2.27 KB
93.06 KB

This should fix the other fails, although we might need tor evisit the tests for EntityResourceBase (jsonapi) in a follow-up because its logic wasn't updated to only account for one cache entry, unlike rest's EntityResourceBase.

Status: Needs review » Needs work

The last submitted patch, 64: drupal-2551419-64.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Great work here! I had totally forgotten that Dynamic Page Cache currently (ab)uses the render cache redirect system, that alone makes this issue such a great cleanup, regardless of further possibilities. The subscriber looks so much more readable with the patch, really nice.

kristiaanvandeneynde’s picture

+++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
@@ -194,10 +172,23 @@ public function onResponse(FilterResponseEvent $event) {
-    $this->renderCache->set($response_as_render_array, $this->dynamicPageCacheRedirectRenderArray);

This line used to cause redirects to be stored because the cache contexts would almost always differ. We don't have that now, so the variation cache tries to find stuff varying by the contexts of the last response that was saved.

So it used to benefit not only from the RenderCache's cache context resolution, but also its redirect resolution, meaning everything stored under the 'response' cache key would eventually vary by the maximum amount of cache contexts that ever bubbled up to the response.

So because the response doesn't always vary by the same contexts, we need a new type of caching wrapper that also handles redirects the way RenderCache does (and then feed RenderCache that cache wrapper).

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
40.78 KB
101.27 KB

I've removed the self-healing strategy from RenderCache and put it in VariationCache as other systems relied on it as well (see #67). This makes RenderCache a really clean wrapper around the variation cache.

I expect this to break a lot more things and there are some todos here:

  1. Remove the extra cache tags logic from RenderCache::get() again.
  2. Rewrite the long ass text about self-healing in RenderCache::set() and move it somewhere else where it makes sense now
  3. Write tests for VariationCache and move all tests related to redirects and self-healing into that test
  4. Rewrite FinishResponseSubscriber (see #59)

Edit: Also need to remove sharesCacheKey() as it no longer serves a purpose

Status: Needs review » Needs work

The last submitted patch, 68: drupal-2551419-68.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
4.88 KB
100.13 KB

Wow, that went far better than expected. These small fixes should reduce failure noise even more.

Status: Needs review » Needs work

The last submitted patch, 70: drupal-2551419-70.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Some weird errors here:

  • demo_umami_content's UninstallDefaultContentTest installs banner blocks, but relies on its config/optional folder to actually place them. This no longer seems to happen as the front page doesn't contain said block's text any more. I do not see how changing the caching system would affect this. Unless it is served a stale cache of the front page before checking for blocks and the placement of blocks should have cleared the front page cache?
  • NodeBlockFunctionalTest expects a page with a block that varies by user to be cacheable (which it should not!) and used to get these results. However, every check from line 150 forward should always return UNCACHEABLE. Unless there is some placeholdering going on that I'm unaware of and is now broken?

I did notice DynamicPageCacheIntegrationTest is failing because the self-healing being moved into the variation cache. It used to HIT because RenderCache didn't need a first redirect to find the cache ID, i.e.: it would construct it itself because render arrays know more about their cacheability than they should during cache gets. Now, the self-healing has moved to an earlier phase.

Old scenario:

  1. Ask for route foo first which varies by nothing special
  2. Response is stored under response:foo:GET
  3. Ask for route bar which varies by animal
  4. Self-healing! Redirect is stored under response:bar:GET
  5. Response is stored under response:bar:GET:pig
  6. Visit route foo again, render cache thinks the cache ID is response:foo:GET and finds it
  7. Visit route bar again, render cache thinks the cache ID is response:bar:GET and finds a redirect to response:bar:GET:pig
  8. Render cache queries response:bar:GET:pig and finds the response

New scenario:

  1. Ask for route foo first which varies by nothing special
  2. Redirect is stored under 'response'
  3. Response is stored under response:foo:GET
  4. Ask for route bar which varies by animal
  5. Self-healing! Redirect is still stored under 'response' but updated to also account for 'animal'
  6. Response is stored under response:bar:GET:pig
  7. Visit route foo again, variation cache queries 'response' and finds a redirect to response:foo:GET:pig
  8. Variation cache queries response:foo:GET:pig and finds nothing
  9. Visit route foo again, variation cache queries 'response' and finds a redirect to response:bar:GET:pig
  10. Render cache queries response:bar:GET:pig and finds the response

So as you can see the only reason this worked (and was more efficient) under RenderCache was because we would tell RenderCache what contexts to use during ::get().

In order to maintain this more efficient approach of only storing redirects for those variations that deviate from the original, we would have to rewrite VariationCache to always ask for the "original cacheability" during ::get() and ::set(), which would make the solution less elegant.

So it's either:

  • Maintain clean API and take a performance hit because DynamicPageCache now varies by the sum of all possible cache contexts used
  • Rewrite variation cache to always ask for original cache metadata so that we can do more efficient redirects

I'll try the second to see how many remaining test fails are solved that way, but it might be less elegant :(

kristiaanvandeneynde’s picture

Come to think of it, RenderCache was only more efficient on the first tier of caching.

Suppose you have a render array that outputs your "housing situation".

  1. It initially varies by housing type: apartment or house
  2. If it's a house it varies by garden: no-garden or garden
  3. if it has a garden it varies by house orientation: east, south, west or north

Output would be one of the following scenarios:

  • You have a nice apartment!
  • You have a nice house!
  • You have a nice house with a north-facing garden!
  • You have a nice house with a east-facing garden!
  • You have a nice house with a south-facing garden!
  • You have a nice house with a west-facing garden!

Any other possible combination still exists, but just wouldn't affect the output.


In the current variation cache, we'd eventually see the following reachable cache entries:

Cache ID Cache entry
keys:apartment:no-garden:east You have a nice apartment!
keys:apartment:no-garden:south You have a nice apartment!
keys:apartment:no-garden:west You have a nice apartment!
keys:apartment:no-garden:north You have a nice apartment!
keys:apartment:garden:east You have a nice apartment!
keys:apartment:garden:south You have a nice apartment!
keys:apartment:garden:west You have a nice apartment!
keys:apartment:garden:north You have a nice apartment!
keys:house:no-garden:east You have a nice house!
keys:house:no-garden:south You have a nice house!
keys:house:no-garden:west You have a nice house!
keys:house:no-garden:north You have a nice house!
keys:house:garden:east You have a nice house with an east-facing garden!
keys:house:garden:south You have a nice house with a south-facing garden!
keys:house:garden:west You have a nice house with a west-facing garden!
keys:house:garden:north You have a nice house with a north-facing garden!

Not really efficient, right? There would also be some cache entries that can no longer be reached (e.g. keys:apartment) depending on which entries were stored first.


In the old RenderCache, we'd eventually see the following cache entries:

Cache ID Cache entry
keys:apartment You have a nice apartment!
keys:house:no-garden:east You have a nice house!
keys:house:no-garden:south You have a nice house!
keys:house:no-garden:west You have a nice house!
keys:house:no-garden:north You have a nice house!
keys:house:garden:east You have a nice house with an east-facing garden!
keys:house:garden:south You have a nice house with a south-facing garden!
keys:house:garden:west You have a nice house with a west-facing garden!
keys:house:garden:north You have a nice house with a north-facing garden!

As you can see, any house without a garden is still stored inefficiently and would also possible have some "dead" cache entries.


So what we're trying to reach is this:

Cache ID Cache entry
keys:apartment You have a nice apartment!
keys:house:no-garden You have a nice house!
keys:house:garden:east You have a nice house with an east-facing garden!
keys:house:garden:south You have a nice house with a south-facing garden!
keys:house:garden:west You have a nice house with a west-facing garden!
keys:house:garden:north You have a nice house with a north-facing garden!

This is actually possible if we store the minimal shared contexts between redirects at every step of the way rather than the total sum of contexts up front.

So in the above scenario we'd end up with the following cache redirects:

Cache ID Redirects to
keys Housing type
keys:house Housing type + Garden type
keys:house:garden Housing type + Garden type + Orientation

Meaning that the retrieval of an apartment only needs to follow one redirect, a house without a garden needs to follow two and a house with a garden three. This could prove to be a performance hit. The upside is that your cache is now far more efficient and will contain no dead entries whatsoever!

Any thoughts on this?

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
12.62 KB
99.89 KB

This should fix the DynamicPageCacheIntegrationTest failure in mentioned in #72. It also takes a new approach to variation caching as outlined in #73 by storing several redirects for complex cache entries.

If everything works out great, we're down a test fail. If this breaks something I did not foresee (ReousrceTestBase jumps to mind), we'll see how bad it breaks :o

I probably still need to further dumb down RenderCache and expand FinishResponseSubscriber as mentioned in #68.

Status: Needs review » Needs work

The last submitted patch, 74: drupal-2551419-74.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
1.04 KB
99.9 KB

Because getting it right the first time is too much to ask for :D

Status: Needs review » Needs work

The last submitted patch, 76: drupal-2551419-76.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
6.14 KB
104.23 KB

This adjusts FinishResponseSubscriber, which should fix RendererBubblingTest and TrackerTest, but will probably break other stuff. I also rewrote the self-healing tests already in preparation of removing it entirely in favor of VariationCache tests (which will also check for self-healing).

Status: Needs review » Needs work

The last submitted patch, 78: drupal-2551419-78.patch, failed testing. View results

kristiaanvandeneynde’s picture

Great so they got fixed!

The new REST/JSONAPI fails are because the user.permissions cache context is being optimized away and X-Drupal-Cache-Tags and X-Drupal-Cache-Contexts now properly reflect that. So those tests need updating.

This leaves only a few failures, which I'm hoping to fix by moving the FinishResponseSubscriber rework to DynamicPageCacheSubscriber so it happens earlier and is available to all subscribers that should have the updated contexts/tags.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
15.16 KB
113.55 KB

This should fix a few more tests (and again might break thousands). I'm expecting to see the rest/jsonapi stuff from #80 still broken because I haven't put in a fix for those yet.

Status: Needs review » Needs work

The last submitted patch, 81: drupal-2551419-81.patch, failed testing. View results

kristiaanvandeneynde’s picture

Aha! It turns out X-Drupal-Cache-Contexts is set too late!

Cache contexts only apply to the dynamic page cache, which runs at $events[KernelEvents::RESPONSE][] = ['onResponse', 100];. However, we do have RESPONSE subscribers that add cacheability after that. This cacheability is still used by page cache, but since that system only leverages cache tags, any cache context added after DynamicPageCacheSubscriber is irrelevant and should thus not show up in the header.

So what I suggest is this:

  • We fold cache contexts in DynamicPageCacheSubscriber and output X-Drupal-Cache-Contexts there.
  • We allow other subscribers (such as ClientErrorResponseSubscriber, FinishResponseSubscriber, ...) to add more tags
  • We still output these cache tags in FinishResponseSubscriber's X-Drupal-Cache-Tags because this is now the full set that will be used by PageCache. Although one could argue we should now ideally output it in PageCache instead to be consistent. Who knows what negatively weighted KernelEvents::RESPONSE might still add tags?

Please note: RouteAccessResponseSubscriber will also still be capable of adding cache tags, but any cache contexts added there (by running $response->addCacheableDependency($access_result);) will be disregarded in the header because it is too late there to actually do anything.

Maybe in the future we should have three headers?

  • X-Drupal-Dynamic-Page-Cache-Contexts
  • X-Drupal-Dynamic-Page-Cache-Tags
  • X-Drupal-Page-Cache-Tags

The above would be the most accurate way to represent what is actually being used in the caching layer.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
17.65 KB
125.86 KB

I've moved the contexts debug header to the more logical place. A lot of tests extending ContentTranslationUITestBase were failing but it seems many of them were specifying incorrect cache contexts.

Status: Needs review » Needs work

The last submitted patch, 84: drupal-3027178-84.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
1.25 KB
125.9 KB

Sigh, service definition fell through the cracks of patch creation...

The problem we're facing here is that the debug headers contained incorrect data but a lot of tests actually queried for (and found!) the right data. The only reason this worked was because RenderCache bled context-folding-affected cacheability up to the header level, even though it shouldn't have. I've since exposed (and fixed) this, but ideally we should have caught and fixed that bug first in a separate issue.

Can still split up the final patch to tackle both issues separately, I suppose.

Status: Needs review » Needs work

The last submitted patch, 86: drupal-2551419-86.patch, failed testing. View results

kristiaanvandeneynde’s picture

Okay, so take Cache.Drupal\Tests\node\Functional\NodeAccessCacheabilityTest for instance: It fails because of these lines:

    // User should be able to edit/delete their own content.
    // Therefore after the access check in node_node_access the user cache
    // context should be added.
    $this->drupalGet('node/' . $node1->id() . '/edit');
    $this->assertCacheContext('user');

'user' is no longer in the header because the header is now set in DynamicPageCacheSubscriber, given how cache contexts have zero impact on caching after that point. Yet, RouteAccessResponseSubscriber adds the user cache context to the response, after DynamicPageCacheSubscriber.

So even though the user cache context will be set by the time the page reaches PageCache, PageCache does not care about cache contexts (and shouldn't!) which begs the question: Why is NodeAccessCacheabilityTest even checking for the user cache context in the first place? It won't do anything at all.

The only time that 'user' could be set on the actual build is if it were added manually or an access result with the context was added to '#access', which makes Renderer::doRender() add it as a dependency to the build.


Footnote:
The only exception to the above that I could find is AnonymousUserResponseSubscriber, which checks the contexts after DynamicPageCacheSubscriber so that it could optionally add a cache tag to the PageCache entry. But that one checks for 'user.permissions', not 'user'.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
4.56 KB
126.3 KB

Right, so we are definitely going to want to "fix" the header issue we have going on here in a separate issue. For that reason I have restored the original headers to their original location (FinishResponseSubscriber) but added two new ones already that properly reflect the actual cache contexts that are being used. I might need to revert a few test adjustments left and right now or make them use the new headers.

For the record, we now have:

  • Dynamic page cache:
    • X-Drupal-Dynamic-Cache-Tags: All tags that are used for dynamic page cache
    • X-Drupal-Dynamic-Cache-Contexts: All (folded!) contexts that are used for dynamic page cache
  • Page cache:
    • X-Drupal-Cache-Tags: All tags that are used for page cache
    • X-Drupal-Cache-Contexts: Purely for debugging, no more folding happened past DPCS, so ['user', 'user.permissions'] is a possible outcome

Status: Needs review » Needs work

The last submitted patch, 89: drupal-2551419-89.patch, failed testing. View results

catch’s picture

Just to say #73 is really useful, but I'm having trouble thinking through what the real-world implications are in terms of hit rates vs. cache redirects (probably it would depend a lot on what the actual variations and traffic patterns are).

kristiaanvandeneynde’s picture

This whole header thing is really getting in the way. There are so many unrelated test fails in this issue because:

  • Our headers are wrong
  • RenderCache used to bleed info to said headers it shouldn't have

Created a spinoff issue here: #3052395: Our cacheability debug headers show completely wrong information. If we can commit that to 8.7.x and 8.8.x already we might have a better shot at fixing this issue without so many random test failures.

kristiaanvandeneynde’s picture

Re #91 that's where the performance experts should come in :) Every get will be slightly more expensive for complicated cases only. So only if you have three layers of cache contexts bubbling up, will the retrieval have to go through extra steps.

Old render cache

  • Without contexts: 1 get
  • With contexts but no bubbling contexts: 1 get (because of #cache being a hacky way of roughly knowing how things got cached)
  • With bubbling contexts: 2 gets

New variation cache

  • Without contexts: 1 get
  • With contexts but no bubbling contexts: 2 get (because we don't have #cache and need to follow a CacheRedirect)
  • With bubbling contexts: 2 gets + 1 get per nested context level, but only if the context values lead you down such a redirect trail

So as you can see, we already take an inherent hit by dropping the expectation of #cache being present so the variation cache can be used for storing items other than render arrays. Any hits beyond that only happen if there are multiple layers of redirects possible and the active context values point in that direction.

catch’s picture

hmm I think we should stick with the current method to be honest, that could add up to a lot of cache gets on a single page and while theoretically the number of variations could be a lot higher, it's very dependent on traffic.

kristiaanvandeneynde’s picture

Okay so given how I've been sidetracked by this whole header thingy, here's what I want to do:

  • Remove all header changes here and tackle those in #3052395: Our cacheability debug headers show completely wrong information
  • Only focus on the cache changes and accept that a lot of tests might fail because they expected to see cache tags coming from a context optimization in the headers. This only happened because RenderCache affected the very thing it was supposed to cache.
  • Update said tests to no longer expect the render array that was cached to be influenced in any way
  • Consequentially, update said tests to no longer expect the updated cacheability to bleed through to the headers
kristiaanvandeneynde’s picture

@catch in #94: We can't stick with the old system 100% because that one used to cheat by having #cache available to it. So we will see an increase to at least 2 gets when there are contexts (2nd bullet for variation cache in #93).

We can try and stick to the self-healing mechanism from RenderCache from that point on, but that would increase the second bullet point from #93 to 3 gets as well:

  1. Follow the keys, get a redirect
  2. Follow the redirect, find a "self-healing redirect"
  3. Follow this redirect to the end result

So by sticking to the old self-healing system, we need 3 GETs for every cache retrieval in a self-healing scenario. With the new multiple-redirect system we also have 3 at minimum.

So the question is: Do we opt for a system with a far better hit ratio and more efficient storage that might need to do more retrievals as soon as we have 2 or more layers of bubbling contexts? Or do we stick to the old, less efficient system but still accept that from now on everything will need 3 lookups?

Either way we can't get around the extra lookup. What we can make sure is that, with the new system, an object with only top-level contexts will still only need 2 lookups instead of 3.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
107.04 KB

No interdiff because this basically undoes a lot of try-and-error bughunting with regard to the test fails. I know now that whatever fails we get regarding missing tags we need to adjust to no longer expect tags the bled through from RenderCache.

Status: Needs review » Needs work

The last submitted patch, 97: drupal-2551419-97.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

A lot of similar failures in Hal, jsonapi and Rest and no more weird Umami failure. I call that a win.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
5.11 KB
106.94 KB

Well this was fucking stupid of me.

// Always returns max-age 0 because CacheableResponseInterface doesn't extend CacheableDependencyInterface.
$cacheability = CacheableMetadata::createFromObject($response);

// The below does work.
$cacheability = CacheableMetadata::createFromObject($response->getCacheableMetadata());

Also moved the context folding after the max-age check because max-age cannot be affected by it and we thus save a performance hit in case of max-age 0 scenarios.

Status: Needs review » Needs work

The last submitted patch, 100: drupal-2551419-100.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
2.4 KB
108.6 KB

The last one is easy.

TrackerTest tests code that never adds the user cache context to its content. What it does do, however, is add the user cache context to the local tasks. But those get properly placeholdered now and thus the optimized user.permissions cache context's tags never makes it to the response level (and thus cacheability headers).

Expecting fully green tests now. Adding "Needs tests" tag for adding unit/kernel tests to the new VariationCache.

kristiaanvandeneynde’s picture

🎉🎉🎉 FINALLY 🎉🎉🎉

Now that I can prove this works, I can already use it in Group and get a release out the door at long last.

kristiaanvandeneynde’s picture

Issue tags: -Needs tests
FileSize
17.5 KB
123.42 KB

This adds a performance boost and unit tests showing that the variation cache works (which we already knew). Nevertheless, it makes it easier for people to understand what's going on inside the variation cache.

This should still go green and as far as I'm concerned is finished. I will be using this version in Group while waiting for it to be committed to core. Unless all of the sudden tests start failing, that is. Which they shouldn't :o

borisson_’s picture

Overall this is looking very good, if there are remarks in here that have been resolved earlier I'm sorry about that. Most of these are nitpicks about documentation.

  1. +++ b/core/lib/Drupal/Core/Cache/CacheRedirect.php
    @@ -0,0 +1,29 @@
    +    // Cache redirects only care about cache contexts.
    +    $this->cacheContexts = $cacheability->getCacheContexts();
    

    I'm not sure about this comment, we should probably explain why or remove the comment. Because all this does is make me confused.

  2. +++ b/core/lib/Drupal/Core/Cache/VariationCache.php
    @@ -0,0 +1,209 @@
    +class VariationCache implements VariationCacheInterface {
    

    Needs docblock.

  3. +++ b/core/lib/Drupal/Core/Cache/VariationCache.php
    @@ -0,0 +1,209 @@
    +  public function get(array $keys) {
    

    I'm not sure if I like just calling this operation get(), since that is not very telling. Especially when doing VariationCache::get($keys) somewhere else. Should this be getRedirect, or is this not what this does?

  4. +++ b/core/lib/Drupal/Core/Cache/VariationCache.php
    @@ -0,0 +1,209 @@
    +  public function set(array $keys, $data, CacheableDependencyInterface $cacheability) {
    

    Same remark

  5. +++ b/core/lib/Drupal/Core/Cache/VariationCache.php
    @@ -0,0 +1,209 @@
    +  public function delete(array $keys) {
    

    This method is also not super clear. We're deleting only the last item from the chain, but because we are passing in an entire array of keys it sounds like the entire chain will be deleted and not one item.

    I think a clearer method name can help here.

  6. +++ b/core/lib/Drupal/Core/Cache/VariationCache.php
    @@ -0,0 +1,209 @@
    +    if ($contexts = $cacheable_metadata->getCacheContexts()) {
    

    I don't really like assignments in if statements. But that is just personal preference I guess. Just wanted to mention that I think splitting this in 2 lines can make this somewhat easier.

    $contexts = $cacheable_metadata->getCacheContexts();
    
    if (empty($contexts)) {
    return implode(':', $keys);
    }
    
    ...
    
  7. +++ b/core/lib/Drupal/Core/Cache/VariationCacheFactory.php
    @@ -0,0 +1,64 @@
    +class VariationCacheFactory implements VariationCacheFactoryInterface {
    

    Needs docblock.

  8. +++ b/core/lib/Drupal/Core/Cache/VariationCacheFactory.php
    @@ -0,0 +1,64 @@
    +  public function get($bin) {
    

    Same remark as earlier: getBin?

  9. +++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php
    @@ -313,24 +293,25 @@ public function providerTestContextBubblingEdgeCases() {
    +   * @todo Revisit when we have self-healing tests for VariationCache. This is
    +   * essentially a clone of the other bubbling tests now.
    

    Is this something you'd want to tackle here as well? I think we should probably already open a followup and link to it in the code?

kristiaanvandeneynde’s picture

Re #105: Thanks for your review! Answers below.

  1. We could add an @see VariationCache::createRedirectedCacheId(). It clearly shows only cache contexts are taken into account.
  2. You're right, need to add a docblock.
  3. The ::get() actually gets the item you stored from the cache. You do not need to care about whether it uses any redirects to get to the final result, ::get() takes care of that internally.
  4. Same answer :)
  5. I added ::delete() and ::invalidate() because some core tests were using it to make up for the fact that they leaked state. The interface clearly states that it only deletes/invalidates the entry that you would get from a ::get() call. I.e.: It takes the active cache contexts into account. I'd argue this is a bonus as you can target the item specifically for the active user. In any other case, I'd advise you to clear multiple items through cache tags.
  6. It's down to personal taste, as the coding standards don't dictate either way. I like it the way it is :P
  7. You're right, need to add a docblock.
  8. This mimics the behavior of CacheFactory, which also names its factory method ::get()
  9. I'd rather tackle that one in a follow-up to keep the patch here as clean as possible. The tests still go green, they might just have become redundant thanks to VariationCacheTest.
kristiaanvandeneynde’s picture

@catch in #94, I just thought of another amazing benefit to the new redirect system. In the current system, you are not allowed to vary a local action by a cache context with a parameter that has many options. In the new system, it would not be a problem.

An easy example is a fictional "Node Bookmarks" module with context user.has_bookmarked_node:<NODE_ID> returning '0' or '1'. If you want to show a local action saying "Add bookmark" or "Edit bookmark" to every node page you'd effectively kill caching altogether because the local tasks block would keep adding the context for every node to the ever-growing RenderCache redirect and start varying every page by said contexts.

In the new system, we no longer have this notion of ever-growing cache contexts, but would instead have to perform only one extra ::get() on every node page that uses the bookmark module. Nodes of node types that don't use the module wouldn't even have to perform said extra ::get()!

This opens up so many options as we can finally use any cache context with a potentially high amount of parameter options without having to worry where it'll end up in the render tree and whether it will kill caching.

So to sum up the benefits of the new system:

  • Items are only cached by what they vary by, instead of also what something unrelated that used the same keys so happened to vary by
  • Ergo: We have a higher cache hit-ratio because items only vary by what they specified they vary by
  • This completely eliminates duplicate cache entries (Last example in #73)
  • Ergo: We have better performance because items don't need to be recalculated when the redirect couldn't find a duplicate entry
  • We have a higher cache hit-ratio because redirects never need to be cleared
  • This cache is possibly more performant in high-variation scenarios where an expensive context is used because only those items that use said context will call it. In the old system, all cache items would call it.
  • The new system is possibly more performant across the board because of all of the above: higher hit-ratio, redirects being stored permanently (they get cleared very often in RenderCache), items only calling the contexts they used

I'd really like to get rid of the old system in favor of this new one as it:

  • Allows us to more freely use any context we like
  • Stops filling our cache with duplicate entries
  • Is simply more elegant and easier to understand than an ever-growing redirect

TL;DR: I think a performance hit of sometimes having to perform one extra get is far more acceptable than the current performance hit where we completely recalculate whole render trees that were already cached, simply because we couldn't find it in the cache.

ndobromirov’s picture

Issue tags: +Needs benchmarks

Whether it's faster or slower, we need to know how much...

kristiaanvandeneynde’s picture

I've never done benchmarks in my life. Any information on how to execute them with Drupal 8?

Berdir’s picture

Well, there are two things:

One is single-request comparison, you can for example use blackfire for that, on cache hits (with and without dynamic page cache I'd say) and also misses.

The other is how it behaves across multiple requests, which will be much more challenging but at least as important.

ndobromirov’s picture

I am eager to benchmark this but due to lack of time I can not at the moment...


Bench-marking however is fairly straight-forward - have a base-lime (no patch) and then have the patch version tested.

For Drupla 8 there are currently 2 ways to test a single request (as @Berdir mentioned).
- One is locally (no service registrations) with tideways + xhprof (DrupalVM has that prepared). Then install the Drupal's xhprof module and you are good to go.
- The other way is the BlackFire that does essentially the same but you need account on their site. I have not used that yet though.

In the end it will expose information about CPU time and RAM usage.
Once you have profiled the no-patch warm and cold caches and the same with the patch, you can compare and decide are the changes OK or not.

Note that any benchmark tool will add overhead due to the data collection routines.
Some PHP extensions have a high overhead as well, for example x-debug. Whenever bench-marking something it's a good thing to stop the x-debug extension fully from php (not just disable all breakpoints).

So in the end you can test the warm cases with the simplest AB tool that will just bombard a URL with some amount or requests.

ab -n 100 -c 1 http://example.com

This will hit example.com a hundred times with a single client thread and present you with a nice aggregated results.

That's pretty much all that I am using when in need to do benchmarks.

kristiaanvandeneynde’s picture

Thanks for the thorough explanation! I just got Blackfire working and will create a few pages that mimic the things we should definitely be testing. For making sure the caches are cold, I can use an event subscriber that clears certain cache tags on request.

kristiaanvandeneynde’s picture

Result 1: Warm caches, logged in as an admin, getting a simple node page.

WITHOUT patch:

WITH patch:

As you can see, the new cache does not affect load time for simple requests. What I did find was that you can clearly see how the case without the patch puts more stress on the renderer whereas the profile with makes the cache work harder.

If we can optimize CacheContextsManager::convertTokensToKeys() and CacheContextsManager::optimizeTokens() to statically cache its results for the duration of the request, then we should definitely see the new approach beat the old one.

kristiaanvandeneynde’s picture

Seeing similar result for other "simple pages": Same results or the new system being ever so slightly slower (1ms-5ms on a total of 1000-1200ms).

Keep in mind that this was to be expected. The new cache should definitely outperform the old when it comes to not having to render things we still have in cache (better hit ratio) and not having to call cache contexts that don't apply. These things are harder to test with blackfire because they usually span multiple requests, not just a single one.

I'd have to develop a custom page for this. Going to create one that works like #73

kristiaanvandeneynde’s picture

These results are from the complex case from #73 where I included a 20ms usleep() for every render call. This is very generous depending on what you're rendering.

Because of the higher cache hit ratio, the profile without the patch called usleep() 36 times whereas the one with the patch called it only 13 times! This clearly indicates that the volatility of our current redirect system (i.e.: it clears too much, too often) will cause reduced performance on a site that introduces new content on a regular basis.

It also shows a much higher cache hit ratio as:

  1. The cache warming caused 12 usleep() calls
    • Old cache: 12 calls
    • New cache: 12 calls
  2. The new cache could immediately find said items whereas the old one couldn't due to the expanding redirect. So that's another 12 usleep() calls.
    • Old cache: 24 calls
    • New cache: 12 calls
  3. The new cache could find all but one item after clearing a single item's tags vs the old cache couldn't find any.
    • Old cache: 36 calls
    • New cache: 13 calls
  4. During this entire process the old cache didn't get a single hit, whereas the new one got 23 out of 24 hits!

    If you want, I can upload the module I used to test this. But I don't want to attach even more files here unless necessary.

catch’s picture

For #115 do you have numbers for a fully warmed cache?

ndobromirov’s picture

As there are now profiles available I am dropping the needs benchmarks tag.
Added more related tags.

kristiaanvandeneynde’s picture

@catch in #115 Will try and get those numbers too. Will probably be early next week.

kristiaanvandeneynde’s picture

@catch re #116

Performance with a fully warmed cache, only retrieving:

Again shows a marginal increase (+1%) in time spent (1.56s vs 1.58s) on a page with multiple complicated retrievals and perfectly warmed caches. Given how rare that actually is with the old caching system (as a single invalidate would "soft invalidate" the whole lot), I'd say the new system is worth the absolutely minor performance hit in favor of the increased hit ratio and possibility to now really use cache contexts with parameters (as detailed in #107)?

catch’s picture

Looks like the 'without' case is doing 48 merge queries so might not have been a fully warmed cache?

kristiaanvandeneynde’s picture

I added usleep() calls in the Renderer. So if something was not retrieved, it needed to be rendered and as such would call usleep(). What you're seeing is my way of changing cache context values during runtime:

protected function renderRedirectedElement($house_type, $garden_type, $house_orientation) {
    $this->state->set('house_type', $house_type);
    $this->state->set('garden_type', $garden_type);
    $this->state->set('house_orientation', $house_orientation);

And every context looks like this:


  /**
   * {@inheritdoc}
   */
  public function getContext() {
    return $this->state->get('house_type');
  }
kristiaanvandeneynde’s picture

Rerolled with changes from #105/#106.

Please note that I addressed point 7 by adding a docblock to VariationCacheFactory but almost all implementations of CacheFactoryInterface do not have one. So even though core is at fault for not having them, adding one kind of breaks the habit.

kristiaanvandeneynde’s picture

+++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php
@@ -313,24 +293,25 @@ public function providerTestContextBubblingEdgeCases() {
+   * @todo Revisit when we have self-healing tests for VariationCache. This is
+   * essentially a clone of the other bubbling tests now.

I should probably get rid of this soon as the variation cache test now checks for self-healing, but let's see what people think of the patch now instead of focusing on changing one test.

kristiaanvandeneynde’s picture

I thought of one edge case and one problematic scenario that needed resolving. The tests in the interdiff speak volumes, so read that :)

Status: Needs review » Needs work

The last submitted patch, 124: drupal-2551419-124.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

I see what's going on. My changes to ResourceTestBase are now irrelevant because the latest patch properly handles overlap scenarios whereas the original one only dealt with supersets and subsets, but not partial matches. This is actually a good thing if my assumptions are correct because it means the patch will become even smaller in size, yet more robust.

Glad I thought of, caught and tested the edge cases in today's patch. :)

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
5.24 KB
130.78 KB

This basically reverts the changes to ResourceTestBase/EntityResourceTestBase and thus makes the patch even smaller.

Status: Needs review » Needs work

The last submitted patch, 127: drupal-2551419-127.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

borisson_’s picture

This basically reverts the changes to ResourceTestBase/EntityResourceTestBase and thus makes the patch even smaller.

The patch got bigger though, so I don't understand this comment?

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
3.28 KB
130.87 KB

Okay so both ResourceTestBase and EntityResourceTestBase should not be checking for cache redirects as it's:

  • Not the responsibility of these tests
  • No longer predictable how many redirects there will be
  • No longer the case that a redirect is at the given address.

    The old render cache would instantly look for keys:[foo]=bar and then maybe get redirected to keys:[foo]=bar:[cat]=dog whereas the new system actually first checks for keys and then could instantly look for keys:[foo]=bar:[cat]=dog, but could also have redirect steps in between. So the amount of CIDs matching LIKE %[foo]=bar% are no longer predictable.

The solution is to simply stop checking for redirects and make sure there is at least one entry in the DB.

kristiaanvandeneynde’s picture

@borisson_ Fewer changes, but further apart. So more "gray lines", meaning a slightly bigger patch in size. But definitely smaller in amount of changes (red/green lines) :)

kristiaanvandeneynde’s picture

Haha, this is what I like about having worked on this for so long. I can understand why tests are failing without having to XDebug them. Back to green we go! :D

borisson_’s picture

@borisson_ Fewer changes, but further apart. So more "gray lines", meaning a slightly bigger patch in size. But definitely smaller in amount of changes (red/green lines) :)

Aha, cool!

The solution is to simply stop checking for redirects and make sure there is at least one entry in the DB.

Ok, that makes sense.

In general, this patch looks very good. It has excellent test coverage, and I couldn't find any more nits to pick either that I didn't already bring up.
I feel very confident about the quality of the code, I am however not sure about the additional complexity this brings to an already complex part of the system.

My gut feeling about this patch is that it is done and can go in as it is in #130.

kristiaanvandeneynde’s picture

Cool, thanks for the review! I made sure the test coverage was very clean and easy to read to facilitate the reviewing of this patch.

90% of the test changes are converting the use of a CID string into cache keys. The other changes are documented in the comments here somewhere (such as #130).

kristiaanvandeneynde’s picture

Wim Leers’s picture

I finally caught up on this. Very sorry for the very long wait, @kristiaanvandeneynde! More than a 100 comments to catch up to takes a long time 😨 Especially for a topic as complex as this one!

The good news is that #2819335: Resource (entity) normalization should use partial caching landed, so now this patch can update two independent use cases, which give us even more confidence that this is a sensible abstraction 👍


#18: I hadn't even thought about cache factories in any of my previous comments here :)
#25: Looks like a nice first iteration passing most tests! This iteration still needs to port quite a bit from RenderCache.
#34: I'm seeing significant changes in existing tests. We should avoid this if at all possible. It signals a potential BC break.
#42: RE "awful lot of core tests call the render cache bin directly rather than going through RenderCache": Well … in the end it's all stored in a cache bin, so it should be okay to not go through RenderCache. Besides, many tests predate RenderCache by years! We shouldn't break them. That'd be a BC break. See previous point.
#51: Nice leap forward, down to two-digit failures! 👏
#56: Given this comment of yours and your line of thinking, I think you're starting to grok some of the trickiest bits there, which took @Fabianx and me a lot of time to get right! 🤓
#58: Really interesting observation! I think this is all largely caused by the Render ArrayPI. The render array is both the input and the output, and so by necessity things are being modified by reference.
#60: Yay for refactoring Dynamic Page Cache's code!
#62: That looks like a very big change in behavior for Dynamic Page Cache. Why isn't there a "redirect" cache entry anymore? #42 mentions only setting a redirect when necessary, but that already is only created when necessary in RenderCache? I think #67 wants to explain this, but I don't understand it :) Why do we no longer need a cache redirect?
#68: Yay!

#72 + #73: Epic debugging! :D The new scenario doing anything redirect related for route foo seems wrong: why is it storing a redirect at all, which is then later even reused when accessing route bar? Extrapolated this means that every Dynamic Page Cache entry would vary by the superset of cache contexts used by all routes seen so far. I see you reach the same conclusion towards the end of #72. It's essential to how RenderCache and Dynamic Page Cache work that the "original cacheability" (actually, original cache ID, i.e. cache keys + cache contexts) are taken into account. That's what the "pre-bubbling CID" was about. That's what the "long-ass comment" is explaining :)
Correct, in HEAD, RenderCache will accumulate all cache contexts, including conditional ones, and then vary by them unconditionally because @Fabianx and I at did not see a way to both vary correctly and not introduce multiple levels of redirects. Multiple levels of redirects would cause a very high I/O cost. So we chose the trade-off to have fewer round trips (lower latency) and store unnecessary variations (consume more cache space). I agree with your ideal scenario. I'd love to see how you arrive there without choosing other trade-offs, i.e. how you can ensure we don't get more I/O roundtrips. Choosing a different trade-off would change application-level behavior across all existing Drupal 8 deployments, and would hence be … very undesirable :)
And … based on what I read in #73, introducing multiple levels of redirects is exactly what you're proposing to do. As much as I like optimally using cache space, I don't think we can change this anymore. At least not in Drupal 8. And definitely not in this patch. Perhaps we can allow for multiple variation cache strategies, and then individual modules can choose whichever makes sense for them (given they know their data patterns)? And sites could even choose to change the default. I'm relieved to see @catch also raising concerns about this in #91 and #94.
(#73 is a superb comment by the way, crystal clear!)

#83: RE: " any cache context added after DynamicPageCacheSubscriber is irrelevant and should thus not show up in the header." -> no, this header is not meant to reflect the cache contexts respected by either Page Cache or Dynamic Page Cache — it's meant to inform developers (and tests) about the final, total set of cache contexts that the response varies by. For the similar remark you make about cache tags: Page Cache is unaffected by late KernelEvents::RESPONSE subscribers because it is not implemented as an event subscriber, but as a StackMiddleware. I suspect you assumed that Page Cache & Dynamic Page Cache need those headers to function at all, but that's not the case: they function by inspecting the CacheableMetadata stored in Response implements CacheableResponseInterface. It's only external caches that function based on such headers, such as Varnish or CDNs. And even then, those headers need to be named and formatted specifically for those external caches.
#84: We can't have Page Cache set the X-Drupal-Cache-Tags header for the same reason I mentioned before: this header is unrelated to Page Cache.
#88: See my response to #83 — that explains both why it makes sense for the user cache context to show up and for a test to assert its presence.
#89: This is going to negatively impact front-end performance significantly. Many sites have responses that are invalidated by hundreds of cache tags, resulting in multi-kilobyte headers. See https://www.drupal.org/node/2592471 for why X-Drupal-Cache-Tags was made opt-in.
#96: Why would we suddenly need to go from 2 to 3 cache reads if we stick to the old system? Yes, it did rely on a render array being manipulated in place. But that's what render arrays do all over the place. Why can't we continue to do that? I don't see why we absolutely need one extra lookup.
#102 + #103: Impressive work, thanks for being persistent enough to see this through to a green patch 😊Very few people would have kept going at it!
#104 says cache redirects only care about cache contexts. This is not true. It also needs cache tags to be associated, like HEAD's render cache already does. It's possible for a conditional cache context to no longer appear (and hence for a redirect to disappear) based on change configuration, which we wouldn't know about without the cache tag.
#107: There are a few bullets that are inaccurate. I've explained above why. One of those explains why increasing I/O calls for a single cache retrieval is a trade-off change that we probably don't want to make at this point, at least not without an opt-in. Aside of changed app perf characteristics I want to stress again that I think it's dangerous to gloss over the fact that cache backend I/O latency can be high.
#113 + #114: Thanks for doing profiling! It's important to also simulate different cache backend I/O latencies, to simulate the different environments Drupal operates in. For many Drupal setups, the cache lives in a DB (or Redis or memcache) that requires network I/O. I suspect you're testing with a DB cache backend that only does local I/O.
#115: Excellent job profiling the extreme edge case where the proposed patch gains the maximum benefit. Can you please do the same for the other extreme?
#130: +1 — this was done merely to harden tests — see #2877778: Harden test EntityResource + Dynamic Page Cache test coverage. I'm glad to see that complexity gone. Although I am still concerned about us changing so many tests so significantly in this issue.


Overall: absolutely incredible effort, @kristiaanvandeneynde! 🤩👏🤯

I agree with @borisson_ in #133 where he says that he feels unsure due to the additional complexity in an additional complex part of the system.

My top three key concerns:

  1. I was hoping this issue would only refactor \Drupal\Core\Render\RenderCache, \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber and \Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher, without changing a single test. Changing a few tests would be okay, especially those that were specifically testing implementation details of RenderCache. But this is changing a lot of other tests.
  2. Changing the trade-off (more I/O and latency-dependent for better cache hit ratio and smaller cache storage consumption). I'd love to see those internals improved, but not in this issue. Just the refactoring alone would already be a pretty big scope for a single issue/commit. Changing the overall approach deserves its own issue. If you say that there is no way to extract RenderCache's logic into a separate service without changing the approach, then I think we should first change RenderCache's logic in a separate issue. That'd make this issue far simpler.
  3. I don't see any comments pointing to #2429257: Bubble cache contexts (which introduced this functionality in the first place) nor any of the related bugfixes such as #2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption nor improvements such as #2466585: Decouple cache implementation from the renderer and expose as renderCache service, or any other related issue. (All issues in this area are listed in #2429287: [meta] Finalize the cache contexts API & DX/usage, enable a leap forward in performance.)

It's totally possible that reviewing the patch in detail will reduce my concerns :) I hope so!

But before doing that patch review (which would probably take several dozens of hours) I'd love to hear feedback on this comment from you, @kristiaanvandeneynde. I'd especially like @catch and @Fabianx to chime in on the changed trade-offs.

I hope it's clear I'm not at all saying that things must stay the way they are because I worked on them — I'd love for all this to become better, and I'm super glad that Drupal now has a lower bus factor on this part of the caching system 😄 I'm concerned about the risks.

Once again: epic work, @kristiaanvandeneynde. I can't believe you managed to change significant implementation aspects and got the patch to green!

kristiaanvandeneynde’s picture

I'll start with your comment-specific reviews, focusing on the more recent ones as the oldest ones are hardly reflected in the patch anymore.

The new scenario doing anything redirect related for route foo seems wrong: why is it storing a redirect at all, which is then later even reused when accessing route bar?

Because with render arrays we used to say: DPC always varies by route and method, the cache doesn't need to figure that out, we tell it so. With the new system we can't know that (unless we add an extra parameter asking for such a thing). So the old system would immediately start storing data or redirects at response:<route>:<method> whereas the new system stores an initial redirect with those exact directions at response

This extra necessary get is explained in #93 and #96 and is a necessary evil for the new system to work. But as further explained in #107 et al, the fact that we now have a "clean" API that is truly agnostic means we can finally use the caching layer at will without having to worry about edge cases that are only broken because of the way we implemented the render cache (e.g.: highly dynamic contexts on local actions block).


Re replies to #83, #84 and #85: I'd like to not go into detail about that one again as it seriously sidetracked this issue before :) The only thing I want to reiterate is that the header is set to cache metadata that does not accurately represent what the very layer that sets the header uses. Whether that is intentional, confusing or both I'll leave for another discussion. With all due respect, though!


Why would we suddenly need to go from 2 to 3 cache reads if we stick to the old system?

We wouldn't if we were only refactoring RenderCache. But because the whole goal of this issue is to abstract the logic in said cache into a layer that can be used by anything, we need to think about what we want our API to look like. Render arrays are the only thing that can currently cheat by saying what cache contexts are always there beforehand (pre-bubbling).

So either we accept the extra hit of the initial redirect or we change VariationCache to accept an extra parameter $minimum_cacheability. That would also work but again beat the purpose of trying to have a clean API by needing to specify cacheability twice (full vs bare minimum). I chose the self-healing API for the full 100% because it adds less complexity for the implementer at the cost of an extra redirect behind the scenes.


Says cache redirects only care about cache contexts. This is not true. It also needs cache tags to be associated

They actually don't care when it comes to doing their job: pointing to the right CID. That's what that comment is about.

As for cache tags: they are now applied in a more clever way :P Instead of tagging the redirect, we store the redirects indefinitely. The actual cache item would also be tagged with the cache tag that might change the contexts upon its next invalidation, right? So what happens now is that when said thing happens and we do not change contexts, the old redirects still work and would lead to a stale cache item because we invalidated the tag. At this point, the new cache entry still has the same contexts and is stored using the already existing redirects.

However, when we do change contexts after invalidating the cache tag, the old redirect route would still no longer find the cache entry (because the tag also cleared it) and then while trying to store its new entry figure out that the old redirect route no longer holds true and overwrite it with the new redirect route.

I find this system ingenious because the old render cache would clear everything all the time because the single massive redirect would be stored with the sum of all tags and thus continuously be cleared, leading to a lot of cache misses. The new system leaves redirect paths untouched unless something really did change. This means we do not break redirect routes to unaffected cache entries every time we invalidate one item.


Re increased I/O (reply to #107, #113, #114): I did test on a Docker set-up where the DB was in its own container. But network traffic would indeed be fast because it's all run locally.


Excellent job profiling the extreme edge case where the proposed patch gains the maximum benefit. Can you please do the same for the other extreme?

I wouldn't say any of the profiling was extreme. The ones from #113 and #114 show simple page loads are hardly affected (given local I/O) whereas #115/#119 weren't intended to show extreme cases, but rather profile a multi-page request, which Blackfire doesn't seem to do. The whole difficulty here was to profile what happens when requesting multiple variations of the same cache keys.

If you want, I could try and whip up an extreme case where the new cache wins by miles and try to find another extreme case with the same result for the old cache, but those scenarios would be so farfetched, I doubt it would do any good. The focus really lies on what happens across multiple requests because my research shows the old cache does a lot of invalidating of items it shouldn't invalidate.


Although I am still concerned about us changing so many tests so significantly in this issue.

I would agree if the tests truly changed. But if you filter out all of the conversions from cache IDs to cache keys and the removed usage of #cache_redirect, you'll notice not that many tests have changed. Only the changes from #130 remain (which you like) and some changes to scenarios in RenderBubblingTest, which should arguably no longer test for any cache entries as that is now taken care of by the dedicated VariationCacheTest. I even left a @todo there specifying how we might want to remove the cache testing from that class altogether.

kristiaanvandeneynde’s picture

Will comment on your top three concerns as soon as possible.

kristiaanvandeneynde’s picture

Getting to your concerns now:

  1. This was addressed in #137: It seems like a lot of changes, but really there aren't.
  2. We could re-use RenderCache's logic and then build around it, but it would just not be desirable. We'd have to adjust the API for get() et al to not only ask for keys but also for $permanently_present_cacheability. This is something we could certainly do, but if we do end up refactoring the whole thing to be self-healing, then we end up with a dead parameter.

    That said, the current "downside" of introducing at least one extra redirect with the new system would be alleviated by the above because we too could now predict the first cache ID where we need to look rather than store a redirect at the cache ID comprised only of the cache keys.

    The beauty of the new system, however, is that it could eventually replace all cache implementations. If you look at the code in VariationCache::get(), you'll notice that it falls back to a regular cache if there are no contexts. This means that in a future release of Drupal, we could simply have one API for all cache backends and it would only introduce redirects if it actually needs to. So if any alter were to change your cache object's cacheability to suddenly have contexts, you wouldn't need to worry about it because you're already calling a cache that knows how to deal with it. Nope, can't do this. If we first store something at keys and then try to GET anything with the same keys but also some contexts, we're gonna get the wrong cache item.

    Another downside of keeping the old system is that it doesn't properly support caching non-render-arrays. As shown in the old DynamicPageCacheSubscriber, you need to create some funky render arrays containing the actual object to allow the render cache to work. I'd rather not create a new API that has the first step of its instructions be: " Wrap your object in a render array".

    So what would you suggest here? How can I keep the old logic for now (with the ever-growing redirect that always gets flushed) while allowing for the new system to eventually replace it? So that perhaps we can introduce both systems at once and

  3. I'm not sure what I need to add here. Did I need to refer to those issues?

To summarize: we could keep old logic and even adjust new logic to have one redirect fewer by adding a $permanently_present_cacheability parameter, but that would also inhibit further improvements down the line because we'd be stuck with a parameter that we ideally want to get rid of ASAP.

Also, I cannot stress this enough: While the old system works fine at the first layer of redirects, the self-healing redirect is designed in such a way that it will be flushed almost all the time (because it's tagged with everything). So even if we introduce more redirects from the second level onward, it would still lead to a cache HIT rather than an almost guaranteed cache MISS.

I suppose an extra round-trip to the cache backend is cheaper than having to recalculate the whole thing, right?

kristiaanvandeneynde’s picture

Come to think of it, $permanently_present_cacheability might not be such a bad idea (see crossed out paragraph in previous comment). If we initially vary something by foo and someone else thinks it's funny to remove foo and vary it by bar, we could cross-check it against the $permanently_present_cacheability and throw an exception.

And if you ever need someone else's code's cache to support variations, then it's up to you to swap out that service with a decorated version that uses the variation cache.

By introducing this, we can at the very least remove the extra redirect you're all worried about :) Then the only remaining issue would be to keep the old self-healing caching that hardly caches at all or the new self-healing caching that might introduce an extra redirect, but not necessarily.

kristiaanvandeneynde’s picture

I expect to see a huge amount of red tests.

This patch addresses the fact that the old render cache could benefit from "knowing too much" and the new cache couldn't. After careful consideration (#139/#140) I now realize we ought to keep that insider knowledge.

Because of this, the "extra redirect step" that you all feared so much is a thing of the past. If contexts are provided to the $initial_cacheability, the first cache ID in the chain will immediately use those contexts. This should boost performance even more and with the improved hit rate of the new system, I'm cautiously confident that the new system might actually outperform the old.

Status: Needs review » Needs work

The last submitted patch, 141: drupal-2551419-141.patch, failed testing. View results

kristiaanvandeneynde’s picture

Not too bad, expected worse for such a huge change. Will have a look into these as soon as possible.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
4.69 KB
138.37 KB

I fixed a few inconsistencies where I did not revert some changes of a WIP different approach.

Status: Needs review » Needs work

The last submitted patch, 144: drupal-2551419-144.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
1.38 KB
138.45 KB

A bit worried about the JavaScript errors. But maybe I need to see why some tests fail on a 500 and will that tell me more. Attached patch adds the required cache contexts to a render cache GET and should net us better results.

kristiaanvandeneynde’s picture

Okay I debugged the 500 errors. It's because of the LogicException being thrown in VariationCache because the initial cache contexts contain the required ones (language_interface, theme, user.permissions) and the element's actual cache contexts don't contain those. Investigating.

Status: Needs review » Needs work

The last submitted patch, 146: drupal-2551419-146.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
139.17 KB
2.2 KB

Okay, turns out ToolbarController::preRenderGetRenderedSubtrees() completely overwrites the original element's cacheability. This should not happen causes various and caching issues (it can't be cache properly) but never surfaced until my new code started throwing a LogicException when a $pre_bubbling_elements has more cache contexts than the eventual $elements. This should fix some of the status code 500 failures.

Also reverting last patch because that seemed to have broken more than it fixed. I did manage to set up a testing site locally, so can now submit more error-proof patches :)

Status: Needs review » Needs work

The last submitted patch, 149: drupal-2551419-149.patch, failed testing. View results

kristiaanvandeneynde’s picture

LOL, that's 37 test fails fewer by simply fixing a bug in Toolbar. Taking care of the final two failing classes later this week.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
1.31 KB
139.18 KB

This should only leave UserBlocksTest::testUserLoginBlock as a fail. I'm trying to figure out why it's failing because when I reproduce the same scenario in the browser, it seems to work :/

Status: Needs review » Needs work

The last submitted patch, 152: drupal-2551419-152.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
2.67 KB
140.26 KB

Hah, found it! And it's yet another example of why we shouldn't test for cache hits in non-cache-related tests. Basically what happened was the following:

GET /filter/tips
User is anonymous, DPC stores a redirect chain of:

  1. Base CID
  2. Full CID including user.permissions, user.roles:anonymous, user.roles:authenticated

GET /filter/tips
User logs in and tries to store a redirect chain of:

  1. Base CID
  2. Full CID including user.permissions, BUT EXCLUDING user.roles:anonymous or user.roles:authenticated (because logged in)

Variation cache sees this and optimizes the redirect chain, "temporarily breaking the bridge" as shown in VariationCacheTest.
GET /filter/tips?foo=bar now tries to use the above chain but won't find anything, therefore calculating it once more and restoring the bridge as follows:

  1. Base CID
  2. Full CID including user.permissions, BUT EXCLUDING user.roles:anonymous or user.roles:authenticated (because logged in)
  3. Full CID including user.permissions, user.roles:anonymous, user.roles:authenticated

During this final step, DPC will still output a MISS where the test expects a HIT. Because of this, we can fix the test by adding a 2nd anonymous request, albeit with different query arguments (because PageCache will otherwise trigger and return the stored DPC MISS header). But the real fix would be to simply remove the DPC header checking from this test as it does not belong here.

Attached is a patch proving my research. Although we should ideally remove the header checks instead.

Status: Needs review » Needs work

The last submitted patch, 154: drupal-2551419-154.patch, failed testing. View results

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
FileSize
140.26 KB

Of all the query args I could have chosen, I ended up with foo=baz, which is also used further down the test 🤦
This replaces it with cat=dog and definitely goes green.

kristiaanvandeneynde’s picture

kristiaanvandeneynde’s picture

This leaves me to separate out the changes to the following:

  1. ItemCacheTagsTest and ShortcutCacheTagsTest because neither of them are really using the render cache but rather any cache. So we should change those tests to use a simple memory cache.
  2. TrackerTest needs to be updated as per my findings in #102.

All other tests changes are related to this issue's patch as they all have (sometimes hardly) valid reasons to test the render cache. Because the render cache backend changed, these tests need to reflect those changes. It would be a perfect world if they didn't test the backend directly, but rather the render cache itself, but here we are... It's not always that simple :)

kristiaanvandeneynde’s picture

FileSize
129.5 KB

I've created the following two, according to #158:
#3085475: ItemCacheTagsTest and ShortcutCacheTagsTest needlessly test the render cache.
#3085487: TrackerTest looks for a cache context it does not add.

From the latter:

Actually, I just realized we can't fix this in a separate issue because the current system will add these wrong cache tags to the header. This needs to go into the main patch.

So the TrackerTest changes need to remain in this patch.

For review, I have added the patch that does not contain any of the separated issues. This is obviously a review-only patch, but it might help filter out the noise.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

stBorchert’s picture

I have to admit that i didn't read the complete issue but do you have the Vary header in mind?
While playing around with JSON:API (especially Consumers) I stumbled across #2972483: Page Cache must respect Vary, otherwise it might serve mismatched responses. and #3023104: Introduce "Vary" page cache response policy.
Unfortunately it isn't possible at the moment, to use the "X-Consumer-ID" header implemented by Consumers because the page cache does not care about the Vary header (or any cache context at all).

Is it possible that your patch would fix this also?

gabesullice’s picture

@stBorchert, I don't think this issue will solve your problem. If you need Vary support, you can't use page_cache module. You can only use dynamic_page_cache module. To get anonymous response caching w/ Vary, you'll have to use something like Varnish. If you have further questions about that, feel free to open a support request for Drupal core using the jsonapi.module or page_cache.module component. You might also consider opening a support request in the consumers module queue.

stBorchert’s picture

Thanks for you quick reply.
I'm trying to fix a problem in Consumers (#3001043: Cacheability issue when using _consumer_id query in JsonApi responses.) where it isn't possible to get different results per consumer when using the Header introduced there but I'm sure to find a solution :)

bradjones1’s picture

Version: 8.9.x-dev » 9.1.x-dev

Pretty sure this is solidly 9.1.x now.

kristiaanvandeneynde’s picture

Is there still interest in moving this forward?

FWIW, recent versions of Group rely on VariationCache (https://www.drupal.org/project/variationcache) and with over 2.2k installs I have yet to see a bug report or performance complaint.

bradjones1’s picture

I was blown away by this when you presented it in Amsterdam and was honored to work with you on the contrib implementation.

I imagine there may be some hesitance in so far as it is a "big" change, but then again Drupal has such good code coverage for both this specifically and in integration tests (which are passing here) that mitigates such a concern in large part.

This probably got lost a bit in the D8 to D9 "new features" freeze that was effectively in place before 9.1 got opened, but now that it is - let's loop in core maintainers?

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

kristiaanvandeneynde’s picture

https://www.drupal.org/project/variationcache

5.5k installs, zero bug reports.

Wim Leers’s picture

👏👏👏🥳

kristiaanvandeneynde’s picture

I'd love to see this in core, though :) One module fewer to maintain and more possibilities when it comes to adding parameter-based cache contexts (see #107).

Wim Leers’s picture

What's holding me back from RTBC'ing this is the changes in performance characteristics as described in #107, #113, #115, #116 and #119. Unfortunately, none of the blackfire.io URLs work anymore… 😬

I did a thorough review in #136 but do not currently have the capacity to follow up on what I wrote then and your responses to it 😞 For that, I apologize.

However, if core maintainers like @catch, @FabianX or @Berdir approve of this, then you don't need my approval at all!

@catch, you're listed as the sole maintainer of the cache system right now — do you have updated thoughts since your last comment here (#120)?

bradjones1’s picture

Is there any agreement on a script/configuration/etc. for profiling this? Perhaps the cache was profiled when initially implemented and there's something there? Or is there any automation that was behind the earlier blackfire profiling efforts on this ticket?

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bradjones1’s picture

To perhaps provide an additional bump on this, I'd note that core itself has an open optimization on variation caches in json:api; e.g., the ResourceObjectNormalizationCacher.

kristiaanvandeneynde’s picture

Yeah, that's just another part of core that (ab)uses the render cache to get cache redirects working. Which is exactly what this issue is trying to fix/get rid of by introducing a dedicated variation cache (i.e.: cache with redirects).

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

borisson_’s picture

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

SocialNicheGuru’s picture

kristiaanvandeneynde’s picture

Update of #168 from almost 2 years ago:

https://www.drupal.org/project/variationcache - 11.6k installs, zero bug reports.

Group can be quite resource-intensive the bigger your site gets and how you configured it. I have yet to see a single report where someone's site was slow because of VC, it's usually some ungodly queries (which I've recently optimized a lot) that are causing issues.

To me, it feels like BigPipe how feels to Wim: I wrote it, it works, no-one complains about it. The fact that its issue queue has remained empty for so long, bar some D10 compatibility issues, speaks volumes about how well it works.

kristiaanvandeneynde’s picture

Tried to reroll into an MR because that's easier to review than a huge patch. Contains the fix from #3082059: ResourceTestBase needlessly tests the caching layer until that one gets accepted.

kristiaanvandeneynde’s picture

Okay seeing a few VariationCache test fails, which could make sense as I have not updated the tests for this issue over the years. Copied the ones from the standalone module now.

Also seeing "Failed asserting that 5 is equal to 4 or is less than 4." type errors, which was to be expected after the changed made to #3082059: ResourceTestBase needlessly tests the caching layer The maximum is 5, though, meaning that the maximum amount of extra redirects across all of core is 1.

Also seeing various "The text "SOME_TEXT" was not found anywhere in the text of the current page." failures, which I'd need to check as I'd be surprised if they were related.

Then also seeing a few "Failed asserting that 500 is identical to 200.", which again I'd need to check if that's even related.


Anyways, updated VariationCache and VariationCacheTest code to match standalone module and upped redirect limit to 5 to reduce some noise.

kristiaanvandeneynde’s picture

The final test that fails was ShortcutCacheTagsTest::testToolbar, added in #2914110: Shortcut hook_toolbar implementation makes all pages uncacheable

It tries to detect config:user.role.authenticated and user:1 because it expects the toolbar's user.permissions cache context to be optimized away for the placeholder's user cache context.

Relevant changes that caused this in shortcut_toolbar() are:

  • $items['shortcuts']['#cache']['contexts'] now contains 'user.permissions' rather than 'user'
  • $items['shortcuts']['tray']['children'] is now a lazy builder with the 'user' cache context

Just like #102, the placeholders are working properly now, so there is no optimization that needs to take place and therefore it never makes it to the page's cache tags.

This is due to changes in PlaceholderGenerator, but I'll read up on those again to double-check if anything odd is happening. I don't suppose so, but doesn't hurt to double-check.

kristiaanvandeneynde’s picture

Reading back up on the comments in the 70ies range, I noticed I added the change to PlaceholderGenerator in 78/81 because back then I still did not take initial cacheability into account and that was breaking DynamicPageCacheSubscriber and related tests.

Now that, since #141, we do take initial cacheability into account, I'm thinking I might be able to make this patch smaller by removing said changes again. Having said that, though, it's clear from my comments surrounding the issue that the change was actually for the better because the header output was not accurate before.

We'll see if I can make it go green when smaller, but right now it's already amazing that the tests go green after all these years.

kristiaanvandeneynde’s picture

Reading the changes in the MR and their associated comments, I'm actually inclined to keep things the way they are. I had good reasons for the changes that were made, I'm not gonna risk delaying everything a few more years because of perfectionism. AFAIC, this MR is "done".

kristiaanvandeneynde’s picture

Hiding patches as we use a merge request now.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
90 bytes

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

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

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

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
borisson_’s picture

Are we still waiting for the change in https://www.drupal.org/project/drupal/issues/3082032, or what are the next steps here? Looks like all of @catch's remarks have been resolved. I now feel more confident about this patch compared to #133 almost 5 years ago.

bradjones1’s picture

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
90 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 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.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review

Not sure why the MR keeps saying it needs to be rebased even after I merged in 10.1.x like I did last two times.

andypost’s picture

There's few big commits arrived today so it needs another rebase/merge, but there's a bug https://gitlab.com/gitlab-org/gitlab/-/issues/407115

andypost’s picture

Status: Needs review » Needs work

left review

kristiaanvandeneynde’s picture

Status: Needs work » Needs review

Added placeholder CR for that change here: https://www.drupal.org/node/3354596

Will flesh it out a bit more soon, but this way the link already works.

kristiaanvandeneynde’s picture

Updated the change record.

borisson_’s picture

I have used an ab-like tool to check if there is a difference in page response times. I have done this on my local machine with umami and a couple of extra blocks enabled on the homepage. This difference that we see here is super small, I think this is more likely to be something else that ran on my machine rather than the patch actually making it slower.

Based on this, and the discussions with @kristiaanvandeneynde this looks great.

WITHOUT PATCH
-------- Results --------

Successful calls    		100
Total time          		10.8378 s
Average             		0.1084 s
Fastest             		0.0892 s
Slowest             		0.3559 s
Amplitude           		0.2667 s
Standard deviation  		0.037784
Requests Per Second 		9.23
Requests Per Minute 		553.62

WITH PATCH
-------- Results --------

Successful calls    		100
Total time          		10.2614 s
Average             		0.1026 s
Fastest             		0.0266 s
Slowest             		0.2502 s
Amplitude           		0.2236 s
Standard deviation  		0.036465
Requests Per Second 		9.75
Requests Per Minute 		584.71

Setting this to rtbc

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

Actually setting to rtbc.

kristiaanvandeneynde’s picture

Woot, thanks for the performance review @borisson_! Ever since Blackfire changed, I haven't found the time to work on another performance test. So thanks a million.

Looks like the patch actually makes it slightly faster too? Could also be down to small differences on your machine as you noted.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Issue tags: +Needs followup

Made a couple of small commits to resolve unresolved threads. If I haven't broken anything I'm hoping to commit this to 11.x/10.2.x later today or tomorrow.

Tagging needs follow-up for using this in workspaces per #2551419-13: Abstract RenderCache into a separate service that is capable of cache redirects in a non-render array-specific way

  • catch committed 339643be on 11.x
    Issue #2551419 by kristiaanvandeneynde, catch, dawehner, Wim Leers,...
catch’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Fixed
Issue tags: +10.2.0 release highlights, +Needs release note, +Needs change record

Committed 339643b and pushed to 11.x. Thanks!

After doing that, I realised that we should have an extra change record for the new API and a release note with some information about it, to hopefully encourage contrib take-up where it's applicable.

Moving to needs work for that, but nice to finally land this one!

catch’s picture

Status: Fixed » Needs work
longwave’s picture

kristiaanvandeneynde’s picture

Wow, this is huuuuuge 🎉

After doing that, I realised that we should have an extra change record for the new API and a release note with some information about it, to hopefully encourage contrib take-up where it's applicable.

I'll try to draft a change record ASAP.

kristiaanvandeneynde’s picture

Draft change record here: https://www.drupal.org/node/3365546 Also, I flagged it as 10.2.x but did not see a commit for that (yet?), hope I didn't make a mistake with the version on the CR.

Now I just need to figure out a way to seamlessly allow people to transition from contrib VariationCache to core VariationCache. I was thinking the following:

  • As soon as core 10.2.0 lands, I make sure there's a version of contrib VariationCache where the core dependency is 10.2.x and up and a version where the max core version is 10.1.x
  • The one going until 10.1.x is simply the current version, the one starting from 10.2.x has every class as an extension of the new corresponding core class
  • As long as we support Drupal versions smaller than 10.2.x, we remain this way and keep our modules (i.e. Group) relying on contrib VariationCache
  • Once the minimum supported version for core is 10.2.x, we create a new release of Group where we directly call the core classes and drop the contrib dependency.

Would that work? AFAICR, having the same service name in contrib as a core service simply replaces the core one with the contrib one, so we should be fine?

If this needs further discussion, I'm definitely open to take it up on Slack or another D.O issue.

catch’s picture

Issue summary: View changes
Status: Needs work » Fixed
Issue tags: -Needs followup, -Needs release note, -Needs change record

See https://www.drupal.org/about/core/blog/new-drupal-core-branching-scheme-... for why there's no 10.2.x commit, don't worry it'll be in there :)

The plan for the contrib module in #209 sounds like it will work - should probably be an issue against variationcache for that?

Change record looks like a good start. Moving this back to fixed.

I re-reread the workspaces discussion linked from #13, and the issue that we wanted to fix got fixed via memory cache which to me is clean enough, so removing the needs follow-up tag at least for that one.

Added a release notes snippet.

kristiaanvandeneynde’s picture

Aha, that was a good read. Now I get why it's committed to 11.x

Thanks for the feedback on #209

Berdir’s picture

I wouldn't rely on your services automatically replace the ones from core, that depends on order in which the files are loaded. I'd do a ServiceProvider and alter them.

One problem is that you can't retroactively change core compatibility of old releases. If you do a new minor release that is not compatible with 10.2 and a new 2.x major version that requires 10.2, composer will by default just downgrade to the previous 1.x release as most people will have version constraint like ^1 in place. They will need to manually update that, assuming they directly require it. If not, then composer will update them to 2.x.

You could see if there's a way to not do a new major version, but support both earlier and newer versions in one release. You won't be able to extend from the core classes then, you'd still need to duplicate everything and either replace or define the services. You might be able to do some class alias trickery to extend from core classes if they exist, the way phpunit/twig and similar BC compatibility sometimes works. If you don't, you'd risk breaking code that relies on the new 10.2 interface.

Wim Leers’s picture

Epic! 🤩

Congratulations and thank you, @kristiaanvandeneynde!

Status: Fixed » Closed (fixed)

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

andypost’s picture

Is there reason why no change record published?

kristiaanvandeneynde’s picture

Hmm good point, they're still in draft status

Chi’s picture

Re #215 That's common issue on drupal.org.
Filed a ticket. #3384938: Publish change records when issues are fixed

kristiaanvandeneynde’s picture

I've gone ahead and published both CRs