Commit 00b288a4 authored by bdragon's avatar bdragon Committed by bdragon
Browse files

Issue #2989601 by bdragon, damiankloip, Fabianx, catch: Support for cache tags natively in memcache

parent b49c96c9
## IMPORTANT NOTE ##
This file contains installation instructions for the 8.x-1.x version of the
This file contains installation instructions for the 8.x-2.x version of the
Drupal Memcache module. Configuration differs between 8.x and 7.x versions
of the module, so be sure to follow the 7.x instructions if you are configuring
the 7.x-1.x version of this module!
......@@ -91,7 +91,7 @@ config in settings.php:
$settings['memcache']['key_prefix'] = 'something_unique';
### Key Hash Algorithm
### Key Hash Algorithm ###
Note: if the length of your prefix + key + bin combine to be more than 250
characters, they will be automatically hashed. Memcache only supports key
......@@ -135,9 +135,11 @@ services:
class: Drupal\Core\Lock\LockBackendInterface
factory: memcache.lock.factory:get
## Cache Container on bootstrap ##
## Cache Container on bootstrap (with cache tags on database) ##
By default Drupal starts the cache_container on the database, in order to override that you can use the following code on your settings.php file. Make sure that the $class_load->addPsr4 is poiting to the right location of memcache (on this case modules/contrib/memcache/src)
In this mode, the database is still bootstrapped so that cache tag invalidation can be handled. If you want to avoid database bootstrap, see the container definition in the next section instead.
$memcache_exists = class_exists('Memcache', FALSE);
$memcached_exists = class_exists('Memcached', FALSE);
if ($memcache_exists || $memcached_exists) {
......@@ -165,7 +167,7 @@ if ($memcache_exists || $memcached_exists) {
'arguments' => ['@memcache.settings']
],
'memcache.backend.cache.container' => [
'class' => 'Drupal\memcache\DrupalMemcacheFactory',
'class' => 'Drupal\memcache\DrupalMemcacheInterface',
'factory' => ['@memcache.backend.cache.factory', 'get'],
'arguments' => ['container'],
],
......@@ -181,6 +183,59 @@ if ($memcache_exists || $memcached_exists) {
];
}
## Cache Container on bootstrap (pure memcache) ##
By default Drupal starts the cache_container on the database, in order to override that you can use the following code on your settings.php file. Make sure that the $class_load->addPsr4 is poiting to the right location of memcache (on this case modules/contrib/memcache/src)
For this mode to work correctly, you must be using the overridden cache_tags.invalidator.checksum service.
See example.services.yml for the corresponding configuration.
$memcache_exists = class_exists('Memcache', FALSE);
$memcached_exists = class_exists('Memcached', FALSE);
if ($memcache_exists || $memcached_exists) {
$class_loader->addPsr4('Drupal\\memcache\\', 'modules/contrib/memcache/src');
// Define custom bootstrap container definition to use Memcache for cache.container.
$settings['bootstrap_container_definition'] = [
'parameters' => [],
'services' => [
# Dependencies.
'settings' => [
'class' => 'Drupal\Core\Site\Settings',
'factory' => 'Drupal\Core\Site\Settings::getInstance',
],
'memcache.settings' => [
'class' => 'Drupal\memcache\MemcacheSettings',
'arguments' => ['@settings'],
],
'memcache.factory' => [
'class' => 'Drupal\memcache\Driver\MemcacheDriverFactory',
'arguments' => ['@memcache.settings'],
],
'memcache.backend.cache.container' => [
'class' => 'Drupal\memcache\DrupalMemcacheInterface',
'factory' => ['@memcache.factory', 'get'],
# Actual cache bin to use for the container cache.
'arguments' => ['container'],
],
'memcache.backend.timestamp.invalidator' => [
'class' => 'Drupal\memcache\Invalidator\MemcacheTimestampInvalidator',
# Remember to use the same bin as the memcache.timestamp.bin service!
# Adjust tolerance factor as appropriate when not running memcache on localhost.
'arguments' => ['@memcache.factory', 'memcache_invalidation_timestamps', 0.001],
],
# Define a custom cache tags invalidator for the bootstrap container.
'cache_tags_provider.container' => [
'class' => 'Drupal\memcache\Cache\TimestampCacheTagsChecksum',
'arguments' => ['@memcache.backend.timestamp.invalidator'],
],
'cache.container' => [
'class' => 'Drupal\memcache\MemcacheBackend',
'arguments' => ['container', '@memcache.backend.cache.container', '@cache_tags_provider.container'],
],
],
];
}
## TROUBLESHOOTING ##
PROBLEM:
......
# This file contains example services overrides.
#
# Enable with this line in settings.php
# $settings['container_yamls'][] = 'modules/memcache/example.services.yml';
#
# Or copy & paste the desired services into sites/default/services.yml.
#
# Note that the memcache module must be enabled for this to work.
services:
# Timestamp invalidation service used for invalidation logic.
memcache.timestamp.invalidator:
class: Drupal\memcache\Invalidator\MemcacheTimestampInvalidator
# Remember to use the same bin as the bootstrap container if you are using it!
# Adjust tolerance factor as appropriate when not running memcache on localhost.
arguments: ['@memcache.factory', 'memcache_invalidation_timestamps', 0.001]
# Cache tag checksum backend. Used by memcache and most other cache backends
# to deal with cache tag invalidations.
cache_tags.invalidator.checksum:
class: Drupal\memcache\Cache\TimestampCacheTagsChecksum
arguments: ['@memcache.timestamp.invalidator']
tags:
- { name: cache_tags_invalidator }
# Replaces the default lock backend with a memcache implementation.
lock:
class: Drupal\Core\Lock\LockBackendInterface
factory: ['@memcache.lock.factory', get]
<?php
/**
* @file
* Contains \Drupal\memcache\TimestampCacheTagsChecksum.
*/
namespace Drupal\memcache\Cache;
use Drupal\Core\Cache\CacheTagsChecksumInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\memcache\Invalidator\TimestampInvalidatorInterface;
/**
* Cache tags invalidations checksum implementation that uses timestamp invalidation.
*/
class TimestampCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
/**
* The timestamp invalidator object.
*
* @var TimestampInvalidatorInterface
*/
protected $invalidator;
/**
* Contains already loaded cache invalidations from the backend.
*
* @var array
*/
protected $tagCache = [];
/**
* A list of tags that have already been invalidated in this request.
*
* Used to prevent the invalidation of the same cache tag multiple times.
*
* @var array
*/
protected $invalidatedTags = [];
/**
* Constructs a TimestampCacheTagsChecksum object.
*
* @param TimestampInvalidatorInterface $invalidator
*/
public function __construct(TimestampInvalidatorInterface $invalidator) {
$this->invalidator = $invalidator;
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
foreach ($tags as $tag) {
// @todo Revisit this behavior and determine a better way to handle.
// Only invalidate tags once per request unless they are written again.
if (isset($this->invalidatedTags[$tag])) {
continue;
}
$this->invalidatedTags[$tag] = TRUE;
$this->tagCache[$tag] = $this->invalidator->invalidateTimestamp($tag);
}
}
/**
* {@inheritdoc}
*/
public function getCurrentChecksum(array $tags) {
// @todo Revist the invalidatedTags hack.
// Remove tags that were already invalidated during this request from the
// static caches so that another invalidation can occur later in the same
// request. Without that, written cache items would not be invalidated
// correctly.
foreach ($tags as $tag) {
unset($this->invalidatedTags[$tag]);
}
// Taking the minimum of the current timestamp and the checksum is used to
// ensure that items that are not valid yet are identified properly as not
// valid. The checksum will change continuously until the item is valid,
// at which point the checksum will match and freeze at that value.
return min($this->invalidator->getCurrentTimestamp(), $this->calculateChecksum($tags));
}
/**
* {@inheritdoc}
*/
public function isValid($checksum, array $tags) {
if (empty($tags)) {
// If there weren't any tags, the checksum should always be 0 or FALSE.
return $checksum == 0;
}
return $checksum == $this->calculateChecksum($tags);
}
/**
* Calculates the current checksum for a given set of tags.
*
* @param array $tags
* The array of tags to calculate the checksum for.
*
* @return int
* The calculated checksum.
*/
protected function calculateChecksum(array $tags) {
$query_tags = array_diff($tags, array_keys($this->tagCache));
if ($query_tags) {
$backend_tags = $this->invalidator->getLastInvalidationTimestamps($query_tags);
$this->tagCache += $backend_tags;
$invalid = array_diff($query_tags, array_keys($backend_tags));
if (!empty($invalid)) {
// Invalidate any missing tags now. This is necessary because we cannot
// zero-optimize our tag list -- we can't tell the difference between
// a tag that has never been invalidated and a tag that was
// garbage-collected by the backend!
//
// This behavioral difference is the main change that allows us to use
// an unreliable backend to track cache tag invalidation.
//
// Invalidating the tag will cause it to start being tracked, so it can
// be matched against the checksums stored on items.
// All items cached after that point with the tag will end up with
// a valid checksum, and all items cached before that point with the tag
// will have an invalid checksum, because missing invalidations will
// keep moving forward in time as they get garbage collected and are
// re-invalidated.
//
// The main effect of all this is that a tag going missing
// will automatically cause the cache items tagged with it to no longer
// have the correct checksum.
foreach ($invalid as $invalid_tag) {
$this->invalidator->invalidateTimestamp($invalid_tag);
}
}
}
// The checksum is equal to the *most recent* invalidation of an applicable tag.
// If the item is untagged, the checksum is always 0.
return max([0] + array_intersect_key($this->tagCache, array_flip($tags)));
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->tagCache = [];
$this->invalidatedTags = [];
}
}
......@@ -55,7 +55,7 @@ class MemcacheDriverFactory {
protected $failedConnectionCache = [];
/**
* Constructs a DrupalMemcacheFactory object.
* Constructs a MemcacheDriverFactory object.
*
* @param \Drupal\memcache\MemcacheSettings $settings
*/
......@@ -74,7 +74,7 @@ class MemcacheDriverFactory {
* @param bool $flush
* Rebuild the bin/server/cache mapping.
*
* @return \Drupal\memcache\DrupalMemcacheInterface
* @return \Drupal\memcache\DrupalMemcacheInterface | bool
* A Memcache object.
*/
public function get($bin = NULL, $flush = FALSE) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment