Make WAN cache HOLDOFF_TTL smaller by combining db/snapshot lag
[lhc/web/wiklou.git] / includes / libs / objectcache / WANObjectCache.php
index 39eb792..d9a8d71 100644 (file)
  * @author Aaron Schulz
  */
 
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
 /**
  * Multi-datacenter aware caching interface
  *
@@ -60,7 +64,7 @@
  * @ingroup Cache
  * @since 1.26
  */
-class WANObjectCache {
+class WANObjectCache implements LoggerAwareInterface {
        /** @var BagOStuff The local datacenter cache */
        protected $cache;
        /** @var HashBagOStuff Script instance PHP cache */
@@ -69,18 +73,18 @@ class WANObjectCache {
        protected $pool;
        /** @var EventRelayer Bus that handles purge broadcasts */
        protected $relayer;
+       /** @var LoggerInterface */
+       protected $logger;
 
        /** @var int ERR_* constant for the "last error" registry */
        protected $lastRelayError = self::ERR_NONE;
 
        /** 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
@@ -125,12 +129,18 @@ class WANObjectCache {
         *   - cache   : BagOStuff object
         *   - pool    : pool name
         *   - relayer : EventRelayer object
+        *   - logger  : LoggerInterface object
         */
        public function __construct( array $params ) {
                $this->cache = $params['cache'];
                $this->pool = $params['pool'];
                $this->relayer = $params['relayer'];
                $this->procCache = new HashBagOStuff();
+               $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->logger = $logger;
        }
 
        /**
@@ -300,6 +310,10 @@ class WANObjectCache {
         *               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.
+        *               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 possible snapshot lag is detected,
         *               then stash the value into a temporary location
         *               with this TTL. This is only useful if the reads
@@ -311,19 +325,34 @@ class WANObjectCache {
                $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." );
 
-               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;
+                       return true; // no-op the write for being unsafe
                }
-
-               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 );
                        }
+                       // 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
+                               return true; // no-op the write for being unsafe
+                       }
                }
 
                $wrapped = $this->wrap( $value, $ttl );