/** Tiny positive float to use when using "minTime" to assert an inequality */
const TINY_POSTIVE = 0.000001;
- /** Seconds of delay after get() where set() storms are a consideration with 'lockTSE' */
- const SET_DELAY_HIGH_SEC = 0.1;
+ /** Milliseconds of delay after get() where set() storms are a consideration with 'lockTSE' */
+ const SET_DELAY_HIGH_MS = 50;
/** Min millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
const RECENT_SET_LOW_MS = 50;
/** Max millisecond set() backoff for keys in hold-off (far less than INTERIM_KEY_TTL) */
* - tombAsOf: UNIX timestamp of the tombstone or null if the key is not tombstoned
* - lastCKPurge: UNIX timestamp of the highest check key or null if none provided
*
- * Othwerwise, $info will transform into the cached value timestamp.
+ * Otherwise, $info will transform into the cached value timestamp.
*
* @param string $key Cache key made from makeKey() or makeGlobalKey()
* @param mixed|null &$curTTL Approximate TTL left on the key if present/tombstoned [returned]
* @param string $key Cache key
* @param mixed $value
* @param int $ttl Seconds to live. Special values are:
- * - WANObjectCache::TTL_INDEFINITE: Cache forever
+ * - WANObjectCache::TTL_INDEFINITE: Cache forever (default)
* @param array $opts Options map:
- * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
+ * - lag: seconds of replica DB lag. Typically, this is either the replica DB lag
* before the data was read or, if applicable, the replica DB lag before
* the snapshot-isolated transaction the data was read from started.
* Use false to indicate that replication is not running.
* Default: 0 seconds
- * - since : UNIX timestamp of the data in $value. Typically, this is either
+ * - since: UNIX timestamp of the data in $value. Typically, this is either
* the current time the data was read or (if applicable) the time when
* the snapshot-isolated transaction the data was read from started.
* Default: 0 seconds
- * - pending : Whether this data is possibly from an uncommitted write transaction.
+ * - pending: whether this data is possibly from an uncommitted write transaction.
* Generally, other threads should not see values from the future and
* they certainly should not see ones that ended up getting rolled back.
* Default: false
- * - lockTSE : if excessive replication/snapshot lag is detected, then store the value
+ * - lockTSE: if excessive replication/snapshot lag is detected, then store the value
* with this TTL and flag it as stale. This is only useful if the reads for this key
* use getWithSetCallback() with "lockTSE" set. Note that if "staleTTL" is set
* then it will still add on to this TTL in the excessive lag scenario.
* Default: WANObjectCache::TSE_NONE
- * - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
+ * - 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: WANObjectCache::STALE_TTL_NONE.
+ * - creating: optimize for the case where the key does not already exist.
+ * Default: false
* @note Options added in 1.28: staleTTL
+ * @note Options added in 1.33: creating
* @return bool Success
*/
- final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
+ final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
$now = $this->getCurrentTime();
$lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
$staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
$age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
+ $creating = $opts['creating'] ?? false;
$lag = $opts['lag'] ?? 0;
// Do not cache potentially uncommitted data as it might get rolled back
[ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
);
// Case C: medium length request with medium replication lag
+ } elseif ( $lockTSE >= 0 ) {
+ // Store value as *almost* stale to avoid cache and mutex stampedes
+ $logicalTTL = self::TTL_SECOND;
+ $this->logger->info(
+ 'Lowered set() TTL for {cachekey} due to high read lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
} else {
- if ( $lockTSE >= 0 ) {
- // Store value as *almost* stale to avoid cache and mutex stampedes
- $logicalTTL = self::TTL_SECOND;
- $this->logger->info(
- 'Lowered set() TTL for {cachekey} due to high read lag.',
- [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
- );
- } else {
- $this->logger->info(
- 'Rejected set() for {cachekey} due to high read lag.',
- [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
- );
+ $this->logger->info(
+ 'Rejected set() for {cachekey} due to high read lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
- return true; // no-op the write for being unsafe
- }
+ return true; // no-op the write for being unsafe
}
}
// Wrap that value with time/TTL/version metadata
$wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $now );
+ $storeTTL = $ttl + $staleTTL;
- $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
- return ( is_string( $cWrapped ) )
- ? false // key is tombstoned; do nothing
- : $wrapped;
- };
+ if ( $creating ) {
+ $ok = $this->cache->add( self::VALUE_KEY_PREFIX . $key, $wrapped, $storeTTL );
+ } else {
+ $ok = $this->cache->merge(
+ self::VALUE_KEY_PREFIX . $key,
+ function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
+ // A string value means that it is a tombstone; do nothing in that case
+ return ( is_string( $cWrapped ) ) ? false : $wrapped;
+ },
+ $storeTTL,
+ 1 // 1 attempt
+ );
+ }
- return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
+ return $ok;
}
/**
* stampede is worth avoiding. Note that if the key falls out of cache then concurrent
* threads will all run the callback on cache miss until the value is saved in cache.
* The only stampede protection in that case is from duplicate cache sets when the
- * callback takes longer than WANObjectCache::SET_DELAY_HIGH_SEC seconds; consider
+ * callback takes longer than WANObjectCache::SET_DELAY_HIGH_MS milliseconds; consider
* using "busyValue" if such stampedes are a problem. Note that the higher "lockTSE" is
* set, the higher the worst-case staleness of returned values can be. Also note that
* this option does not by itself handle the case of the key simply expiring on account
* It is generally preferable to use a class constant when setting this value.
* This has no effect unless pcTTL is used.
* Default: WANObjectCache::PC_PRIMARY.
- * - version: Integer version number. This allows for callers to make breaking changes to
- * how values are stored while maintaining compatability and correct cache purges. New
- * versions are stored alongside older versions concurrently. Avoid storing class objects
- * however, as this reduces compatibility (due to serialization).
+ * - version: Integer version number. This lets callers make breaking changes to the format
+ * of cached values without causing problems for sites that use non-instantaneous code
+ * deployments. Old and new code will recognize incompatible versions and purges from
+ * both old and new code will been seen by each other. When this method encounters an
+ * incompatibly versioned value at the provided key, a "variant key" will be used for
+ * reading from and saving to cache. The variant key is specific to the key and version
+ * number provided to this method. If the variant key value is older than that of the
+ * provided key, or the provided key is non-existant, then the variant key will be seen
+ * as non-existant. Therefore, delete() calls invalidate the provided key's variant keys.
+ * The "checkKeys" and "touchedCallback" options still apply to variant keys as usual.
+ * Avoid storing class objects, as this reduces compatibility (due to serialization).
* Default: null.
* - minAsOf: Reject values if they were generated before this UNIX timestamp.
* This is useful if the source of a key is suspected of having possibly changed
}
} elseif ( !$useMutex || $hasLock ) {
if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
+ $setOpts['creating'] = ( $curValue === false );
// Save the value unless a lock-winning thread is already expected to do that
$setOpts['lockTSE'] = $lockTSE;
$setOpts['staleTTL'] = $staleTTL;
// consistent hashing).
if ( $lockTSE < 0 || $hasLock ) {
return true; // either not a priori hot or thread has the lock
- } elseif ( $elapsed <= self::SET_DELAY_HIGH_SEC ) {
+ } elseif ( $elapsed <= self::SET_DELAY_HIGH_MS * 1e3 ) {
return true; // not enough time for threads to pile up
}