* having to inspect a "current time left" variable (e.g. $curTTL, $curTTLs), a cache
* regeneration will automatically be triggered using the callback.
*
- * The simplest way to avoid stampedes for hot keys is to use
- * the 'lockTSE' option in $opts. If cache purges are needed, also:
+ * The $ttl argument and "hotTTR" option (in $opts) use time-dependant randomization
+ * to avoid stampedes. Keys that are slow to regenerate and either heavily used
+ * or subject to explicit (unpredictable) purges, may need additional mechanisms.
+ * The simplest way to avoid stampedes for such keys is to use 'lockTSE' (in $opts).
+ * If explicit purges are needed, also:
* - a) Pass $key into $checkKeys
* - b) Use touchCheckKey( $key ) instead of delete( $key )
*
* This is useful if the source of a key is suspected of having possibly changed
* recently, and the caller wants any such changes to be reflected.
* Default: WANObjectCache::MIN_TIMESTAMP_NONE.
- * - hotTTR: Expected time-till-refresh for keys that average ~1 hit/second.
- * This should be greater than "ageNew". Keys with higher hit rates will regenerate
- * more often. This is useful when a popular key is changed but the cache purge was
- * delayed or lost. Seldom used keys are rarely affected by this setting, unless an
- * extremely low "hotTTR" value is passed in.
+ * - hotTTR: Expected time-till-refresh (TTR) for keys that average ~1 hit/second (1 Hz).
+ * Keys with a hit rate higher than 1Hz will refresh sooner than this TTR and vise versa.
+ * Such refreshes won't happen until keys are "ageNew" seconds old. The TTR is useful at
+ * reducing the impact of missed cache purges, since the effect of a heavily referenced
+ * key being stale is worse than that of a rarely referenced key. Unlike simply lowering
+ * $ttl, seldomly used keys are largely unaffected by this option, which makes it possible
+ * to have a high hit rate for the "long-tail" of less-used keys.
* Default: WANObjectCache::HOT_TTR.
* - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less
* than this. It becomes more likely over time, becoming certain once the key is expired.
// A deleted key with a negative TTL left must be tombstoned
$isTombstone = ( $curTTL !== null && $value === false );
+ if ( $isTombstone && $lockTSE <= 0 ) {
+ // Use the INTERIM value for tombstoned keys to reduce regeneration load
+ $lockTSE = 1;
+ }
// Assume a key is hot if requested soon after invalidation
$isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
// Use the mutex if there is no value and a busy fallback is given
// Use the INTERIM value for tombstoned keys to reduce regeneration load.
// For hot keys, either another thread has the lock or the lock failed;
// use the INTERIM value from the last thread that regenerated it.
- $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
- list( $value ) = $this->unwrap( $wrapped, microtime( true ) );
- if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
- $asOf = $wrapped[self::FLD_TIME];
-
+ $value = $this->getInterimValue( $key, $versioned, $minTime, $asOf );
+ if ( $value !== false ) {
return $value;
}
// Use the busy fallback value if nothing else
} finally {
--$this->callbackDepth;
}
+ $valueIsCacheable = ( $value !== false && $ttl >= 0 );
+
// When delete() is called, writes are write-holed by the tombstone,
// so use a special INTERIM key to pass the new value around threads.
- if ( ( $isTombstone && $lockTSE > 0 ) && $value !== false && $ttl >= 0 ) {
+ if ( ( $isTombstone && $lockTSE > 0 ) && $valueIsCacheable ) {
$tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
$newAsOf = microtime( true );
$wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
// Avoid using set() to avoid pointless mcrouter broadcasting
- $this->cache->merge(
- self::INTERIM_KEY_PREFIX . $key,
- function () use ( $wrapped ) {
- return $wrapped;
- },
- $tempTTL,
- 1
- );
+ $this->setInterimValue( $key, $wrapped, $tempTTL );
}
- if ( $value !== false && $ttl >= 0 ) {
+ if ( $valueIsCacheable ) {
$setOpts['lockTSE'] = $lockTSE;
// Use best known "since" timestamp if not provided
$setOpts += [ 'since' => $preCallbackTime ];
if ( $lockAcquired ) {
// Avoid using delete() to avoid pointless mcrouter broadcasting
- $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, 1 );
+ $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
}
return $value;
}
+ /**
+ * @param string $key
+ * @param bool $versioned
+ * @param float $minTime
+ * @param mixed $asOf
+ * @return mixed
+ */
+ protected function getInterimValue( $key, $versioned, $minTime, &$asOf ) {
+ $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
+ list( $value ) = $this->unwrap( $wrapped, microtime( true ) );
+ if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
+ $asOf = $wrapped[self::FLD_TIME];
+
+ return $value;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $key
+ * @param array $wrapped
+ * @param int $tempTTL
+ */
+ protected function setInterimValue( $key, $wrapped, $tempTTL ) {
+ $this->cache->merge(
+ self::INTERIM_KEY_PREFIX . $key,
+ function () use ( $wrapped ) {
+ return $wrapped;
+ },
+ $tempTTL,
+ 1
+ );
+ }
+
/**
* Method to fetch multiple cache keys at once with regeneration
*
* $setOpts += Database::getCacheSetOptions( $dbr );
*
* // Load the row for this file
- * $row = $dbr->selectRow( 'file', '*', [ 'id' => $id ], __METHOD__ );
+ * $row = $dbr->selectRow( 'file', File::selectFields(), [ 'id' => $id ], __METHOD__ );
*
* return $row ? (array)$row : false;
* },
*
* // Load the rows for these files
* $rows = [];
- * $res = $dbr->select( 'file', '*', [ 'id' => $ids ], __METHOD__ );
+ * $res = $dbr->select( 'file', File::selectFields(), [ 'id' => $ids ], __METHOD__ );
* foreach ( $res as $row ) {
* $rows[$row->id] = $row;
* $mtime = wfTimestamp( TS_UNIX, $row->timestamp );
/**
* @see BagOStuff::makeKey()
- * @param string $keys,... Key component
- * @return string
+ * @param string $keys,... Key component (starting with a key collection name)
+ * @return string Colon-delimited list of $keyspace followed by escaped components of $args
* @since 1.27
*/
public function makeKey() {
/**
* @see BagOStuff::makeGlobalKey()
- * @param string $keys,... Key component
- * @return string
+ * @param string $keys,... Key component (starting with a key collection name)
+ * @return string Colon-delimited list of $keyspace followed by escaped components of $args
* @since 1.27
*/
public function makeGlobalKey() {
* @return int Adaptive TTL
* @since 1.28
*/
- public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = .2 ) {
+ public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
$mtime = (int)$mtime; // handle fractional seconds and string integers
}
}
/**
- * Check if a key should be regenerated (using random probability)
+ * Check if a key is nearing expiration and thus due for randomized regeneration
*
* This returns false if $curTTL >= $lowTTL. Otherwise, the chance
* of returning true increases steadily from 0% to 100% as the $curTTL
return array_diff( $keys, $keysFound );
}
- /**
+ /**
* @param array $keys
* @param array $checkKeys
* @return array Map of (cache key => mixed)