Merge "Make WAN cache HOLDOFF_TTL smaller by combining db/snapshot lag"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 28 Oct 2015 15:10:40 +0000 (15:10 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 28 Oct 2015 15:10:40 +0000 (15:10 +0000)
1  2 
includes/libs/objectcache/WANObjectCache.php

@@@ -64,7 -64,7 +64,7 @@@ use Psr\Log\NullLogger
   * @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 ) {