From 54991403ff0b83b961e47e3b275416092781c99b Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Sat, 3 Oct 2015 00:43:40 -0700 Subject: [PATCH] objectcache: Add BagOStuff::READ_VERIFIED flag to get() * This lets multiwrite backends upgrade cached items to higher tiers using UPGRADE_TTL. * This is useful for memcached/sql tiers or apc/memcached. Change-Id: I34b30ce8b54f8de36429d48c80f6768aed310272 --- includes/libs/objectcache/BagOStuff.php | 20 ++++++--- includes/objectcache/MultiWriteBagOStuff.php | 47 +++++++++++++++----- includes/parser/ParserCache.php | 7 ++- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index ddbe8eaa3e..31bffd4447 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -62,6 +62,7 @@ abstract class BagOStuff implements LoggerAwareInterface { /** Bitfield constants for get()/getMulti() */ const READ_LATEST = 1; // use latest data for replicated stores + const READ_VERIFIED = 2; // promise that caller can tell when keys are stale public function __construct( array $params = array() ) { if ( isset( $params['logger'] ) ) { @@ -87,16 +88,24 @@ abstract class BagOStuff implements LoggerAwareInterface { } /** - * Get an item with the given key. Returns false if it does not exist. + * Get an item with the given key + * + * If the key includes a determistic input hash (e.g. the key can only have + * the correct value) or complete staleness checks are handled by the caller + * (e.g. nothing relies on the TTL), then the READ_VERIFIED flag should be set. + * This lets tiered backends know they can safely upgrade a cached value to + * higher tiers using standard TTLs. + * * @param string $key * @param mixed $casToken [optional] - * @param integer $flags Bitfield; supports READ_LATEST [optional] - * @return mixed Returns false on failure + * @param integer $flags Bitfield of BagOStuff::READ_* constants [optional] + * @return mixed Returns false on failure and if the item does not exist */ abstract public function get( $key, &$casToken = null, $flags = 0 ); /** - * Set an item. + * Set an item + * * @param string $key * @param mixed $value * @param int $exptime Either an interval in seconds or a unix timestamp for expiry @@ -105,7 +114,8 @@ abstract class BagOStuff implements LoggerAwareInterface { abstract public function set( $key, $value, $exptime = 0 ); /** - * Delete an item. + * Delete an item + * * @param string $key * @return bool True if the item was deleted or not found, false on failure */ diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php index cb3754a8f4..b69077270e 100644 --- a/includes/objectcache/MultiWriteBagOStuff.php +++ b/includes/objectcache/MultiWriteBagOStuff.php @@ -34,6 +34,11 @@ class MultiWriteBagOStuff extends BagOStuff { /** @var bool Use async secondary writes */ protected $asyncWrites = false; + /** Idiom for "write to all backends" */ + const ALL = INF; + + const UPGRADE_TTL = 3600; // TTL when a key is copied to a higher cache tier + /** * $params include: * - caches: This should have a numbered array of cache parameter @@ -79,17 +84,28 @@ class MultiWriteBagOStuff extends BagOStuff { * @param bool $debug */ public function setDebug( $debug ) { - $this->doWrite( 'setDebug', $debug ); + $this->doWrite( self::ALL, 'setDebug', $debug ); } public function get( $key, &$casToken = null, $flags = 0 ) { + $misses = 0; // number backends checked + $value = false; foreach ( $this->caches as $cache ) { $value = $cache->get( $key, $casToken, $flags ); if ( $value !== false ) { - return $value; + break; } + ++$misses; + } + + if ( $value !== false + && $misses > 0 + && ( $flags & self::READ_VERIFIED ) == self::READ_VERIFIED + ) { + $this->doWrite( $misses, 'set', $key, $value, self::UPGRADE_TTL ); } - return false; + + return $value; } /** @@ -99,7 +115,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool */ public function set( $key, $value, $exptime = 0 ) { - return $this->doWrite( 'set', $key, $value, $exptime ); + return $this->doWrite( self::ALL, 'set', $key, $value, $exptime ); } /** @@ -107,7 +123,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool */ public function delete( $key ) { - return $this->doWrite( 'delete', $key ); + return $this->doWrite( self::ALL, 'delete', $key ); } /** @@ -117,7 +133,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool */ public function add( $key, $value, $exptime = 0 ) { - return $this->doWrite( 'add', $key, $value, $exptime ); + return $this->doWrite( self::ALL, 'add', $key, $value, $exptime ); } /** @@ -126,7 +142,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool|null */ public function incr( $key, $value = 1 ) { - return $this->doWrite( 'incr', $key, $value ); + return $this->doWrite( self::ALL, 'incr', $key, $value ); } /** @@ -135,7 +151,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool */ public function decr( $key, $value = 1 ) { - return $this->doWrite( 'decr', $key, $value ); + return $this->doWrite( self::ALL, 'decr', $key, $value ); } /** @@ -166,7 +182,7 @@ class MultiWriteBagOStuff extends BagOStuff { * @return bool Success */ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) { - return $this->doWrite( 'merge', $key, $callback, $exptime ); + return $this->doWrite( self::ALL, 'merge', $key, $callback, $exptime ); } public function getLastError() { @@ -178,15 +194,22 @@ class MultiWriteBagOStuff extends BagOStuff { } /** + * Apply a write method to the first $count backing caches + * + * @param integer $count * @param string $method + * @param mixed ... * @return bool */ - protected function doWrite( $method /*, ... */ ) { + protected function doWrite( $count, $method /*, ... */ ) { $ret = true; - $args = func_get_args(); - array_shift( $args ); + $args = array_slice( func_get_args(), 2 ); foreach ( $this->caches as $i => $cache ) { + if ( $i >= $count ) { + break; // ignore the lower tiers + } + if ( $i == 0 || !$this->asyncWrites ) { // First store or in sync mode: write now and get result if ( !call_user_func_array( array( $cache, $method ), $args ) ) { diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php index c03b5c2112..0eba166b64 100644 --- a/includes/parser/ParserCache.php +++ b/includes/parser/ParserCache.php @@ -139,7 +139,9 @@ class ParserCache { } // Determine the options which affect this article - $optionsKey = $this->mMemc->get( $this->getOptionsKey( $article ) ); + $casToken = null; + $optionsKey = $this->mMemc->get( + $this->getOptionsKey( $article ), $casToken, BagOStuff::READ_VERIFIED ); if ( $optionsKey instanceof CacheTime ) { if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) { wfIncrStats( "pcache.miss.expired" ); @@ -198,7 +200,8 @@ class ParserCache { return false; } - $value = $this->mMemc->get( $parserOutputKey ); + $casToken = null; + $value = $this->mMemc->get( $parserOutputKey, $casToken, BagOStuff::READ_VERIFIED ); if ( !$value ) { wfDebug( "ParserOutput cache miss.\n" ); wfIncrStats( "pcache.miss.absent" ); -- 2.20.1