Merge "Drop index oi_name_archive_name on table oldimage"
[lhc/web/wiklou.git] / includes / libs / objectcache / WANObjectCache.php
index b9753d3..f0a439a 100644 (file)
@@ -44,15 +44,20 @@ use Psr\Log\NullLogger;
  *
  * The simplest purge method is delete().
  *
- * There are two supported ways to handle broadcasted operations:
+ * There are three supported ways to handle broadcasted operations:
  *   - a) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint
- *        that has subscribed listeners on the cache servers applying the cache updates.
+ *         that has subscribed listeners on the cache servers applying the cache updates.
  *   - b) Ignore the 'purge' EventRelayer configuration (default is NullEventRelayer)
- *        and set up mcrouter as the underlying cache backend, using one of the memcached
- *        BagOStuff classes as 'cache'. Use OperationSelectorRoute in the mcrouter settings
- *        to configure 'set' and 'delete' operations to go to all DCs via AllAsyncRoute and
- *        configure other operations to go to the local DC via PoolRoute (for reference,
- *        see https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles).
+ *         and set up mcrouter as the underlying cache backend, using one of the memcached
+ *         BagOStuff classes as 'cache'. Use OperationSelectorRoute in the mcrouter settings
+ *         to configure 'set' and 'delete' operations to go to all DCs via AllAsyncRoute and
+ *         configure other operations to go to the local DC via PoolRoute (for reference,
+ *         see https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles).
+ *   - c) Ignore the 'purge' EventRelayer configuration (default is NullEventRelayer)
+ *         and set up dynomite as cache middleware between the web servers and either
+ *         memcached or redis. This will also broadcast all key setting operations, not just purges,
+ *         which can be useful for cache warming. Writes are eventually consistent via the
+ *         Dynamo replication model (see https://github.com/Netflix/dynomite).
  *
  * Broadcasted operations like delete() and touchCheckKey() are done asynchronously
  * in all datacenters this way, though the local one should likely be near immediate.
@@ -93,11 +98,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** @var mixed[] Temporary warm-up cache */
        private $warmupCache = [];
 
-       /** @var callable Callback used in generating default options in getWithSetCallback() */
-       private $sowSetOptsCallback;
-       /** @var callable Callback used in generating default options in getWithSetCallback() */
-       private $reapSetOptsCallback;
-
        /** Max time expected to pass between delete() and DB commit finishing */
        const MAX_COMMIT_DELAY = 3;
        /** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */
@@ -186,12 +186,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        ? $params['relayers']['purge']
                        : new EventRelayerNull( [] );
                $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
-               $this->sowSetOptsCallback = function () {
-                       return null; // no-op
-               };
-               $this->reapSetOptsCallback = function () {
-                       return []; // no-op
-               };
        }
 
        public function setLogger( LoggerInterface $logger ) {
@@ -286,8 +280,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $checkKeysForAll = [];
                $checkKeysByKey = [];
                $checkKeysFlat = [];
-               foreach ( $checkKeys as $i => $keys ) {
-                       $prefixed = self::prefixCacheKeys( (array)$keys, self::TIME_KEY_PREFIX );
+               foreach ( $checkKeys as $i => $checkKeyGroup ) {
+                       $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::TIME_KEY_PREFIX );
                        $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
                        // Is this check keys for a specific cache key, or for all keys being fetched?
                        if ( is_int( $i ) ) {
@@ -459,7 +453,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
                        // Case B: any long-running transaction; ignore this set()
                        } elseif ( $age > self::MAX_READ_LAG ) {
-                               $this->logger->warning( "Rejected set() for $key due to snapshot lag." );
+                               $this->logger->info( "Rejected set() for $key due to snapshot lag." );
 
                                return true; // no-op the write for being unsafe
                        // Case C: high replication lag; lower TTL instead of ignoring all set()s
@@ -468,7 +462,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                $this->logger->warning( "Lowered set() TTL for $key due to replication lag." );
                        // Case D: medium length request with medium replication lag; ignore this set()
                        } else {
-                               $this->logger->warning( "Rejected set() for $key due to high read lag." );
+                               $this->logger->info( "Rejected set() for $key due to high read lag." );
 
                                return true; // no-op the write for being unsafe
                        }
@@ -1012,9 +1006,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $setOpts = [];
                ++$this->callbackDepth;
                try {
-                       $tag = call_user_func( $this->sowSetOptsCallback );
                        $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
-                       $setOptDefaults = call_user_func( $this->reapSetOptsCallback, $tag );
                } finally {
                        --$this->callbackDepth;
                }
@@ -1039,8 +1031,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        $setOpts['lockTSE'] = $lockTSE;
                        // Use best known "since" timestamp if not provided
                        $setOpts += [ 'since' => $preCallbackTime ];
-                       // Use default "lag" and "pending" values if not set
-                       $setOpts += $setOptDefaults;
                        // Update the cache; this will fail if the key is tombstoned
                        $this->set( $key, $value, $ttl, $setOpts );
                }
@@ -1142,6 +1132,65 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                return $values;
        }
 
+       /**
+        * Locally set a key to expire soon if it is stale based on $purgeTimestamp
+        *
+        * This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
+        * broadcasting in mcrouter setups and also avoids races with new tombstones.
+        *
+        * @param string $key Cache key
+        * @param int $purgeTimestamp UNIX timestamp of purge
+        * @param bool &$isStale Whether the key is stale
+        * @return bool Success
+        * @since 1.28
+        */
+       public function reap( $key, $purgeTimestamp, &$isStale = false ) {
+               $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
+               $wrapped = $this->cache->get( self::VALUE_KEY_PREFIX . $key );
+               if ( is_array( $wrapped ) && $wrapped[self::FLD_TIME] < $minAsOf ) {
+                       $isStale = true;
+                       $this->logger->warning( "Reaping stale value key '$key'." );
+                       $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
+                       $ok = $this->cache->changeTTL( self::VALUE_KEY_PREFIX . $key, $ttlReap );
+                       if ( !$ok ) {
+                               $this->logger->error( "Could not complete reap of key '$key'." );
+                       }
+
+                       return $ok;
+               }
+
+               $isStale = false;
+
+               return true;
+       }
+
+       /**
+        * Locally set a "check" key to expire soon if it is stale based on $purgeTimestamp
+        *
+        * @param string $key Cache key
+        * @param int $purgeTimestamp UNIX timestamp of purge
+        * @param bool &$isStale Whether the key is stale
+        * @return bool Success
+        * @since 1.28
+        */
+       public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
+               $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
+               if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
+                       $isStale = true;
+                       $this->logger->warning( "Reaping stale check key '$key'." );
+                       $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, 1 );
+                       if ( !$ok ) {
+                               $this->logger->error( "Could not complete reap of check key '$key'." );
+                       }
+
+                       return $ok;
+               }
+
+               $isStale = false;
+
+               return false;
+       }
+
        /**
         * @see BagOStuff::makeKey()
         * @param string ... Key component
@@ -1267,22 +1316,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
        }
 
-       /**
-        * Set the callbacks that provide the fallback values for cache set options
-        *
-        * The $reap callback returns default values to use for the "lag", "since", and "pending"
-        * options used by WANObjectCache::set(). It takes the ID from $sow as the sole parameter.
-        * An empty array should be returned if there is no usage to base the return value on.
-        *
-        * @param callable $sow Function that starts recording and returns an ID
-        * @param callable $reap Function that takes an ID, stops recording, and returns the options
-        * @since 1.28
-        */
-       public function setDefaultCacheSetOptionCallbacks( callable $sow, callable $reap ) {
-               $this->sowSetOptsCallback = $sow;
-               $this->reapSetOptsCallback = $reap;
-       }
-
        /**
         * Do the actual async bus purge of a key
         *
@@ -1389,7 +1422,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        return false;
                }
 
-               // Lifecycle is: new, ramp-up refresh chance, full refresh chance
+               // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
+               // Note that the "expected # of refreshes" for the ramp-up time range is half of what it
+               // would be if P(refresh) was at its full value during that time range.
                $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
                // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
                // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1