Problem/Motivation

Another approach to solve the same problems that https://www.drupal.org/node/2348245 tried to tackle. We have things like views that have twelve plugin managers (and six handler managers) Whenever we use them we are getting the cached definitions individually for each manager, when we always need almost all of them for a view to work.

I think more import here is making less calls to the cache, rather than shaving lots of time off the page load. If a site comes under a lot of load, lots of more calls to your cache/database etc.. is not good news.

Proposed resolution

Create a backend that all plugin managers can use that collects cids for each plugin definition and loads them all in one getMultiple() call. Subsequent calls to get() individual definitions will then fetch it from the already loaded data.

Remaining tasks

Is this concept ligit? tests, reviews, settle on plan - all the things.

User interface changes

None

API changes

None

Comments

damiankloip’s picture

StatusFileSize
new9.3 KB

Here is an initial rough patch with this idea. Still needs some work of course.

damiankloip’s picture

Issue summary: View changes
damiankloip’s picture

Issue summary: View changes
wim leers’s picture

Title: Create a cache backend that pre loads multiple items in one getMutliple() call » Create a cache backend that pre loads multiple items in one getMultiple() call
damiankloip’s picture

Assigned: Unassigned » damiankloip

Working on some more improvements and tests.

dawehner’s picture

Just some really quick review.

  1. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,210 @@
    +    $this->bin = 'cache_multi_' . $bin;
    

    Do we really want to have an entire bin per wrapped bin?

  2. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,210 @@
    +  public function get($cid, $allow_invalid = FALSE) {
    +    return isset($this->cacheItems[$cid]) ? $this->cacheItems[$cid] : FALSE;
    +  }
    ...
    +   */
    +  public function getMultiple(&$cids, $allow_invalid = FALSE) {
    +    return array_intersect_key($this->cacheItems, array_flip($cids));
    +  }
    

    Shouldn't we otherwise call out to the wrapped cache backend, if we can't find some entry?

  3. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,210 @@
    +   */
    +  public function delete($cid) {
    +    $this->cacheBackend->delete($cid);
    +    $this->removeMultiKeys([$cid]);
    +  }
    

    any reason to not call out to deleteMultiple? I guess deleting doesn't happen that often?

damiankloip’s picture

Thanks for taking a look, there is much work to do :) But you know this:

1. This is poorly named right now, this is not wrapping a bin as such, this is just used as the cache key for the list or items to be pre loaded.
2. This is a good point, I think we will want to do something like that in case an item is missing for that request to avoid rebuilding when we don't need to.
3. Mehh. Not sure :)

damiankloip’s picture

Issue tags: +D8 Accelerate
damiankloip’s picture

Status: Active » Needs review
StatusFileSize
new14.21 KB
new10.51 KB

This should be a bit more complete, with passing tests. Some questions/todo's:

  • Use the cache.views_plugin proxy bin/thing for plugins and handler, currently just 'plugins'
  • Implement retrieving items from the storage backend if the multi backend does not have an item. This seems mostly unneeded but safer. If another request did rebuild the cache item during the current request?
  • Move the setting of the cache item and the multiCids key to one method? Currently it's just one method to set the multi key(s) and the adding of the item to the static cache is done manually
  • Some additional test coverage may be needed on top of the standard GenericCacheBackendUnitTestBase implementation
  • Better naming for the MultiBackend, and probably some of the properties
  • Consider not set() ing the multi cids cache key every time the methods are called. That would mean a destruct() or something?
jibran’s picture

Status: Needs review » Needs work

Wow it's green. Nice work @damiankloip

  1. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,344 @@
    +      $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : []);
    
    Looking at the <code>set

    and setMultiple code I think this should be $this->cacheItems[$cid] = $this->createCacheObject($cid, $data, $expire, $tags);

  2. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,344 @@
    +      $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : []);
    ...
    +    $return = $this->cacheBackend->setMultiple($items);
    

    Isn't this call redundant after above call? We can copy the return value of set in $return[$cid].

  3. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,344 @@
    +    $this->addMultiKeys(array_keys($items));
    

    If we are calling $this->set then this is also useless.

  4. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,344 @@
    +   * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
    

    @inheritdoc

  5. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,344 @@
    +   * @param $cid
    +   * @param $data
    +   * @param $expire
    +   * @param array $tags
    ...
    +   * @return \stdClass
    

    Description block missing.

damiankloip’s picture

Thanks Jibran. I think multiple should just not be calling set (). I'll make these changes along with some of the other stuff I mentioned in #9.

damiankloip’s picture

Status: Needs work » Needs review
StatusFileSize
new15.65 KB
new7.2 KB

A few more changes, based on the feedback above and a couple of other things, such as delegating get() calls to the storage backend if no preloaded item is found in get() and getMultiple().

IMO only keys that are explicitly set() on the backend should be stored for preloading. Thoughts? The get() delegation just covers us a bit better.

Also added views handler to use this same service.

damiankloip’s picture

StatusFileSize
new15.64 KB
new490 bytes

The last submitted patch, 12: 2473205-12.patch, failed testing.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,355 @@
    +/**
    + * A proxy cache backend to preload multiple items.
    + */
    +class MultiBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {
    

    Do you mind added an explanation about the strategies used internally and maybe usecases of that implementation?

  2. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,355 @@
    +    $this->multiCidKey = 'cache_multi_' . $bin;
    

    Should we maybe make the CID overridable as well? This sounds like it could potentially easily collide.

  3. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,355 @@
    +
    +    // @todo Lazy load this?
    +    $this->preloadMultiple();
    

    It feels like its wrong to do it that early

  4. +++ b/core/modules/views/views.services.yml
    @@ -1,61 +1,64 @@
    +  cache.views_plugin:
    +    class: Drupal\Core\Cache\MultiBackend
    +    arguments: ['@cache.discovery', views_plugins]
    

    I guess we can mark this service as not public

damiankloip’s picture

StatusFileSize
new16.52 KB
new4.38 KB

Thanks Daniel!

1. Added. Needs a review.
2. I added a $multi_cid_prefix parameter for the bin. This seems better than a method and having to subclass the cache backend. Although we could have a method too?
3. Yep, removed the @todo and added calls to this in get() and getMultiple(). I also added an internal $preloaded property
4. Done, good idea!

I will work on some tests, as we could maybe do with a separate unit test to test E.g. that things are just preloaded once. I don't think a full unit test is necessary as we have the GenericCacheBackendUnitTestBase implementation that covers a lot.

Also, MutliBackend I am pretty sure is not the best name. So a better name is welcome.

damiankloip’s picture

StatusFileSize
new16.41 KB
new2.86 KB

Couple of other smaller fixes.

dawehner’s picture

A quick review

  1. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -9,6 +9,14 @@
    + * This is useful for cases like Views. Where there are multiple plugin types
    + * that are always needed to execute a view. Instead of loading all cached
    

    Where ... view, that sentence sounds kinda wrong

  2. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -48,26 +56,35 @@ class MultiBackend implements CacheBackendInterface, CacheTagsInvalidatorInterfa
    +  public function __construct(CacheBackendInterface $cache_backend, $bin, $multi_cid_prefix = 'cache_multi') {
    ...
    +    $this->multiCidKey = $multi_cid_prefix . ':' . $bin;
    

    +1

damiankloip’s picture

Working on some tests, will also look at locking for set() calls.

catch’s picture

As well as locking, we need to ensure the list is always additive to avoid race conditions.

So lock -> get existing entry -> add items -> set.

Then no cross-request flip-flopping for different sets of items in the list. This is the same approach that CacheCollector takes. I think that's equally necessary whether we write on every set or at end of request.

And also need to lock when deleting with the same lock name as set uses.

damiankloip’s picture

StatusFileSize
new17.61 KB
new6.32 KB

Agree totally, we need the list to be additive so one request cannot completely overwrite another one.

Here is a new patch with locking similar to cache collector. setting gets cached items, adds, sets again. Delete gets cached items > removes items > sets again.

We still need to decide if we want to move all of this to destruct. We can then just write back if set or setMultiple has been called during that request. It would probably make things more efficient as the current method will lock > load > update > write on every set() call. So with cold caches...

damiankloip’s picture

StatusFileSize
new17.66 KB
new885 bytes

Not sure how those changes got lost.

damiankloip’s picture

StatusFileSize
new17.66 KB
new636 bytes
new319.9 KB

Ugh, and this one. Did some quick xhprofing too, profiling a whole page load - admin/content:

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,419 @@
    +      // @todo Move this to set on destruct?
    +      $this->cacheBackend->set($this->multiCidKey, $multi_cid_items);
    

    so DestructableInterface? Well the question is whether we want to propagate that information rather earlier than later to potential new requests

  2. +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php
    @@ -0,0 +1,419 @@
    +      'created' => round(microtime(TRUE), 3),
    

    What is the idea for that? Maybe some docs?

catch’s picture

I think destructable interface is OK here. In the case of a cache miss, it just means we fetch individual cache items instead of the multiple get. That is probably not much more expensive (or could even be cheaper) than trying to acquire/release the lock several times in a request.

damiankloip’s picture

Yes, I agree. I like your thinking.

webchick’s picture

Just doing some D8 Accelerate house-cleaning. Any update as to where this is at? I don't see a report at https://assoc.drupal.org/d8-accelerate-awarded-grants and there hasn't been movement here in a couple of months.

damiankloip’s picture

catch has a report about this issue I think, I sent him one a while ago, which I think he forwarded on to the DA.

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

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should 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.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should 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.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should 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.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.

smustgrave’s picture

Status: Needs review » Postponed (maintainer needs more info)

Since this hasn't been updated in 7 years wonder if this is still a valid task.

If so think an issue summary update could help + there is feedback requested in #24

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

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should 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.

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

Since there hasn't been a follow up in 11 months and 7 years before that going to close as outdated for now.

If still valid though please reopen.