X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=8f2c72a141f71ee3938fe02df10b6699c71e6dd7;hb=0ac1ee63e8b131576c8e9b703ed01ee5f9a377d1;hp=b337e9eb6dbc8ea012f063cea1ba0eb4a6241acb;hpb=5405fd880e914e7d460a9148477688770aeb7d1a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index b337e9eb6d..8f2c72a141 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -387,13 +387,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $purgeValues = []; foreach ( $timeKeys as $timeKey ) { $purge = isset( $wrappedValues[$timeKey] ) - ? self::parsePurgeValue( $wrappedValues[$timeKey] ) + ? $this->parsePurgeValue( $wrappedValues[$timeKey] ) : false; if ( $purge === false ) { // Key is not set or invalid; regenerate $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL ); $this->cache->add( $timeKey, $newVal, self::CHECK_KEY_TTL ); - $purge = self::parsePurgeValue( $newVal ); + $purge = $this->parsePurgeValue( $newVal ); } $purgeValues[] = $purge; } @@ -601,25 +601,102 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * Note that "check" keys won't collide with other regular keys. * * @param string $key - * @return float UNIX timestamp of the check key + * @return float UNIX timestamp */ final public function getCheckKeyTime( $key ) { - $key = self::TIME_KEY_PREFIX . $key; + return $this->getMultiCheckKeyTime( [ $key ] )[$key]; + } - $purge = self::parsePurgeValue( $this->cache->get( $key ) ); - if ( $purge !== false ) { - $time = $purge[self::FLD_TIME]; - } else { - // Casting assures identical floats for the next getCheckKeyTime() calls - $now = (string)$this->getCurrentTime(); - $this->cache->add( $key, - $this->makePurgeValue( $now, self::HOLDOFF_TTL ), - self::CHECK_KEY_TTL - ); - $time = (float)$now; + /** + * Fetch the values of each timestamp "check" key + * + * This works like getCheckKeyTime() except it takes a list of keys + * and returns a map of timestamps instead of just that of one key + * + * This might be useful if both: + * - a) a class of entities each depend on hundreds of other entities + * - b) these other entities are depended upon by millions of entities + * + * The later entities can each use a "check" key to invalidate their dependee entities. + * However, it is expensive for the former entities to verify against all of the relevant + * "check" keys during each getWithSetCallback() call. A less expensive approach is to do + * these verifications only after a "time-till-verify" (TTV) has passed. This is a middle + * ground between using blind TTLs and using constant verification. The adaptiveTTL() method + * can be used to dynamically adjust the TTV. Also, the initial TTV can make use of the + * last-modified times of the dependant entities (either from the DB or the "check" keys). + * + * Example usage: + * @code + * $value = $cache->getWithSetCallback( + * $cache->makeGlobalKey( 'wikibase-item', $id ), + * self::INITIAL_TTV, // initial time-till-verify + * function ( $oldValue, &$ttv, &$setOpts, $oldAsOf ) use ( $checkKeys, $cache ) { + * $now = microtime( true ); + * // Use $oldValue if it passes max ultimate age and "check" key comparisons + * if ( $oldValue && + * $oldAsOf > max( $cache->getMultiCheckKeyTime( $checkKeys ) ) && + * ( $now - $oldValue['ctime'] ) <= self::MAX_CACHE_AGE + * ) { + * // Increase time-till-verify by 50% of last time to reduce overhead + * $ttv = $cache->adaptiveTTL( $oldAsOf, self::MAX_TTV, self::MIN_TTV, 1.5 ); + * // Unlike $oldAsOf, "ctime" is the ultimate age of the cached data + * return $oldValue; + * } + * + * $mtimes = []; // dependency last-modified times; passed by reference + * $value = [ 'data' => $this->fetchEntityData( $mtimes ), 'ctime' => $now ]; + * // Guess time-till-change among the dependencies, e.g. 1/(total change rate) + * $ttc = 1 / array_sum( array_map( + * function ( $mtime ) use ( $now ) { + * return 1 / ( $mtime ? ( $now - $mtime ) : 900 ); + * }, + * $mtimes + * ) ); + * // The time-to-verify should not be overly pessimistic nor optimistic + * $ttv = min( max( $ttc, self::MIN_TTV ), self::MAX_TTV ); + * + * return $value; + * }, + * [ 'staleTTL' => $cache::TTL_DAY ] // keep around to verify and re-save + * ); + * @endcode + * + * @see WANObjectCache::getCheckKeyTime() + * @see WANObjectCache::getWithSetCallback() + * + * @param array $keys + * @return float[] Map of (key => UNIX timestamp) + * @since 1.31 + */ + final public function getMultiCheckKeyTime( array $keys ) { + $rawKeys = []; + foreach ( $keys as $key ) { + $rawKeys[$key] = self::TIME_KEY_PREFIX . $key; + } + + $rawValues = $this->cache->getMulti( $rawKeys ); + $rawValues += array_fill_keys( $rawKeys, false ); + + $times = []; + foreach ( $rawKeys as $key => $rawKey ) { + $purge = $this->parsePurgeValue( $rawValues[$rawKey] ); + if ( $purge !== false ) { + $time = $purge[self::FLD_TIME]; + } else { + // Casting assures identical floats for the next getCheckKeyTime() calls + $now = (string)$this->getCurrentTime(); + $this->cache->add( + $rawKey, + $this->makePurgeValue( $now, self::HOLDOFF_TTL ), + self::CHECK_KEY_TTL + ); + $time = (float)$now; + } + + $times[$key] = $time; } - return $time; + return $times; } /** @@ -1399,7 +1476,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @return bool Success * @since 1.28 */ - public function reap( $key, $purgeTimestamp, &$isStale = false ) { + final 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 ) { @@ -1428,7 +1505,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @return bool Success * @since 1.28 */ - public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) { + final 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; @@ -1474,7 +1551,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @return ArrayIterator Iterator yielding (cache key => entity ID) in $entities order * @since 1.28 */ - public function makeMultiKeys( array $entities, callable $keyFunc ) { + final public function makeMultiKeys( array $entities, callable $keyFunc ) { $map = []; foreach ( $entities as $entity ) { $map[$keyFunc( $entity, $this )] = $entity; @@ -1547,7 +1624,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @param bool $enabled Whether to enable interim caching * @since 1.31 */ - public function useInterimHoldOffCaching( $enabled ) { + final public function useInterimHoldOffCaching( $enabled ) { $this->useInterimHoldOffCaching = $enabled; } @@ -1641,7 +1718,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @return int Number of warmup key cache misses last round * @since 1.30 */ - public function getWarmupKeyMisses() { + final public function getWarmupKeyMisses() { return $this->warmupKeyMisses; } @@ -1848,7 +1925,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { */ protected function unwrap( $wrapped, $now ) { // Check if the value is a tombstone - $purge = self::parsePurgeValue( $wrapped ); + $purge = $this->parsePurgeValue( $wrapped ); if ( $purge !== false ) { // Purged values should always have a negative current $ttl $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE ); @@ -1916,7 +1993,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * @return array|bool Array containing a UNIX timestamp (float) and holdoff period (integer), * or false if value isn't a valid purge value */ - protected static function parsePurgeValue( $value ) { + protected function parsePurgeValue( $value ) { if ( !is_string( $value ) ) { return false; }