Commit e4b67a08 authored by Jeremy's avatar Jeremy

Issue #2377587: by dawehner, Fabianx, Jeremy: disable stampede protection for...

Issue #2377587: by dawehner, Fabianx, Jeremy: disable stampede protection for specific cache bins/cids
parent 1908b194
......@@ -122,8 +122,36 @@ To avoid lock stampedes, it is important that you enable the memacache lock
implementation when enabling stampede protection -- enabling stampede protection
without enabling the Memache lock implementation can cause worse performance.
Only change the following values if you're sure you know what you're doing,
which requires reading the memcachie.inc code.
Memcache stampede protection is primarily designed to benefit the following
caching pattern: a miss on a cache_get() for a specific cid is immediately
followed by a cache_set() for that cid. Of course, this is not the only caching
pattern used in Drupal, so stampede protection can be selectively disabled for
optimal performance. For example, a cache miss in Drupal core's
module_implements() won't execute a cache_set until drupal_page_footer()
calls module_implements_write_cache() which can occur much later in page
generation. To avoid long hanging locks, stampede protection should be
disabled for these delayed caching patterns.
Memcache stampede protection can be disabled for entire bins, specific cid's in
specific bins, or cid's starting with a specific prefix in specific bins. For
example:
$conf['memcache_stampede_protection_ignore'] = array(
// Ignore stampede protection for the entire 'cache_example' bin.
'cache_example',
// Ignore some cids in 'cache_bootstrap'.
'cache_bootstrap' => array(
'module_implements',
'variables'
),
// Ignore all cids in the 'cache' bin starting with 'i18n:string:'
'cache' => array(
'i18n:string:*',
),
);
Only change the following stampede protection tunables if you're sure you know
what you're doing, which requires first reading the memcache.inc code.
The value passed to lock_acquire, defaults to '15':
$conf['memcache_stampede_semaphore'] = 15;
......
......@@ -103,7 +103,7 @@ class MemCacheDrupal implements DrupalCacheInterface {
if (variable_get('memcache_stampede_protection', FALSE)) {
// The process that acquires the lock will get a cache miss, all
// others will get a cache hit.
if ($this->lockInit() && lock_acquire("memcache_$cid:$this->bin", variable_get('memcache_stampede_semaphore', 15))) {
if ($this->lockInit() && $this->stampedeProtected($cid) && lock_acquire("memcache_$cid:$this->bin", variable_get('memcache_stampede_semaphore', 15))) {
$cache = FALSE;
}
}
......@@ -144,7 +144,7 @@ class MemCacheDrupal implements DrupalCacheInterface {
// On cache misses, attempt to avoid stampedes when the
// memcache_stampede_protection variable is enabled.
if (!$cache) {
if (variable_get('memcache_stampede_protection', FALSE) && $this->lockInit() && !lock_acquire("memcache_$cid:$this->bin", variable_get('memcache_stampede_semaphore', 15))) {
if (variable_get('memcache_stampede_protection', FALSE) && $this->lockInit() && $this->stampedeProtected($cid) && !lock_acquire("memcache_$cid:$this->bin", variable_get('memcache_stampede_semaphore', 15))) {
// Prevent any single request from waiting more than three times due to
// stampede protection. By default this is a maximum total wait of 15
// seconds. This accounts for two possibilities - a cache and lock miss
......@@ -479,6 +479,64 @@ class MemCacheDrupal implements DrupalCacheInterface {
return TRUE;
}
/**
* Determines whether stampede protection is enabled for a given bin/cid.
*
* Memcache stampede protection is primarily designed to benefit the following
* caching pattern: a miss on a cache_get for a specific cid is immediately
* followed by a cache_set for that cid. In cases where this pattern is not
* followed, stampede protection can be disabled to avoid long hanging locks.
* For example, a cache miss in Drupal core's module_implements() won't
* execute a cache_set until drupal_page_footer() calls
* module_implements_write_cache() which can occur much later in page
* generation.
*
* @param string $cid
* The cache id of the data to retrieve.
*
* @return bool
* Returns TRUE if stampede protection is enabled for that particular cache
* bin/cid, otherwise FALSE.
*/
protected function stampedeProtected($cid) {
$ignore_settings = variable_get('memcache_stampede_protection_ignore', array(
// Disable stampede protection for specific cids in 'cache_bootstrap'.
'cache_bootstrap' => array(
// The module_implements cache is written after finishing the request.
'module_implements',
// Variables have their own lock protection.
'variables'
),
// Disable stampede protection for cid prefix in 'cache'.
'cache' => array(
// I18n uses a class destructor to write the cache.
'i18n:string:*',
),
));
// Support ignoring an entire bin.
if (in_array($this->bin, $ignore_settings)) {
return FALSE;
}
// Support ignoring by cids.
if (isset($ignore_settings[$this->bin])) {
// Support ignoring specific cids.
if (in_array($cid, $ignore_settings[$this->bin])) {
return FALSE;
}
// Support ignoring cids starting with a suffix.
foreach ($ignore_settings[$this->bin] as $ignore) {
$split = explode('*', $ignore);
if (count($split) > 1 && strpos($cid, $split[0]) === 0) {
return FALSE;
}
}
}
return TRUE;
}
/**
* Helper function to reload variables.
*
......
......@@ -686,6 +686,77 @@ class MemCacheClearCase extends MemcacheTestCase {
}
/**
* Tests memcache stampede protection.
*/
class MemCacheStampedeProtection extends MemcacheTestCase {
public static function getInfo() {
return array(
'name' => 'Memcache stampede protection',
'description' => 'Tests memcache stampede protection.',
'group' => 'Memcache',
);
}
/**
* Tests the opt out functionality of stampede protection using a unit test.
*/
public function testStampedeProtectionIgnoringUnit() {
global $conf;
// Setup a new test bin, to be able to override the used class.
$conf['cache_flush_test_bin'] = 0;
$conf['cache_class_test_bin'] = 'MockMemCacheDrupal';
$conf['cache_flush_test_bin_2'] = 0;
$conf['cache_class_test_bin_2'] = 'MockMemCacheDrupal';
// Setup stampede protection.
$conf['memcache_stampede_protection'] = TRUE;
$conf['memcache_stampede_protection_ignore'] = array(
'test_bin',
'test_bin_2' => array(
'cid_no_prefix',
'cid_with_prefix:*',
),
);
// Ensure the mock object is used.
/** @var \MockMemCacheDrupal $cache_object_bin */
$cache_object_bin = _cache_get_object('test_bin');
$this->assertEqual(get_class($cache_object_bin), 'MockMemCacheDrupal');
/** @var \MockMemCacheDrupal $cache_object_bin2 */
$cache_object_bin2 = _cache_get_object('test_bin_2');
$this->assertEqual(get_class($cache_object_bin2), 'MockMemCacheDrupal');
// Test ignoring of an entire bin.
$this->assertFalse($cache_object_bin->stampedeProtected('test_cid'), t('Disable stampede protection for cid contained in a disabled bin.'));
$this->assertFalse($cache_object_bin->stampedeProtected('cid_no_prefix'), t('Disable stampede protection for cid without prefix in a disabled bin.'));
$this->assertFalse($cache_object_bin->stampedeProtected('cid_with_prefix:example'), t('Disable stampede protection for cid with prefix in a disabled bin.'));
// Test ignoring of specific CIDs.
$this->assertTrue($cache_object_bin2->stampedeProtected('test_cid'), t('Don\'t disable stampede protection for a specific non-matching cid.'));
$this->assertFalse($cache_object_bin2->stampedeProtected('cid_no_prefix'), t('Disable stampede protection for a specific cid.'));
$this->assertFalse($cache_object_bin2->stampedeProtected('cid_with_prefix:example'), t('Disable stampede protection for a specific cid with disabled prefix.'));
$this->assertTrue($cache_object_bin2->stampedeProtected('cid_with_other_prefix:example'), t('Don\'t disable stampede protection for a specific cid with a different prefix.'));
}
}
include_once dirname(__DIR__) . '/memcache.inc';
/**
* Wraps the MemCacheDrupal class in order to be able to call a protected method.
*/
class MockMemCacheDrupal extends MemCacheDrupal {
public function stampedeProtected($cid) {
return parent::stampedeProtected($cid);
}
}
/**
* Test some real world cache scenarios with default modules.
*
......
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