X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2Flibs%2Fobjectcache%2FWANObjectCache.php;h=db27e42e1e381843cf1a3cb20e9fa2f5041a1635;hp=ae7f36cefe2d3392901428a78b7d2eb6ff0da760;hb=ee14393358109fff6023855f6d194016b6332bb0;hpb=30b29edfae7efa3505babdd9a2c472b4b30810b8 diff --git a/includes/libs/objectcache/WANObjectCache.php b/includes/libs/objectcache/WANObjectCache.php index ae7f36cefe..db27e42e1e 100644 --- a/includes/libs/objectcache/WANObjectCache.php +++ b/includes/libs/objectcache/WANObjectCache.php @@ -111,6 +111,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { /** Seconds to keep dependency purge keys around */ const CHECK_KEY_TTL = self::TTL_YEAR; + /** Seconds to keep interim value keys for tombstoned keys around */ + const INTERIM_KEY_TTL = 1; + /** Seconds to keep lock keys around */ const LOCK_TTL = 10; /** Default remaining TTL at which to consider pre-emptive regeneration */ @@ -137,6 +140,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { const HOLDOFF_NONE = 0; /** Idiom for set()/getWithSetCallback() for "do not augment the storage medium TTL" */ const STALE_TTL_NONE = 0; + /** Idiom for set()/getWithSetCallback() for "no post-expired grace period" */ + const GRACE_TTL_NONE = 0; /** Idiom for getWithSetCallback() for "no minimum required as-of timestamp" */ const MIN_TIMESTAMP_NONE = 0.0; @@ -616,14 +621,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * 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). + * be invalidated. This approach is useful if many keys depend on a single entity. * - * 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. + * The timestamp of the "check" key 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 lagged replica DB). A high TTL is set on the "check" + * key, making it possible to know the timestamp of the last change to the corresponding + * entities in most cases. This might use more cache space than resetCheckKey(). * * 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 @@ -813,13 +817,21 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * * @param string $key Cache key made from makeKey() or makeGlobalKey() * @param int $ttl Seconds to live for key updates. Special values are: - * - WANObjectCache::TTL_INDEFINITE: Cache forever - * - WANObjectCache::TTL_UNCACHEABLE: Do not cache at all + * - WANObjectCache::TTL_INDEFINITE: Cache forever (subject to LRU-style evictions) + * - WANObjectCache::TTL_UNCACHEABLE: Do not cache (if the key exists, it is not deleted) * @param callable $callback Value generation function * @param array $opts Options map: * - checkKeys: List of "check" keys. The key at $key will be seen as invalid when either - * touchCheckKey() or resetCheckKey() is called on any of these keys. + * touchCheckKey() or resetCheckKey() is called on any of the keys in this list. This + * is useful if thousands or millions of keys depend on the same entity. The entity can + * simply have its "check" key updated whenever the entity is modified. * Default: []. + * - graceTTL: Consider reusing expired values instead of refreshing them if they expired + * less than this many seconds ago. The odds of a refresh becomes more likely over time, + * becoming certain once the grace period is reached. This can reduce traffic spikes + * when millions of keys are compared to the same "check" key and touchCheckKey() + * or resetCheckKey() is called on that "check" key. + * Default: WANObjectCache::GRACE_TTL_NONE. * - lockTSE: If the key is tombstoned or expired (by checkKeys) less than this many seconds * ago, then try to have a single thread handle cache regeneration at any given time. * Other threads will try to use stale values if possible. If, on miss, the time since @@ -854,16 +866,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { * 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 (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 + * - hotTTR: Expected time-till-refresh (TTR) in seconds for keys that average ~1 hit per + * second (e.g. 1Hz). 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. + * This uses randomization to avoid triggering cache stampedes. 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. + * $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. + * This helps avoid cache stampedes that might be triggered due to the key expiring. * Default: WANObjectCache::LOW_TTL. * - ageNew: Consider popularity refreshes only once a key reaches this age in seconds. * Default: WANObjectCache::AGE_NEW. @@ -903,11 +917,14 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { use ( $callback, $version ) { if ( is_array( $oldValue ) && array_key_exists( self::VFLD_DATA, $oldValue ) + && array_key_exists( self::VFLD_VERSION, $oldValue ) + && $oldValue[self::VFLD_VERSION] === $version ) { $oldData = $oldValue[self::VFLD_DATA]; } else { // VFLD_DATA is not set if an old, unversioned, key is present $oldData = false; + $oldAsOf = null; } return [ @@ -962,6 +979,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $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; + $graceTTL = isset( $opts['graceTTL'] ) ? $opts['graceTTL'] : self::GRACE_TTL_NONE; $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : []; $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null; $popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR; @@ -980,7 +998,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $preCallbackTime = $this->getCurrentTime(); // Determine if a cached value regeneration is needed or desired if ( $value !== false - && $curTTL > 0 + && $this->isAliveOrInGracePeriod( $curTTL, $graceTTL ) && $this->isValid( $value, $versioned, $asOf, $minTime ) && !$this->worthRefreshExpiring( $curTTL, $lowTTL ) && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime ) @@ -994,7 +1012,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { $isTombstone = ( $curTTL !== null && $value === false ); if ( $isTombstone && $lockTSE <= 0 ) { // Use the INTERIM value for tombstoned keys to reduce regeneration load - $lockTSE = 1; + $lockTSE = self::INTERIM_KEY_TTL; } // Assume a key is hot if requested soon after invalidation $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ); @@ -1631,13 +1649,44 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { return $ok; } + /** + * Check if a key is fresh or in the grace window and thus due for randomized reuse + * + * If $curTTL > 0 (e.g. not expired) this returns true. Otherwise, the chance of returning + * true decrease steadily from 100% to 0% as the |$curTTL| moves from 0 to $graceTTL seconds. + * This handles widely varying levels of cache access traffic. + * + * If $curTTL <= -$graceTTL (e.g. already expired), then this returns false. + * + * @param float $curTTL Approximate TTL left on the key if present + * @param int $graceTTL Consider using stale values if $curTTL is greater than this + * @return bool + */ + protected function isAliveOrInGracePeriod( $curTTL, $graceTTL ) { + if ( $curTTL > 0 ) { + return true; + } elseif ( $graceTTL <= 0 ) { + return false; + } + + $ageStale = abs( $curTTL ); // seconds of staleness + $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live + if ( $curGTTL <= 0 ) { + return false; // already out of grace period + } + + // Chance of using a stale value is the complement of the chance of refreshing it + return !$this->worthRefreshExpiring( $curGTTL, $graceTTL ); + } + /** * 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 - * moves from $lowTTL to 0 seconds. This handles widely varying - * levels of cache access traffic. + * This returns false if $curTTL >= $lowTTL. Otherwise, the chance of returning true + * increases steadily from 0% to 100% as the $curTTL moves from $lowTTL to 0 seconds. + * This handles widely varying levels of cache access traffic. + * + * If $curTTL <= 0 (e.g. already expired), then this returns false. * * @param float $curTTL Approximate TTL left on the key if present * @param float $lowTTL Consider a refresh when $curTTL is less than this @@ -1649,7 +1698,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface { } elseif ( $curTTL >= $lowTTL ) { return false; } elseif ( $curTTL <= 0 ) { - return true; + return false; } $chance = ( 1 - $curTTL / $lowTTL );