X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=e7c4edbee6e5695508efc39d1543e0b100d75898;hb=9e831f0b4d5dd25144e1841eb46d632d2697de63;hp=adb2899e58ab320e802688961a740a203c78a663;hpb=d908ac0d44a5c41eac292faaed68b7d39a6165f5;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index adb2899e58..e7c4edbee6 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -88,6 +88,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { /** @var int ERR_* constant for the "last error" registry */ protected $lastRelayError = self::ERR_NONE; + /** @var mixed[] Temporary warm-up cache */ + private $warmupCache = []; + /** 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() */ @@ -284,7 +287,14 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } // Fetch all of the raw values - $wrappedValues = $this->cache->getMulti( array_merge( $valueKeys, $checkKeysFlat ) ); + $keysGet = array_merge( $valueKeys, $checkKeysFlat ); + if ( $this->warmupCache ) { + $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) ); + $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch + } else { + $wrappedValues = []; + } + $wrappedValues += $this->cache->getMulti( $keysGet ); // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic) $now = microtime( true ); @@ -404,6 +414,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * with this TTL and flag it as stale. This is only useful if the reads for * this key use getWithSetCallback() with "lockTSE" set. * Default: WANObjectCache::TSE_NONE + * - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti() + * methods return such stale values with a $curTTL of 0, and getWithSetCallback() + * will call the regeneration callback in such cases, passing in the old value + * and its as-of time to the callback. This is useful if adaptiveTTL() is used + * on the old value's as-of time when it is verified as still being correct. + * Default: 0. + * @note Options added in 1.28: staleTTL * @return bool Success */ final public function set( $key, $value, $ttl = 0, array $opts = [] ) { @@ -411,6 +428,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE; $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0; $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0; + $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : 0; // Do not cache potentially uncommitted data as it might get rolled back if ( !empty( $opts['pending'] ) ) { @@ -452,7 +470,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { : $wrapped; }; - return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl, 1 ); + return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 ); } /** @@ -823,6 +841,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * - ageNew: Consider popularity refreshes only once a key reaches this age in seconds. * Default: WANObjectCache::AGE_NEW. * @return mixed Value found or written to the key + * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf * @note Callable type hints are not used to avoid class-autoloading */ final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) { @@ -920,12 +939,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value $value = $cValue; // return value - // Determine if a regeneration is desired + $preCallbackTime = microtime( true ); + // Determine if a cached value regeneration is needed or desired if ( $value !== false && $curTTL > 0 && $this->isValid( $value, $versioned, $asOf, $minTime ) && !$this->worthRefreshExpiring( $curTTL, $lowTTL ) - && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow ) + && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime ) ) { return $value; } @@ -994,8 +1014,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } if ( $value !== false && $ttl >= 0 ) { - // Update the cache; this will fail if the key is tombstoned $setOpts['lockTSE'] = $lockTSE; + // Use best known "since" timestamp if not provided + $setOpts += [ 'since' => $preCallbackTime ]; + // Update the cache; this will fail if the key is tombstoned $this->set( $key, $value, $ttl, $setOpts ); } @@ -1007,6 +1029,95 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return $value; } + /** + * Method to fetch/regenerate multiple cache keys at once + * + * This works the same as getWithSetCallback() except: + * - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys() + * - b) The $callback argument expects a callback taking the following arguments: + * - $id: ID of an entity to query + * - $oldValue : the prior cache value or false if none was present + * - &$ttl : a reference to the new value TTL in seconds + * - &$setOpts : a reference to options for set() which can be altered + * - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present + * Aside from the additional $id argument, the other arguments function the same + * way they do in getWithSetCallback(). + * - c) The return value is a map of (cache key => value) in the order of $keyedIds + * + * @see WANObjectCache::getWithSetCallback() + * + * Example usage: + * @code + * $rows = $cache->getMultiWithSetCallback( + * // Map of cache keys to entity IDs + * $cache->makeMultiKeys( + * $this->fileVersionIds(), + * function ( $id, WANObjectCache $cache ) { + * return $cache->makeKey( 'file-version', $id ); + * } + * ), + * // Time-to-live (in seconds) + * $cache::TTL_DAY, + * // Function that derives the new key value + * return function ( $id, $oldValue, &$ttl, array &$setOpts ) { + * $dbr = wfGetDB( DB_REPLICA ); + * // Account for any snapshot/replica DB lag + * $setOpts += Database::getCacheSetOptions( $dbr ); + * + * // Load the row for this file + * $row = $dbr->selectRow( 'file', '*', [ 'id' => $id ], __METHOD__ ); + * + * return $row ? (array)$row : false; + * }, + * [ + * // Process cache for 30 seconds + * 'pcTTL' => 30, + * // Use a dedicated 500 item cache (initialized on-the-fly) + * 'pcGroup' => 'file-versions:500' + * ] + * ); + * $files = array_map( [ __CLASS__, 'newFromRow' ], $rows ); + * @endcode + * + * @param ArrayIterator $keyedIds Result of WANObjectCache::makeMultiKeys() + * @param integer $ttl Seconds to live for key updates + * @param callable $callback Callback the yields entity regeneration callbacks + * @param array $opts Options map + * @return array Map of (cache key => value) in the same order as $keyedIds + * @since 1.28 + */ + final public function getMultiWithSetCallback( + ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = [] + ) { + $keysWarmUp = iterator_to_array( $keyedIds, true ); + $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : []; + foreach ( $checkKeys as $i => $checkKeyOrKeys ) { + if ( is_int( $i ) ) { + $keysWarmUp[] = $checkKeyOrKeys; + } else { + $keysWarmUp = array_merge( $keysWarmUp, $checkKeyOrKeys ); + } + } + + $this->warmupCache = $this->cache->getMulti( $keysWarmUp ); + $this->warmupCache += array_fill_keys( $keysWarmUp, false ); + + // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback + $id = null; + $func = function ( $oldValue, &$ttl, array $setOpts, $oldAsOf ) use ( $callback, &$id ) { + return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf ); + }; + + $values = []; + foreach ( $keyedIds as $key => $id ) { + $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts ); + } + + $this->warmupCache = []; + + return $values; + } + /** * @see BagOStuff::makeKey() * @param string ... Key component @@ -1027,6 +1138,21 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() ); } + /** + * @param array $entities List of entity IDs + * @param callable $keyFunc Callback yielding a key from (entity ID, this WANObjectCache) + * @return ArrayIterator Iterator yielding (cache key => entity ID) in $entities order + * @since 1.28 + */ + public function makeMultiKeys( array $entities, callable $keyFunc ) { + $map = []; + foreach ( $entities as $entity ) { + $map[$keyFunc( $entity, $this )] = $entity; + } + + return new ArrayIterator( $map ); + } + /** * Get the "last error" registered; clearLastError() should be called manually * @return int ERR_* class constant for the "last error" registry @@ -1213,10 +1339,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @param float $asOf UNIX timestamp of the value * @param integer $ageNew Age of key when this might recommend refreshing (seconds) * @param integer $timeTillRefresh Age of key when it should be refreshed if popular (seconds) + * @param float $now The current UNIX timestamp * @return bool */ - protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh ) { - $age = microtime( true ) - $asOf; + protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) { + $age = $now - $asOf; $timeOld = $age - $ageNew; if ( $timeOld <= 0 ) { return false;