objectcache: Add BagOStuff::READ_VERIFIED flag to get()
authorAaron Schulz <aschulz@wikimedia.org>
Sat, 3 Oct 2015 07:43:40 +0000 (00:43 -0700)
committerTimo Tijhof <krinklemail@gmail.com>
Mon, 5 Oct 2015 23:03:00 +0000 (16:03 -0700)
* 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
includes/objectcache/MultiWriteBagOStuff.php
includes/parser/ParserCache.php

index ddbe8ea..31bffd4 100644 (file)
@@ -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
         */
index cb3754a..b690772 100644 (file)
@@ -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 ) ) {
index c03b5c2..0eba166 100644 (file)
@@ -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" );