const TTL_LAGGED = 30;
/** Idiom for delete() for "no hold-off" */
const HOLDOFF_NONE = 0;
- /** Idiom for set() for "do not augment the storage medium TTL" */
+ /** Idiom for set()/getWithSetCallback() for "do not augment the storage medium TTL" */
const STALE_TTL_NONE = 0;
/** Idiom for getWithSetCallback() for "no minimum required as-of timestamp" */
* on all keys that should be changed. When get() is called on those
* keys, the relevant "check" keys must be supplied for this to work.
*
- * The "check" key essentially represents a last-modified field.
- * When touched, the field will be updated on all cache servers.
- * Keys using it via get(), getMulti(), or getWithSetCallback() will
- * be invalidated. It is treated as being HOLDOFF_TTL seconds in the future
- * by those methods to avoid race conditions where dependent keys get updated
- * with stale values (e.g. from a DB replica DB).
- *
- * This is typically useful for keys with hardcoded names or in some cases
- * dynamically generated names where a low number of combinations exist.
- * When a few important keys get a large number of hits, a high cache
- * time is usually desired as well as "lockTSE" logic. The resetCheckKey()
- * method is less appropriate in such cases since the "time since expiry"
- * cannot be inferred, causing any get() after the reset to treat the key
- * as being "hot", resulting in more stale value usage.
+ * The "check" key essentially represents a last-modified time of an entity.
+ * When the key is touched, the timestamp will be updated to the current time.
+ * Keys using the "check" key via get(), getMulti(), or getWithSetCallback() will
+ * be invalidated. The timestamp of "check" is treated as being HOLDOFF_TTL seconds
+ * in the future by get*() methods in order to avoid race conditions where keys are
+ * updated with stale values (e.g. from a DB replica DB).
+ *
+ * This method is typically useful for keys with hardcoded names or in some cases
+ * dynamically generated names, provided the number of such keys is modest. It sets a
+ * high TTL on the "check" key, making it possible to know the timestamp of the last
+ * change to the corresponding entities in most cases.
+ *
+ * When a few important keys get a large number of hits, a high cache time is usually
+ * desired as well as "lockTSE" logic. The resetCheckKey() method is less appropriate
+ * in such cases since the "time since expiry" cannot be inferred, causing any get()
+ * after the reset to treat the key as being "hot", resulting in more stale value usage.
*
* Note that "check" keys won't collide with other regular keys.
*
* to, any temporary ejection of that server will cause the value to be
* seen as purged as a new server will initialize the "check" key.
*
- * The advantage is that this does not place high TTL keys on every cache
- * server, making it better for code that will cache many different keys
- * and either does not use lockTSE or uses a low enough TTL anyway.
- *
- * This is typically useful for keys with dynamically generated names
- * where a high number of combinations exist.
+ * The advantage here is that the "check" keys, which have high TTLs, will only
+ * be created when a get*() method actually uses that key. This is better when
+ * a large number of "check" keys are invalided in a short period of time.
*
* Note that "check" keys won't collide with other regular keys.
*
* Default: WANObjectCache::LOW_TTL.
* - ageNew: Consider popularity refreshes only once a key reaches this age in seconds.
* Default: WANObjectCache::AGE_NEW.
+ * - staleTTL: Seconds to keep the key around if it is stale. This means that on cache
+ * miss the callback may get $oldValue/$oldAsOf values for keys that have already been
+ * expired for this specified time. This is useful if adaptiveTTL() is used on the old
+ * value's as-of time when it is verified as still being correct.
+ * Default: WANObjectCache::STALE_TTL_NONE
* @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
protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
$lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
+ $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
$checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
$busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
$popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR;
if ( $valueIsCacheable ) {
$setOpts['lockTSE'] = $lockTSE;
+ $setOpts['staleTTL'] = $staleTTL;
// Use best known "since" timestamp if not provided
$setOpts += [ 'since' => $preCallbackTime ];
// Update the cache; this will fail if the key is tombstoned
* $ttl = $cache->adaptiveTTL( $mtime, $cache::TTL_DAY );
* @endcode
*
+ * Another use case is when there are no applicable "last modified" fields in the DB,
+ * and there are too many dependencies for explicit purges to be viable, and the rate of
+ * change to relevant content is unstable, and it is highly valued to have the cached value
+ * be as up-to-date as possible.
+ *
+ * Example usage:
+ * @code
+ * $query = "<some complex query>";
+ * $idListFromComplexQuery = $cache->getWithSetCallback(
+ * $cache->makeKey( 'complex-graph-query', $hashOfQuery ),
+ * GraphQueryClass::STARTING_TTL,
+ * function ( $oldValue, &$ttl, array &$setOpts, $oldAsOf ) use ( $query, $cache ) {
+ * $gdb = $this->getReplicaGraphDbConnection();
+ * // Account for any snapshot/replica DB lag
+ * $setOpts += GraphDatabase::getCacheSetOptions( $gdb );
+ *
+ * $newList = iterator_to_array( $gdb->query( $query ) );
+ * sort( $newList, SORT_NUMERIC ); // normalize
+ *
+ * $minTTL = GraphQueryClass::MIN_TTL;
+ * $maxTTL = GraphQueryClass::MAX_TTL;
+ * if ( $oldValue !== false ) {
+ * // Note that $oldAsOf is the last time this callback ran
+ * $ttl = ( $newList === $oldValue )
+ * // No change: cache for 150% of the age of $oldValue
+ * ? $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, 1.5 )
+ * // Changed: cache for %50 of the age of $oldValue
+ * : $cache->adaptiveTTL( $oldAsOf, $maxTTL, $minTTL, .5 );
+ * }
+ *
+ * return $newList;
+ * },
+ * [
+ * // Keep stale values around for doing comparisons for TTL calculations.
+ * // High values improve long-tail keys hit-rates, though might waste space.
+ * 'staleTTL' => GraphQueryClass::GRACE_TTL
+ * ]
+ * );
+ * @endcode
+ *
* @param int|float $mtime UNIX timestamp
* @param int $maxTTL Maximum TTL (seconds)
* @param int $minTTL Minimum TTL (seconds); Default: 30
return $minTTL; // no last-modified time provided
}
- $age = time() - $mtime;
+ $age = $this->getCurrentTime() - $mtime;
return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
}