X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=4f90c7d77d06bde16b57abc20f77735425fb8137;hp=cb1be955863f977b9fecdfe883983a387fbc5672;hb=a9007e8baf802f0f57d095e3bb4ad201c98c0cb3;hpb=fb742a3a8eb951c91544e70fe09b63b1bac6aec5 diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index cb1be95586..4f90c7d77d 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -17,7 +17,6 @@ * * @file * @ingroup Cache - * @author Aaron Schulz */ use Psr\Log\LoggerAwareInterface; @@ -202,8 +201,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { public static function newEmpty() { return new self( [ 'cache' => new EmptyBagOStuff(), - 'pool' => 'empty', - 'relayer' => new EventRelayerNull( [] ) + 'pool' => 'empty' ] ); } @@ -242,7 +240,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * That method has cache slam avoiding features for hot/expensive keys. * * @param string $key Cache key - * @param mixed $curTTL Approximate TTL left on the key if present/tombstoned [returned] + * @param mixed &$curTTL Approximate TTL left on the key if present/tombstoned [returned] * @param array $checkKeys List of "check" keys * @param float &$asOf UNIX timestamp of cached value; null on failure [returned] * @return mixed Value of cache key or false on failure @@ -263,11 +261,11 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @see WANObjectCache::get() * * @param array $keys List of cache keys - * @param array $curTTLs Map of (key => approximate TTL left) for existing keys [returned] + * @param array &$curTTLs Map of (key => approximate TTL left) for existing keys [returned] * @param array $checkKeys List of check keys to apply to all $keys. May also apply "check" * keys to specific cache keys only by using cache keys as keys in the $checkKeys array. * @param float[] &$asOfs Map of (key => UNIX timestamp of cached value; null on failure) - * @return array Map of (key => value) for keys that exist + * @return array Map of (key => value) for keys that exist and are not tombstoned */ final public function getMulti( array $keys, &$curTTLs = [], array $checkKeys = [], array &$asOfs = [] @@ -1049,7 +1047,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } /** - * Method to fetch/regenerate multiple cache keys at once + * Method to fetch multiple cache keys at once with regeneration * * This works the same as getWithSetCallback() except: * - a) The $keys argument expects the result of WANObjectCache::makeMultiKeys() @@ -1064,6 +1062,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * - c) The return value is a map of (cache key => value) in the order of $keyedIds * * @see WANObjectCache::getWithSetCallback() + * @see WANObjectCache::getMultiWithUnionSetCallback() * * Example usage: * @code @@ -1108,39 +1107,145 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { final public function getMultiWithSetCallback( ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = [] ) { + $valueKeys = array_keys( $keyedIds->getArrayCopy() ); $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : []; - $keysWarmUp = []; - // Get all the value keys to fetch... - foreach ( $keyedIds as $key => $id ) { - $keysWarmUp[] = self::VALUE_KEY_PREFIX . $key; + // Load required keys into process cache in one go + $this->warmupCache = $this->getRawKeysForWarmup( + $this->getNonProcessCachedKeys( $valueKeys, $opts ), + $checkKeys + ); + $this->warmupKeyMisses = 0; + + // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback + $id = null; // current entity ID + $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) { + return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf ); + }; + + $values = []; + foreach ( $keyedIds as $key => $id ) { // preserve order + $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts ); } - // Get all the check keys to fetch... - foreach ( $checkKeys as $i => $checkKeyOrKeys ) { - if ( is_int( $i ) ) { - // Single check key that applies to all value keys - $keysWarmUp[] = self::TIME_KEY_PREFIX . $checkKeyOrKeys; - } else { - // List of check keys that apply to value key $i - $keysWarmUp = array_merge( - $keysWarmUp, - self::prefixCacheKeys( $checkKeyOrKeys, self::TIME_KEY_PREFIX ) - ); + + $this->warmupCache = []; + + return $values; + } + + /** + * 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 returning a map of (ID => new value) + * for all entity IDs in $regenById and it takes the following arguments: + * - $ids: a list of entity IDs to regenerate + * - &$ttls: a reference to the (entity ID => new TTL) map + * - &$setOpts: a reference to options for set() which can be altered + * - c) The return value is a map of (cache key => value) in the order of $keyedIds + * - d) The "lockTSE" and "busyValue" options are ignored + * + * @see WANObjectCache::getWithSetCallback() + * @see WANObjectCache::getMultiWithSetCallback() + * + * Example usage: + * @code + * $rows = $cache->getMultiWithUnionSetCallback( + * // 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 + * function ( array $ids, array &$ttls, array &$setOpts ) { + * $dbr = wfGetDB( DB_REPLICA ); + * // Account for any snapshot/replica DB lag + * $setOpts += Database::getCacheSetOptions( $dbr ); + * + * // Load the rows for these files + * $rows = []; + * $res = $dbr->select( 'file', '*', [ 'id' => $ids ], __METHOD__ ); + * foreach ( $res as $row ) { + * $rows[$row->id] = $row; + * $mtime = wfTimestamp( TS_UNIX, $row->timestamp ); + * $ttls[$row->id] = $this->adaptiveTTL( $mtime, $ttls[$row->id] ); + * } + * + * return $rows; + * }, + * ] + * ); + * $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.30 + */ + final public function getMultiWithUnionSetCallback( + ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = [] + ) { + $idsByValueKey = $keyedIds->getArrayCopy(); + $valueKeys = array_keys( $idsByValueKey ); + $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : []; + unset( $opts['lockTSE'] ); // incompatible + unset( $opts['busyValue'] ); // incompatible + + // Load required keys into process cache in one go + $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts ); + $this->warmupCache = $this->getRawKeysForWarmup( $keysGet, $checkKeys ); + $this->warmupKeyMisses = 0; + + // IDs of entities known to be in need of regeneration + $idsRegen = []; + + // Find out which keys are missing/deleted/stale + $curTTLs = []; + $asOfs = []; + $curByKey = $this->getMulti( $keysGet, $curTTLs, $checkKeys, $asOfs ); + foreach ( $keysGet as $key ) { + if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) { + $idsRegen[] = $idsByValueKey[$key]; } } - $this->warmupCache = $this->cache->getMulti( $keysWarmUp ); - $this->warmupCache += array_fill_keys( $keysWarmUp, false ); - $this->warmupKeyMisses = 0; + // Run the callback to populate the regeneration value map for all required IDs + $newSetOpts = []; + $newTTLsById = array_fill_keys( $idsRegen, $ttl ); + $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : []; // 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 ); + $id = null; // current entity ID + $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) + use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts ) + { + if ( array_key_exists( $id, $newValsById ) ) { + // Value was already regerated as expected, so use the value in $newValsById + $newValue = $newValsById[$id]; + $ttl = $newTTLsById[$id]; + $setOpts = $newSetOpts; + } else { + // Pre-emptive/popularity refresh and version mismatch cases are not detected + // above and thus $newValsById has no entry. Run $callback on this single entity. + $ttls = [ $id => $ttl ]; + $newValue = $callback( [ $id ], $ttls, $setOpts )[$id]; + $ttl = $ttls[$id]; + } + + return $newValue; }; + // Run the cache-aside logic using warmupCache instead of persistent cache queries $values = []; - foreach ( $keyedIds as $key => $id ) { + foreach ( $idsByValueKey as $key => $id ) { // preserve order $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts ); } @@ -1503,7 +1608,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * * @param array|string|bool $wrapped * @param float $now Unix Current timestamp (preferrably pre-query) - * @return array (mixed; false if absent/invalid, current time left) + * @return array (mixed; false if absent/tombstoned/invalid, current time left) */ protected function unwrap( $wrapped, $now ) { // Check if the value is a tombstone @@ -1598,4 +1703,59 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return $this->processCaches[$group]; } + + /** + * @param array $keys + * @param array $opts + * @return array List of keys + */ + private function getNonProcessCachedKeys( array $keys, array $opts ) { + $keysFound = []; + if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) { + $pcGroup = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY; + $procCache = $this->getProcessCache( $pcGroup ); + foreach ( $keys as $key ) { + if ( $procCache->get( $key ) !== false ) { + $keysFound[] = $key; + } + } + } + + return array_diff( $keys, $keysFound ); + } + + /** + * @param array $keys + * @param array $checkKeys + * @return array Map of (cache key => mixed) + */ + private function getRawKeysForWarmup( array $keys, array $checkKeys ) { + if ( !$keys ) { + return []; + } + + $keysWarmUp = []; + // Get all the value keys to fetch... + foreach ( $keys as $key ) { + $keysWarmUp[] = self::VALUE_KEY_PREFIX . $key; + } + // Get all the check keys to fetch... + foreach ( $checkKeys as $i => $checkKeyOrKeys ) { + if ( is_int( $i ) ) { + // Single check key that applies to all value keys + $keysWarmUp[] = self::TIME_KEY_PREFIX . $checkKeyOrKeys; + } else { + // List of check keys that apply to value key $i + $keysWarmUp = array_merge( + $keysWarmUp, + self::prefixCacheKeys( $checkKeyOrKeys, self::TIME_KEY_PREFIX ) + ); + } + } + + $warmupCache = $this->cache->getMulti( $keysWarmUp ); + $warmupCache += array_fill_keys( $keysWarmUp, false ); + + return $warmupCache; + } }