objectcache: use INTERIM_KEY_TTL constant in WANObjectCache for readability
[lhc/web/wiklou.git] / includes / libs / objectcache / WANObjectCache.php
index ae7f36c..db27e42 100644 (file)
@@ -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 );