* @ingroup Cache
* @since 1.26
*/
-class WANObjectCache implements LoggerAwareInterface {
+class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
/** @var BagOStuff The local datacenter cache */
protected $cache;
/** @var HashBagOStuff Script instance PHP cache */
/** Max time expected to pass between delete() and DB commit finishing */
const MAX_COMMIT_DELAY = 3;
- /** Max replication lag before applying TTL_LAGGED to set() */
- const MAX_REPLICA_LAG = 5;
- /** Max time since snapshot transaction start to avoid no-op of set() */
- const MAX_SNAPSHOT_LAG = 5;
+ /** Max replication+snapshot lag before applying TTL_LAGGED or disallowing set() */
+ const MAX_READ_LAG = 7;
/** Seconds to tombstone keys on delete() */
- const HOLDOFF_TTL = 14; // MAX_COMMIT_DELAY + MAX_REPLICA_LAG + MAX_SNAPSHOT_LAG + 1
+ const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1
/** Seconds to keep dependency purge keys around */
- const CHECK_KEY_TTL = 31536000; // 1 year
+ const CHECK_KEY_TTL = self::TTL_YEAR;
/** Seconds to keep lock keys around */
const LOCK_TTL = 5;
/** Default remaining TTL at which to consider pre-emptive regeneration */
* // Fetch the row from the DB
* $row = $dbr->selectRow( ... );
* $key = $cache->makeKey( 'building', $buildingId );
- * $cache->set( $key, $row, 86400, $setOpts );
+ * $cache->set( $key, $row, $cache::TTL_DAY, $setOpts );
* @endcode
*
* @param string $key Cache key
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
$age = isset( $opts['since'] ) ? max( 0, microtime( true ) - $opts['since'] ) : 0;
$lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
-
+ // Disallow set() if the source data is uncommitted as it might get rolled back
if ( !empty( $opts['pending'] ) ) {
$this->logger->info( "Rejected set() for $key due to pending writes." );
return true; // no-op the write for being unsafe
}
-
- if ( $lag > self::MAX_REPLICA_LAG ) {
- // Too much lag detected; lower TTL so it converges faster
- $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
- $this->logger->warning( "Lowered set() TTL for $key due to replication lag." );
- }
-
- if ( $age > self::MAX_SNAPSHOT_LAG ) {
+ // Check if there's a risk of writing stale data after the purge tombstone expired
+ if ( ( $lag + $age ) > self::MAX_READ_LAG ) {
if ( $lockTSE >= 0 ) {
+ // Focus on avoiding stampedes; stash the value with a low TTL
$tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
$this->cache->set( self::STASH_KEY_PREFIX . $key, $value, $tempTTL );
}
- $this->logger->warning( "Rejected set() for $key due to snapshot lag." );
-
- return true; // no-op the write for being unsafe
+ // Case A: any long-running transaction; ignore this set()
+ if ( $age > self::MAX_READ_LAG ) {
+ $this->logger->warning( "Rejected set() for $key due to snapshot lag." );
+
+ return true; // no-op the write for being unsafe
+ // Case B: replication lag is high; lower TTL instead of ignoring all set()s
+ } elseif ( $lag > self::MAX_READ_LAG ) {
+ $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
+ $this->logger->warning( "Lowered set() TTL for $key due to replication lag." );
+ // Case C: medium length request during medium lag; ignore this set()
+ } else {
+ $this->logger->warning( "Rejected set() for $key due to high read lag." );
+
+ return true; // no-op the write for being unsafe
+ }
}
$wrapped = $this->wrap( $value, $ttl );
* $dbw->commit(); // end of request
* @endcode
*
- * If called twice on the same key, then the last hold-off TTL takes
- * precedence. For idempotence, the $ttl should not vary for different
- * delete() calls on the same key. Also note that lowering $ttl reduces
- * the effective range of the 'lockTSE' parameter to getWithSetCallback().
+ * The $ttl parameter can be used when purging values that have not actually changed
+ * recently. For example, a cleanup script to purge cache entries does not really need
+ * a hold-off period, so it can use the value 1. Likewise for user-requested purge.
+ * Note that $ttl limits the effective range of 'lockTSE' for getWithSetCallback().
+ *
+ * If called twice on the same key, then the last hold-off TTL takes precedence. For
+ * idempotence, the $ttl should not vary for different delete() calls on the same key.
*
* @param string $key Cache key
- * @param integer $ttl How long to block writes to the key [seconds]
+ * @param integer $ttl Tombstone TTL; Default: WANObjectCache::HOLDOFF_TTL
* @return bool True if the item was purged or not found, false on failure
*/
final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
* $catInfo = $cache->getWithSetCallback(
* // Key to store the cached value under
* $cache->makeKey( 'cat-attributes', $catId ),
- * // Time-to-live (seconds)
- * 60,
+ * // Time-to-live (in seconds)
+ * $cache::TTL_MINUTE,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* $catConfig = $cache->getWithSetCallback(
* // Key to store the cached value under
* $cache->makeKey( 'site-cat-config' ),
- * // Time-to-live (seconds)
- * 86400,
+ * // Time-to-live (in seconds)
+ * $cache::TTL_DAY,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* // Key to store the cached value under
* $cache->makeKey( 'cat-state', $cat->getId() ),
* // Time-to-live (seconds)
- * 900,
+ * $cache::TTL_HOUR,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* // Determine new value from the DB
* $lastCatActions = $cache->getWithSetCallback(
* // Key to store the cached value under
* $cache->makeKey( 'cat-last-actions', 100 ),
- * // Time-to-live (seconds)
+ * // Time-to-live (in seconds)
* 10,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {