MediaWiki  master
WANObjectCache.php
Go to the documentation of this file.
1 <?php
22 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerInterface;
25 use Psr\Log\NullLogger;
28 
120 class WANObjectCache implements
124  LoggerAwareInterface
125 {
127  protected $cache;
129  protected $processCaches = [];
131  protected $logger;
133  protected $stats;
135  protected $asyncHandler;
136 
138  protected $mcrouterAware;
140  protected $region;
142  protected $cluster;
144  protected $useInterimHoldOffCaching = true;
146  protected $epoch;
148  protected $secret;
150  protected $coalesceKeys;
152  protected $coalesceScheme;
153 
155  private $keyHighQps;
158 
160  private $callbackDepth = 0;
162  private $warmupCache = [];
164  private $warmupKeyMisses = 0;
165 
168 
170  private const MAX_COMMIT_DELAY = 3;
172  private const MAX_READ_LAG = 7;
174  public const HOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
175 
177  private const LOW_TTL = 30;
179  public const TTL_LAGGED = 30;
180 
182  private const HOT_TTR = 900;
184  private const AGE_NEW = 60;
185 
187  private const TSE_NONE = -1;
188 
190  private const STALE_TTL_NONE = 0;
192  private const GRACE_TTL_NONE = 0;
194  public const HOLDOFF_TTL_NONE = 0;
196  public const HOLDOFF_NONE = self::HOLDOFF_TTL_NONE;
197 
199  public const MIN_TIMESTAMP_NONE = 0.0;
200 
202  private const PC_PRIMARY = 'primary:1000';
203 
205  public const PASS_BY_REF = -1;
206 
208  private const SCHEME_HASH_TAG = 1;
210  private const SCHEME_HASH_STOP = 2;
211 
213  private static $CHECK_KEY_TTL = self::TTL_YEAR;
215  private static $INTERIM_KEY_TTL = 1;
216 
218  private static $LOCK_TTL = 10;
220  private static $COOLOFF_TTL = 1;
222  private static $RAMPUP_TTL = 30;
223 
225  private static $TINY_NEGATIVE = -0.000001;
227  private static $TINY_POSTIVE = 0.000001;
228 
230  private static $RECENT_SET_LOW_MS = 50;
232  private static $RECENT_SET_HIGH_MS = 100;
233 
235  private static $GENERATION_SLOW_SEC = 3;
236 
238  private static $PURGE_TIME = 0;
240  private static $PURGE_HOLDOFF = 1;
241 
243  private static $VERSION = 1;
244 
246  private static $FLD_FORMAT_VERSION = 0;
248  private static $FLD_VALUE = 1;
250  private static $FLD_TTL = 2;
252  private static $FLD_TIME = 3;
254  private static $FLD_FLAGS = 4;
256  private static $FLD_VALUE_VERSION = 5;
258  private static $FLD_GENERATION_TIME = 6;
259 
261  private static $TYPE_VALUE = 'v';
263  private static $TYPE_TIMESTAMP = 't';
265  private static $TYPE_MUTEX = 'm';
267  private static $TYPE_INTERIM = 'i';
269  private static $TYPE_COOLOFF = 'c';
270 
272  private static $PURGE_VAL_PREFIX = 'PURGED:';
273 
310  public function __construct( array $params ) {
311  $this->cache = $params['cache'];
312  $this->region = $params['region'] ?? 'main';
313  $this->cluster = $params['cluster'] ?? 'wan-main';
314  $this->mcrouterAware = !empty( $params['mcrouterAware'] );
315  $this->epoch = $params['epoch'] ?? 0;
316  $this->secret = $params['secret'] ?? (string)$this->epoch;
317  $this->coalesceKeys = $params['coalesceKeys'] ?? false;
318  if ( !empty( $params['mcrouterAware'] ) ) {
319  // https://github.com/facebook/mcrouter/wiki/Key-syntax
320  $this->coalesceScheme = self::SCHEME_HASH_STOP;
321  } else {
322  // https://redis.io/topics/cluster-spec
323  // https://github.com/twitter/twemproxy/blob/v0.4.1/notes/recommendation.md#hash-tags
324  // https://github.com/Netflix/dynomite/blob/v0.7.0/notes/recommendation.md#hash-tags
325  $this->coalesceScheme = self::SCHEME_HASH_TAG;
326  }
327 
328  $this->keyHighQps = $params['keyHighQps'] ?? 100;
329  $this->keyHighUplinkBps = $params['keyHighUplinkBps'] ?? ( 1e9 / 8 / 100 );
330 
331  $this->setLogger( $params['logger'] ?? new NullLogger() );
332  $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
333  $this->asyncHandler = $params['asyncHandler'] ?? null;
334  }
335 
339  public function setLogger( LoggerInterface $logger ) {
340  $this->logger = $logger;
341  }
342 
348  public static function newEmpty() {
349  return new static( [ 'cache' => new EmptyBagOStuff() ] );
350  }
351 
402  final public function get(
403  $key, &$curTTL = null, array $checkKeys = [], &$info = null
404  ) {
405  $curTTLs = self::PASS_BY_REF;
406  $infoByKey = self::PASS_BY_REF;
407  $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $infoByKey );
408 
409  $curTTL = $curTTLs[$key] ?? null;
410  if ( $info === self::PASS_BY_REF ) {
411  $info = [
412  'asOf' => $infoByKey[$key]['asOf'] ?? null,
413  'tombAsOf' => $infoByKey[$key]['tombAsOf'] ?? null,
414  'lastCKPurge' => $infoByKey[$key]['lastCKPurge'] ?? null,
415  'version' => $infoByKey[$key]['version'] ?? null
416  ];
417  } else {
418  $info = $infoByKey[$key]['asOf'] ?? null; // b/c
419  }
420 
421  return array_key_exists( $key, $values ) ? $values[$key] : false;
422  }
423 
446  final public function getMulti(
447  array $keys,
448  &$curTTLs = [],
449  array $checkKeys = [],
450  &$info = null
451  ) {
452  $result = [];
453  $curTTLs = [];
454  $infoByKey = [];
455 
456  // Order-corresponding list of value keys for the provided base keys
457  $valueKeys = $this->makeSisterKeys( $keys, self::$TYPE_VALUE );
458 
459  $fullKeysNeeded = $valueKeys;
460  $checkKeysForAll = [];
461  $checkKeysByKey = [];
462  foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
463  // Note: avoid array_merge() inside loop in case there are many keys
464  if ( is_int( $i ) ) {
465  // Single check key that applies to all value keys
466  $fullKey = $this->makeSisterKey( $checkKeyOrKeyGroup, self::$TYPE_TIMESTAMP );
467  $fullKeysNeeded[] = $fullKey;
468  $checkKeysForAll[] = $fullKey;
469  } else {
470  // List of check keys that apply to a specific value key
471  foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
472  $fullKey = $this->makeSisterKey( $checkKey, self::$TYPE_TIMESTAMP );
473  $fullKeysNeeded[] = $fullKey;
474  $checkKeysByKey[$i][] = $fullKey;
475  }
476  }
477  }
478 
479  if ( $this->warmupCache ) {
480  // Get the raw values of the keys from the warmup cache
481  $wrappedValues = $this->warmupCache;
482  $fullKeysMissing = array_diff( $fullKeysNeeded, array_keys( $wrappedValues ) );
483  if ( $fullKeysMissing ) { // sanity
484  $this->warmupKeyMisses += count( $fullKeysMissing );
485  $wrappedValues += $this->cache->getMulti( $fullKeysMissing );
486  }
487  } else {
488  // Fetch the raw values of the keys from the backend
489  $wrappedValues = $this->cache->getMulti( $fullKeysNeeded );
490  }
491 
492  // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
493  $now = $this->getCurrentTime();
494 
495  // Collect timestamps from all "check" keys
496  $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
497  $purgeValuesByKey = [];
498  foreach ( $checkKeysByKey as $cacheKey => $checks ) {
499  $purgeValuesByKey[$cacheKey] = $this->processCheckKeys( $checks, $wrappedValues, $now );
500  }
501 
502  // Get the main cache value for each key and validate them
503  reset( $keys );
504  foreach ( $valueKeys as $i => $vKey ) {
505  // Get the corresponding base key for this value key
506  $key = current( $keys );
507  next( $keys );
508 
509  list( $value, $keyInfo ) = $this->unwrap(
510  array_key_exists( $vKey, $wrappedValues ) ? $wrappedValues[$vKey] : false,
511  $now
512  );
513  // Force dependent keys to be seen as stale for a while after purging
514  // to reduce race conditions involving stale data getting cached
515  $purgeValues = $purgeValuesForAll;
516  if ( isset( $purgeValuesByKey[$key] ) ) {
517  $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
518  }
519 
520  $lastCKPurge = null; // timestamp of the highest check key
521  foreach ( $purgeValues as $purge ) {
522  $lastCKPurge = max( $purge[self::$PURGE_TIME], $lastCKPurge );
523  $safeTimestamp = $purge[self::$PURGE_TIME] + $purge[self::$PURGE_HOLDOFF];
524  if ( $value !== false && $safeTimestamp >= $keyInfo['asOf'] ) {
525  // How long ago this value was invalidated by *this* check key
526  $ago = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
527  // How long ago this value was invalidated by *any* known check key
528  $keyInfo['curTTL'] = min( $keyInfo['curTTL'], $ago );
529  }
530  }
531  $keyInfo[ 'lastCKPurge'] = $lastCKPurge;
532 
533  if ( $value !== false ) {
534  $result[$key] = $value;
535  }
536  if ( $keyInfo['curTTL'] !== null ) {
537  $curTTLs[$key] = $keyInfo['curTTL'];
538  }
539 
540  $infoByKey[$key] = ( $info === self::PASS_BY_REF )
541  ? $keyInfo
542  : $keyInfo['asOf']; // b/c
543  }
544 
545  $info = $infoByKey;
546 
547  return $result;
548  }
549 
557  private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
558  $purgeValues = [];
559  foreach ( $timeKeys as $timeKey ) {
560  $purge = isset( $wrappedValues[$timeKey] )
561  ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
562  : false;
563  if ( $purge === false ) {
564  // Key is not set or malformed; regenerate
565  $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
566  $this->cache->add( $timeKey, $newVal, self::$CHECK_KEY_TTL );
567  $purge = $this->parsePurgeValue( $newVal );
568  }
569  $purgeValues[] = $purge;
570  }
571 
572  return $purgeValues;
573  }
574 
651  final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
652  $now = $this->getCurrentTime();
653  $lag = $opts['lag'] ?? 0;
654  $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
655  $pending = $opts['pending'] ?? false;
656  $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
657  $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
658  $creating = $opts['creating'] ?? false;
659  $version = $opts['version'] ?? null;
660  $walltime = $opts['walltime'] ?? null;
661 
662  if ( $ttl < 0 ) {
663  return true; // not cacheable
664  }
665 
666  // Do not cache potentially uncommitted data as it might get rolled back
667  if ( $pending ) {
668  $this->logger->info(
669  'Rejected set() for {cachekey} due to pending writes.',
670  [ 'cachekey' => $key ]
671  );
672 
673  return true; // no-op the write for being unsafe
674  }
675 
676  // Check if there is a risk of caching (stale) data that predates the last delete()
677  // tombstone due to the tombstone having expired. If so, then the behavior should depend
678  // on whether the problem is specific to this regeneration attempt or systemically affects
679  // attempts to regenerate this key. For systemic cases, the cache writes should set a low
680  // TTL so that the value at least remains cacheable. For non-systemic cases, the cache
681  // write can simply be rejected.
682  if ( $age > self::MAX_READ_LAG ) {
683  // Case A: high snapshot lag
684  if ( $walltime === null ) {
685  // Case A0: high snapshot lag without regeneration wall time info.
686  // Probably systemic; use a low TTL to avoid stampedes/uncacheability.
687  $mitigated = 'snapshot lag';
688  $mitigationTTL = self::TTL_SECOND;
689  } elseif ( ( $age - $walltime ) > self::MAX_READ_LAG ) {
690  // Case A1: value regeneration during an already long-running transaction.
691  // Probably non-systemic; rely on a less problematic regeneration attempt.
692  $mitigated = 'snapshot lag (late regeneration)';
693  $mitigationTTL = self::TTL_UNCACHEABLE;
694  } else {
695  // Case A2: value regeneration takes a long time.
696  // Probably systemic; use a low TTL to avoid stampedes/uncacheability.
697  $mitigated = 'snapshot lag (high regeneration time)';
698  $mitigationTTL = self::TTL_SECOND;
699  }
700  } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
701  // Case B: high replication lag without high snapshot lag
702  // Probably systemic; use a low TTL to avoid stampedes/uncacheability
703  $mitigated = 'replication lag';
704  $mitigationTTL = self::TTL_LAGGED;
705  } elseif ( ( $lag + $age ) > self::MAX_READ_LAG ) {
706  // Case C: medium length request with medium replication lag
707  // Probably non-systemic; rely on a less problematic regeneration attempt
708  $mitigated = 'read lag';
709  $mitigationTTL = self::TTL_UNCACHEABLE;
710  } else {
711  // New value generated with recent enough data
712  $mitigated = null;
713  $mitigationTTL = null;
714  }
715 
716  if ( $mitigationTTL === self::TTL_UNCACHEABLE ) {
717  $this->logger->warning(
718  "Rejected set() for {cachekey} due to $mitigated.",
719  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age, 'walltime' => $walltime ]
720  );
721 
722  return true; // no-op the write for being unsafe
723  }
724 
725  // TTL to use in staleness checks (does not effect persistence layer TTL)
726  $logicalTTL = null;
727 
728  if ( $mitigationTTL !== null ) {
729  // New value generated from data that is old enough to be risky
730  if ( $lockTSE >= 0 ) {
731  // Value will have the normal expiry but will be seen as stale sooner
732  $logicalTTL = min( $ttl ?: INF, $mitigationTTL );
733  } else {
734  // Value expires sooner (leaving enough TTL for preemptive refresh)
735  $ttl = min( $ttl ?: INF, max( $mitigationTTL, self::LOW_TTL ) );
736  }
737 
738  $this->logger->warning(
739  "Lowered set() TTL for {cachekey} due to $mitigated.",
740  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age, 'walltime' => $walltime ]
741  );
742  }
743 
744  // Wrap that value with time/TTL/version metadata
745  $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now, $walltime );
746  $storeTTL = $ttl + $staleTTL;
747 
748  if ( $creating ) {
749  $ok = $this->cache->add(
750  $this->makeSisterKey( $key, self::$TYPE_VALUE ),
751  $wrapped,
752  $storeTTL
753  );
754  } else {
755  $ok = $this->cache->merge(
756  $this->makeSisterKey( $key, self::$TYPE_VALUE ),
757  function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
758  // A string value means that it is a tombstone; do nothing in that case
759  return ( is_string( $cWrapped ) ) ? false : $wrapped;
760  },
761  $storeTTL,
762  1 // 1 attempt
763  );
764  }
765 
766  return $ok;
767  }
768 
830  final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
831  if ( $ttl <= 0 ) {
832  // Publish the purge to all datacenters
833  $ok = $this->relayDelete( $this->makeSisterKey( $key, self::$TYPE_VALUE ) );
834  } else {
835  // Publish the purge to all datacenters
836  $ok = $this->relayPurge(
837  $this->makeSisterKey( $key, self::$TYPE_VALUE ),
838  $ttl,
839  self::HOLDOFF_TTL_NONE
840  );
841  }
842 
843  $kClass = $this->determineKeyClassForStats( $key );
844  $this->stats->increment( "wanobjectcache.$kClass.delete." . ( $ok ? 'ok' : 'error' ) );
845 
846  return $ok;
847  }
848 
868  final public function getCheckKeyTime( $key ) {
869  return $this->getMultiCheckKeyTime( [ $key ] )[$key];
870  }
871 
933  final public function getMultiCheckKeyTime( array $keys ) {
934  $rawKeys = [];
935  foreach ( $keys as $key ) {
936  $rawKeys[$key] = $this->makeSisterKey( $key, self::$TYPE_TIMESTAMP );
937  }
938 
939  $rawValues = $this->cache->getMulti( $rawKeys );
940  $rawValues += array_fill_keys( $rawKeys, false );
941 
942  $times = [];
943  foreach ( $rawKeys as $key => $rawKey ) {
944  $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
945  if ( $purge !== false ) {
946  $time = $purge[self::$PURGE_TIME];
947  } else {
948  // Casting assures identical floats for the next getCheckKeyTime() calls
949  $now = (string)$this->getCurrentTime();
950  $this->cache->add(
951  $rawKey,
952  $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
954  );
955  $time = (float)$now;
956  }
957 
958  $times[$key] = $time;
959  }
960 
961  return $times;
962  }
963 
998  final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
999  // Publish the purge to all datacenters
1000  $ok = $this->relayPurge(
1001  $this->makeSisterKey( $key, self::$TYPE_TIMESTAMP ),
1002  self::$CHECK_KEY_TTL,
1003  $holdoff
1004  );
1005 
1006  $kClass = $this->determineKeyClassForStats( $key );
1007  $this->stats->increment( "wanobjectcache.$kClass.ck_touch." . ( $ok ? 'ok' : 'error' ) );
1008 
1009  return $ok;
1010  }
1011 
1039  final public function resetCheckKey( $key ) {
1040  // Publish the purge to all datacenters
1041  $ok = $this->relayDelete( $this->makeSisterKey( $key, self::$TYPE_TIMESTAMP ) );
1042 
1043  $kClass = $this->determineKeyClassForStats( $key );
1044  $this->stats->increment( "wanobjectcache.$kClass.ck_reset." . ( $ok ? 'ok' : 'error' ) );
1045 
1046  return $ok;
1047  }
1048 
1356  final public function getWithSetCallback(
1357  $key, $ttl, $callback, array $opts = [], array $cbParams = []
1358  ) {
1359  $version = $opts['version'] ?? null;
1360  $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1361  $pCache = ( $pcTTL >= 0 )
1362  ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
1363  : null;
1364 
1365  // Use the process cache if requested as long as no outer cache callback is running.
1366  // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
1367  // process cached values are more lagged than persistent ones as they are not purged.
1368  if ( $pCache && $this->callbackDepth == 0 ) {
1369  $cached = $pCache->get( $this->getProcessCacheKey( $key, $version ), $pcTTL, false );
1370  if ( $cached !== false ) {
1371  $this->logger->debug( "getWithSetCallback($key): process cache hit" );
1372  return $cached;
1373  }
1374  }
1375 
1376  $res = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
1377  list( $value, $valueVersion, $curAsOf ) = $res;
1378  if ( $valueVersion !== $version ) {
1379  // Current value has a different version; use the variant key for this version.
1380  // Regenerate the variant value if it is not newer than the main value at $key
1381  // so that purges to the main key propagate to the variant value.
1382  $this->logger->debug( "getWithSetCallback($key): using variant key" );
1383  list( $value ) = $this->fetchOrRegenerate(
1384  $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
1385  $ttl,
1386  $callback,
1387  [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts,
1388  $cbParams
1389  );
1390  }
1391 
1392  // Update the process cache if enabled
1393  if ( $pCache && $value !== false ) {
1394  $pCache->set( $this->getProcessCacheKey( $key, $version ), $value );
1395  }
1396 
1397  return $value;
1398  }
1399 
1416  private function fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams ) {
1417  $checkKeys = $opts['checkKeys'] ?? [];
1418  $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
1419  $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
1420  $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
1421  $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
1422  $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
1423  $touchedCb = $opts['touchedCallback'] ?? null;
1424  $initialTime = $this->getCurrentTime();
1425 
1426  $kClass = $this->determineKeyClassForStats( $key );
1427 
1428  // Get the current key value and its metadata
1429  $curTTL = self::PASS_BY_REF;
1430  $curInfo = self::PASS_BY_REF;
1431  $curValue = $this->get( $key, $curTTL, $checkKeys, $curInfo );
1433  '@phan-var array $curInfo';
1434  // Apply any $touchedCb invalidation timestamp to get the "last purge timestamp"
1435  list( $curTTL, $LPT ) = $this->resolveCTL( $curValue, $curTTL, $curInfo, $touchedCb );
1436  // Use the cached value if it exists and is not due for synchronous regeneration
1437  if (
1438  $this->isValid( $curValue, $curInfo['asOf'], $minAsOf ) &&
1439  $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
1440  ) {
1441  $preemptiveRefresh = (
1442  $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
1443  $this->worthRefreshPopular( $curInfo['asOf'], $ageNew, $hotTTR, $initialTime )
1444  );
1445  if ( !$preemptiveRefresh ) {
1446  $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
1447 
1448  return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
1449  } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts, $cbParams ) ) {
1450  $this->logger->debug( "fetchOrRegenerate($key): hit with async refresh" );
1451  $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
1452 
1453  return [ $curValue, $curInfo['version'], $curInfo['asOf'] ];
1454  } else {
1455  $this->logger->debug( "fetchOrRegenerate($key): hit with sync refresh" );
1456  }
1457  }
1458 
1459  // Determine if there is stale or volatile cached value that is still usable
1460  $isKeyTombstoned = ( $curInfo['tombAsOf'] !== null );
1461  if ( $isKeyTombstoned ) {
1462  // Key is write-holed; use the (volatile) interim key as an alternative
1463  list( $possValue, $possInfo ) = $this->getInterimValue( $key, $minAsOf );
1464  // Update the "last purge time" since the $touchedCb timestamp depends on $value
1465  $LPT = $this->resolveTouched( $possValue, $LPT, $touchedCb );
1466  } else {
1467  $possValue = $curValue;
1468  $possInfo = $curInfo;
1469  }
1470 
1471  // Avoid overhead from callback runs, regeneration locks, and cache sets during
1472  // hold-off periods for the key by reusing very recently generated cached values
1473  if (
1474  $this->isValid( $possValue, $possInfo['asOf'], $minAsOf, $LPT ) &&
1475  $this->isVolatileValueAgeNegligible( $initialTime - $possInfo['asOf'] )
1476  ) {
1477  $this->logger->debug( "fetchOrRegenerate($key): volatile hit" );
1478  $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
1479 
1480  return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
1481  }
1482 
1483  $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
1484  $busyValue = $opts['busyValue'] ?? null;
1485  $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
1486  $version = $opts['version'] ?? null;
1487 
1488  // Determine whether one thread per datacenter should handle regeneration at a time
1489  $useRegenerationLock =
1490  // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
1491  // deduce the key hotness because |$curTTL| will always keep increasing until the
1492  // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
1493  // is not set, constant regeneration of a key for the tombstone lifetime might be
1494  // very expensive. Assume tombstoned keys are possibly hot in order to reduce
1495  // the risk of high regeneration load after the delete() method is called.
1496  $isKeyTombstoned ||
1497  // Assume a key is hot if requested soon ($lockTSE seconds) after invalidation.
1498  // This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
1499  ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE ) ||
1500  // Assume a key is hot if there is no value and a busy fallback is given.
1501  // This avoids stampedes on eviction or preemptive regeneration taking too long.
1502  ( $busyValue !== null && $possValue === false );
1503 
1504  // If a regeneration lock is required, threads that do not get the lock will try to use
1505  // the stale value, the interim value, or the $busyValue placeholder, in that order. If
1506  // none of those are set then all threads will bypass the lock and regenerate the value.
1507  $hasLock = $useRegenerationLock && $this->claimStampedeLock( $key );
1508  if ( $useRegenerationLock && !$hasLock ) {
1509  if ( $this->isValid( $possValue, $possInfo['asOf'], $minAsOf ) ) {
1510  $this->logger->debug( "fetchOrRegenerate($key): returning stale value" );
1511  $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
1512 
1513  return [ $possValue, $possInfo['version'], $curInfo['asOf'] ];
1514  } elseif ( $busyValue !== null ) {
1515  $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1516  $this->logger->debug( "fetchOrRegenerate($key): busy $miss" );
1517  $this->stats->increment( "wanobjectcache.$kClass.$miss.busy" );
1518 
1519  return [ $this->resolveBusyValue( $busyValue ), $version, $curInfo['asOf'] ];
1520  }
1521  }
1522 
1523  // Generate the new value given any prior value with a matching version
1524  $setOpts = [];
1525  $preCallbackTime = $this->getCurrentTime();
1527  try {
1528  $value = $callback(
1529  ( $curInfo['version'] === $version ) ? $curValue : false,
1530  $ttl,
1531  $setOpts,
1532  ( $curInfo['version'] === $version ) ? $curInfo['asOf'] : null,
1533  $cbParams
1534  );
1535  } finally {
1537  }
1538  $postCallbackTime = $this->getCurrentTime();
1539 
1540  // How long it took to fetch, validate, and generate the value
1541  $elapsed = max( $postCallbackTime - $initialTime, 0.0 );
1542 
1543  // Attempt to save the newly generated value if applicable
1544  if (
1545  // Callback yielded a cacheable value
1546  ( $value !== false && $ttl >= 0 ) &&
1547  // Current thread was not raced out of a regeneration lock or key is tombstoned
1548  ( !$useRegenerationLock || $hasLock || $isKeyTombstoned ) &&
1549  // Key does not appear to be undergoing a set() stampede
1550  $this->checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock )
1551  ) {
1552  // How long it took to generate the value
1553  $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
1554  $this->stats->timing( "wanobjectcache.$kClass.regen_walltime", 1e3 * $walltime );
1555  // If the key is write-holed then use the (volatile) interim key as an alternative
1556  if ( $isKeyTombstoned ) {
1557  $this->setInterimValue( $key, $value, $lockTSE, $version, $walltime );
1558  } else {
1559  $finalSetOpts = [
1560  // @phan-suppress-next-line PhanUselessBinaryAddRight
1561  'since' => $setOpts['since'] ?? $preCallbackTime,
1562  'version' => $version,
1563  'staleTTL' => $staleTTL,
1564  'lockTSE' => $lockTSE, // informs lag vs performance trade-offs
1565  'creating' => ( $curValue === false ), // optimization
1566  'walltime' => $walltime
1567  ] + $setOpts;
1568  $this->set( $key, $value, $ttl, $finalSetOpts );
1569  }
1570  }
1571 
1572  $this->yieldStampedeLock( $key, $hasLock );
1573 
1574  $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1575  $this->logger->debug( "fetchOrRegenerate($key): $miss, new value computed" );
1576  $this->stats->increment( "wanobjectcache.$kClass.$miss.compute" );
1577 
1578  return [ $value, $version, $curInfo['asOf'] ];
1579  }
1580 
1585  private function claimStampedeLock( $key ) {
1586  // Note that locking is not bypassed due to I/O errors; this avoids stampedes
1587  return $this->cache->add(
1588  $this->makeSisterKey( $key, self::$TYPE_MUTEX ),
1589  1,
1590  self::$LOCK_TTL
1591  );
1592  }
1593 
1598  private function yieldStampedeLock( $key, $hasLock ) {
1599  if ( $hasLock ) {
1600  // The backend might be a mcrouter proxy set to broadcast DELETE to *all* the local
1601  // datacenter cache servers via OperationSelectorRoute (for increased consistency).
1602  // Since that would be excessive for these locks, use TOUCH to expire the key.
1603  $this->cache->changeTTL(
1604  $this->makeSisterKey( $key, self::$TYPE_MUTEX ),
1605  $this->getCurrentTime() - 60
1606  );
1607  }
1608  }
1609 
1617  private function makeSisterKeys( array $baseKeys, $type ) {
1618  $keys = [];
1619  foreach ( $baseKeys as $baseKey ) {
1620  $keys[] = $this->makeSisterKey( $baseKey, $type );
1621  }
1622 
1623  return $keys;
1624  }
1625 
1633  private function makeSisterKey( $baseKey, $typeChar ) {
1634  if ( $this->coalesceKeys === 'non-global' ) {
1635  $useColocationScheme = ( strncmp( $baseKey, "global:", 7 ) !== 0 );
1636  } else {
1637  $useColocationScheme = ( $this->coalesceKeys === true );
1638  }
1639 
1640  if ( !$useColocationScheme ) {
1641  // Old key style: "WANCache:<character>:<base key>"
1642  $fullKey = 'WANCache:' . $typeChar . ':' . $baseKey;
1643  } elseif ( $this->coalesceScheme === self::SCHEME_HASH_STOP ) {
1644  // Key style: "WANCache:<base key>|#|<character>"
1645  $fullKey = 'WANCache:' . $baseKey . '|#|' . $typeChar;
1646  } else {
1647  // Key style: "WANCache:{<base key>}:<character>"
1648  $fullKey = 'WANCache:{' . $baseKey . '}:' . $typeChar;
1649  }
1650 
1651  return $fullKey;
1652  }
1653 
1658  private function isVolatileValueAgeNegligible( $age ) {
1659  return ( $age < mt_rand( self::$RECENT_SET_LOW_MS, self::$RECENT_SET_HIGH_MS ) / 1e3 );
1660  }
1661 
1683  private function checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock ) {
1684  $valueKey = $this->makeSisterKey( $key, self::$TYPE_VALUE );
1685  list( $estimatedSize ) = $this->cache->setNewPreparedValues( [ $valueKey => $value ] );
1686 
1687  if ( !$hasLock ) {
1688  // Suppose that this cache key is very popular (KEY_HIGH_QPS reads/second).
1689  // After eviction, there will be cache misses until it gets regenerated and saved.
1690  // If the time window when the key is missing lasts less than one second, then the
1691  // number of misses will not reach KEY_HIGH_QPS. This window largely corresponds to
1692  // the key regeneration time. Estimate the count/rate of cache misses, e.g.:
1693  // - 100 QPS, 20ms regeneration => ~2 misses (< 1s)
1694  // - 100 QPS, 100ms regeneration => ~10 misses (< 1s)
1695  // - 100 QPS, 3000ms regeneration => ~300 misses (100/s for 3s)
1696  $missesPerSecForHighQPS = ( min( $elapsed, 1 ) * $this->keyHighQps );
1697 
1698  // Determine whether there is enough I/O stampede risk to justify throttling set().
1699  // Estimate unthrottled set() overhead, as bps, from miss count/rate and value size,
1700  // comparing it to the per-key uplink bps limit (KEY_HIGH_UPLINK_BPS), e.g.:
1701  // - 2 misses (< 1s), 10KB value, 1250000 bps limit => 160000 bits (low risk)
1702  // - 2 misses (< 1s), 100KB value, 1250000 bps limit => 1600000 bits (high risk)
1703  // - 10 misses (< 1s), 10KB value, 1250000 bps limit => 800000 bits (low risk)
1704  // - 10 misses (< 1s), 100KB value, 1250000 bps limit => 8000000 bits (high risk)
1705  // - 300 misses (100/s), 1KB value, 1250000 bps limit => 800000 bps (low risk)
1706  // - 300 misses (100/s), 10KB value, 1250000 bps limit => 8000000 bps (high risk)
1707  // - 300 misses (100/s), 100KB value, 1250000 bps limit => 80000000 bps (high risk)
1708  if ( ( $missesPerSecForHighQPS * $estimatedSize ) >= $this->keyHighUplinkBps ) {
1709  $this->cache->clearLastError();
1710  if (
1711  !$this->cache->add(
1712  $this->makeSisterKey( $key, self::$TYPE_COOLOFF ),
1713  1,
1714  self::$COOLOFF_TTL
1715  ) &&
1716  // Don't treat failures due to I/O errors as the key being in cooloff
1717  $this->cache->getLastError() === BagOStuff::ERR_NONE
1718  ) {
1719  $this->stats->increment( "wanobjectcache.$kClass.cooloff_bounce" );
1720 
1721  return false;
1722  }
1723  }
1724  }
1725 
1726  // Corresponding metrics for cache writes that actually get sent over the write
1727  $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1e3 * $elapsed );
1728  $this->stats->updateCount( "wanobjectcache.$kClass.regen_set_bytes", $estimatedSize );
1729 
1730  return true;
1731  }
1732 
1741  private function resolveCTL( $value, $curTTL, $curInfo, $touchedCallback ) {
1742  if ( $touchedCallback === null || $value === false ) {
1743  return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'] ) ];
1744  }
1745 
1746  $touched = $touchedCallback( $value );
1747  if ( $touched !== null && $touched >= $curInfo['asOf'] ) {
1748  $curTTL = min( $curTTL, self::$TINY_NEGATIVE, $curInfo['asOf'] - $touched );
1749  }
1750 
1751  return [ $curTTL, max( $curInfo['tombAsOf'], $curInfo['lastCKPurge'], $touched ) ];
1752  }
1753 
1761  private function resolveTouched( $value, $lastPurge, $touchedCallback ) {
1762  return ( $touchedCallback === null || $value === false )
1763  ? $lastPurge // nothing to derive the "touched timestamp" from
1764  : max( $touchedCallback( $value ), $lastPurge );
1765  }
1766 
1772  private function getInterimValue( $key, $minAsOf ) {
1773  $now = $this->getCurrentTime();
1774 
1775  if ( $this->useInterimHoldOffCaching ) {
1776  $wrapped = $this->cache->get(
1777  $this->makeSisterKey( $key, self::$TYPE_INTERIM )
1778  );
1779 
1780  list( $value, $keyInfo ) = $this->unwrap( $wrapped, $now );
1781  if ( $this->isValid( $value, $keyInfo['asOf'], $minAsOf ) ) {
1782  return [ $value, $keyInfo ];
1783  }
1784  }
1785 
1786  return $this->unwrap( false, $now );
1787  }
1788 
1796  private function setInterimValue( $key, $value, $ttl, $version, $walltime ) {
1797  $ttl = max( self::$INTERIM_KEY_TTL, (int)$ttl );
1798 
1799  $wrapped = $this->wrap( $value, $ttl, $version, $this->getCurrentTime(), $walltime );
1800  $this->cache->merge(
1801  $this->makeSisterKey( $key, self::$TYPE_INTERIM ),
1802  function () use ( $wrapped ) {
1803  return $wrapped;
1804  },
1805  $ttl,
1806  1
1807  );
1808  }
1809 
1814  private function resolveBusyValue( $busyValue ) {
1815  return ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
1816  }
1817 
1882  final public function getMultiWithSetCallback(
1883  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1884  ) {
1885  // Load required keys into process cache in one go
1886  $this->warmupCache = $this->getRawKeysForWarmup(
1887  $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
1888  $opts['checkKeys'] ?? []
1889  );
1890  $this->warmupKeyMisses = 0;
1891 
1892  // The required callback signature includes $id as the first argument for convenience
1893  // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
1894  // callback with a proxy callback that has the standard getWithSetCallback() signature.
1895  // This is defined only once per batch to avoid closure creation overhead.
1896  $proxyCb = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params ) use ( $callback ) {
1897  return $callback( $params['id'], $oldValue, $ttl, $setOpts, $oldAsOf );
1898  };
1899 
1900  $values = [];
1901  foreach ( $keyedIds as $key => $id ) { // preserve order
1902  $values[$key] = $this->getWithSetCallback(
1903  $key,
1904  $ttl,
1905  $proxyCb,
1906  $opts,
1907  [ 'id' => $id ]
1908  );
1909  }
1910 
1911  $this->warmupCache = [];
1912 
1913  return $values;
1914  }
1915 
1981  final public function getMultiWithUnionSetCallback(
1982  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1983  ) {
1984  $checkKeys = $opts['checkKeys'] ?? [];
1985  unset( $opts['lockTSE'] ); // incompatible
1986  unset( $opts['busyValue'] ); // incompatible
1987 
1988  // Load required keys into process cache in one go
1989  $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
1990  $this->warmupCache = $this->getRawKeysForWarmup( $keysByIdGet, $checkKeys );
1991  $this->warmupKeyMisses = 0;
1992 
1993  // IDs of entities known to be in need of regeneration
1994  $idsRegen = [];
1995 
1996  // Find out which keys are missing/deleted/stale
1997  $curTTLs = [];
1998  $asOfs = [];
1999  $curByKey = $this->getMulti( $keysByIdGet, $curTTLs, $checkKeys, $asOfs );
2000  foreach ( $keysByIdGet as $id => $key ) {
2001  if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
2002  $idsRegen[] = $id;
2003  }
2004  }
2005 
2006  // Run the callback to populate the regeneration value map for all required IDs
2007  $newSetOpts = [];
2008  $newTTLsById = array_fill_keys( $idsRegen, $ttl );
2009  $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
2010 
2011  // The required callback signature includes $id as the first argument for convenience
2012  // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2013  // callback with a proxy callback that has the standard getWithSetCallback() signature.
2014  // This is defined only once per batch to avoid closure creation overhead.
2015  $proxyCb = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2016  use ( $callback, $newValsById, $newTTLsById, $newSetOpts )
2017  {
2018  $id = $params['id'];
2019 
2020  if ( array_key_exists( $id, $newValsById ) ) {
2021  // Value was already regerated as expected, so use the value in $newValsById
2022  $newValue = $newValsById[$id];
2023  $ttl = $newTTLsById[$id];
2024  $setOpts = $newSetOpts;
2025  } else {
2026  // Pre-emptive/popularity refresh and version mismatch cases are not detected
2027  // above and thus $newValsById has no entry. Run $callback on this single entity.
2028  $ttls = [ $id => $ttl ];
2029  $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
2030  $ttl = $ttls[$id];
2031  }
2032 
2033  return $newValue;
2034  };
2035 
2036  // Run the cache-aside logic using warmupCache instead of persistent cache queries
2037  $values = [];
2038  foreach ( $keyedIds as $key => $id ) { // preserve order
2039  $values[$key] = $this->getWithSetCallback(
2040  $key,
2041  $ttl,
2042  $proxyCb,
2043  $opts,
2044  [ 'id' => $id ]
2045  );
2046  }
2047 
2048  $this->warmupCache = [];
2049 
2050  return $values;
2051  }
2052 
2065  final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
2066  $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
2067  $wrapped = $this->cache->get( $this->makeSisterKey( $key, self::$TYPE_VALUE ) );
2068  if ( is_array( $wrapped ) && $wrapped[self::$FLD_TIME] < $minAsOf ) {
2069  $isStale = true;
2070  $this->logger->warning( "Reaping stale value key '$key'." );
2071  $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
2072  $ok = $this->cache->changeTTL(
2073  $this->makeSisterKey( $key, self::$TYPE_VALUE ),
2074  $ttlReap
2075  );
2076  if ( !$ok ) {
2077  $this->logger->error( "Could not complete reap of key '$key'." );
2078  }
2079 
2080  return $ok;
2081  }
2082 
2083  $isStale = false;
2084 
2085  return true;
2086  }
2087 
2097  final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
2098  $purge = $this->parsePurgeValue(
2099  $this->cache->get( $this->makeSisterKey( $key, self::$TYPE_TIMESTAMP ) )
2100  );
2101  if ( $purge && $purge[self::$PURGE_TIME] < $purgeTimestamp ) {
2102  $isStale = true;
2103  $this->logger->warning( "Reaping stale check key '$key'." );
2104  $ok = $this->cache->changeTTL(
2105  $this->makeSisterKey( $key, self::$TYPE_TIMESTAMP ),
2106  self::TTL_SECOND
2107  );
2108  if ( !$ok ) {
2109  $this->logger->error( "Could not complete reap of check key '$key'." );
2110  }
2111 
2112  return $ok;
2113  }
2114 
2115  $isStale = false;
2116 
2117  return false;
2118  }
2119 
2127  public function makeKey( $class, ...$components ) {
2128  return $this->cache->makeKey( ...func_get_args() );
2129  }
2130 
2138  public function makeGlobalKey( $class, ...$components ) {
2139  return $this->cache->makeGlobalKey( ...func_get_args() );
2140  }
2141 
2149  public function hash256( $component ) {
2150  return hash_hmac( 'sha256', $component, $this->secret );
2151  }
2152 
2203  final public function makeMultiKeys( array $ids, $keyCallback ) {
2204  $idByKey = [];
2205  foreach ( $ids as $id ) {
2206  // Discourage triggering of automatic makeKey() hashing in some backends
2207  if ( strlen( $id ) > 64 ) {
2208  $this->logger->warning( __METHOD__ . ": long ID '$id'; use hash256()" );
2209  }
2210  $key = $keyCallback( $id, $this );
2211  // Edge case: ignore key collisions due to duplicate $ids like "42" and 42
2212  if ( !isset( $idByKey[$key] ) ) {
2213  $idByKey[$key] = $id;
2214  } elseif ( (string)$id !== (string)$idByKey[$key] ) {
2215  throw new UnexpectedValueException(
2216  "Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
2217  );
2218  }
2219  }
2220 
2221  return new ArrayIterator( $idByKey );
2222  }
2223 
2259  final public function multiRemap( array $ids, array $res ) {
2260  if ( count( $ids ) !== count( $res ) ) {
2261  // If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
2262  // ArrayIterator will have less entries due to "first appearance" de-duplication
2263  $ids = array_keys( array_flip( $ids ) );
2264  if ( count( $ids ) !== count( $res ) ) {
2265  throw new UnexpectedValueException( "Multi-key result does not match ID list" );
2266  }
2267  }
2268 
2269  return array_combine( $ids, $res );
2270  }
2271 
2276  final public function getLastError() {
2277  $code = $this->cache->getLastError();
2278  switch ( $code ) {
2279  case BagOStuff::ERR_NONE:
2280  return self::ERR_NONE;
2281  case BagOStuff::ERR_NO_RESPONSE:
2282  return self::ERR_NO_RESPONSE;
2283  case BagOStuff::ERR_UNREACHABLE:
2284  return self::ERR_UNREACHABLE;
2285  default:
2286  return self::ERR_UNEXPECTED;
2287  }
2288  }
2289 
2293  final public function clearLastError() {
2294  $this->cache->clearLastError();
2295  }
2296 
2302  public function clearProcessCache() {
2303  $this->processCaches = [];
2304  }
2305 
2326  final public function useInterimHoldOffCaching( $enabled ) {
2327  $this->useInterimHoldOffCaching = $enabled;
2328  }
2329 
2335  public function getQoS( $flag ) {
2336  return $this->cache->getQoS( $flag );
2337  }
2338 
2402  public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
2403  if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
2404  $mtime = (int)$mtime; // handle fractional seconds and string integers
2405  }
2406 
2407  if ( !is_int( $mtime ) || $mtime <= 0 ) {
2408  return $minTTL; // no last-modified time provided
2409  }
2410 
2411  $age = $this->getCurrentTime() - $mtime;
2412 
2413  return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
2414  }
2415 
2420  final public function getWarmupKeyMisses() {
2421  return $this->warmupKeyMisses;
2422  }
2423 
2434  protected function relayPurge( $key, $ttl, $holdoff ) {
2435  if ( $this->mcrouterAware ) {
2436  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
2437  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
2438  $ok = $this->cache->set(
2439  "/*/{$this->cluster}/{$key}",
2440  $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
2441  $ttl
2442  );
2443  } else {
2444  // Some other proxy handles broadcasting or there is only one datacenter
2445  $ok = $this->cache->set(
2446  $key,
2447  $this->makePurgeValue( $this->getCurrentTime(), $holdoff ),
2448  $ttl
2449  );
2450  }
2451 
2452  return $ok;
2453  }
2454 
2461  protected function relayDelete( $key ) {
2462  if ( $this->mcrouterAware ) {
2463  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
2464  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
2465  $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
2466  } else {
2467  // Some other proxy handles broadcasting or there is only one datacenter
2468  $ok = $this->cache->delete( $key );
2469  }
2470 
2471  return $ok;
2472  }
2473 
2485  private function scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams ) {
2486  if ( !$this->asyncHandler ) {
2487  return false;
2488  }
2489  // Update the cache value later, such during post-send of an HTTP request. This forces
2490  // cache regeneration by setting "minAsOf" to infinity, meaning that no existing value
2491  // is considered valid. Furthermore, note that preemptive regeneration is not applicable
2492  // to invalid values, so there is no risk of infinite preemptive regeneration loops.
2493  $func = $this->asyncHandler;
2494  $func( function () use ( $key, $ttl, $callback, $opts, $cbParams ) {
2495  $opts['minAsOf'] = INF;
2496  $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
2497  } );
2498 
2499  return true;
2500  }
2501 
2515  private function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
2516  if ( $curTTL > 0 ) {
2517  return true;
2518  } elseif ( $graceTTL <= 0 ) {
2519  return false;
2520  }
2521 
2522  $ageStale = abs( $curTTL ); // seconds of staleness
2523  $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
2524  if ( $curGTTL <= 0 ) {
2525  return false; // already out of grace period
2526  }
2527 
2528  // Chance of using a stale value is the complement of the chance of refreshing it
2529  return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
2530  }
2531 
2545  protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
2546  if ( $lowTTL <= 0 ) {
2547  return false;
2548  } elseif ( $curTTL >= $lowTTL ) {
2549  return false;
2550  } elseif ( $curTTL <= 0 ) {
2551  return false;
2552  }
2553 
2554  $chance = ( 1 - $curTTL / $lowTTL );
2555 
2556  // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
2557  $decision = ( mt_rand( 1, 1e9 ) <= 1e9 * $chance );
2558 
2559  $this->logger->debug(
2560  "worthRefreshExpiring($curTTL, $lowTTL): " .
2561  "p = $chance; refresh = " . ( $decision ? 'Y' : 'N' )
2562  );
2563 
2564  return $decision;
2565  }
2566 
2582  protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
2583  if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
2584  return false;
2585  }
2586 
2587  $age = $now - $asOf;
2588  $timeOld = $age - $ageNew;
2589  if ( $timeOld <= 0 ) {
2590  return false;
2591  }
2592 
2593  $popularHitsPerSec = 1;
2594  // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
2595  // Note that the "expected # of refreshes" for the ramp-up time range is half
2596  // of what it would be if P(refresh) was at its full value during that time range.
2597  $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::$RAMPUP_TTL / 2, 1 );
2598  // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
2599  // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
2600  // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
2601  $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
2602  // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
2603  $chance *= ( $timeOld <= self::$RAMPUP_TTL ) ? $timeOld / self::$RAMPUP_TTL : 1;
2604 
2605  // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
2606  $decision = ( mt_rand( 1, 1e9 ) <= 1e9 * $chance );
2607 
2608  $this->logger->debug(
2609  "worthRefreshPopular($asOf, $ageNew, $timeTillRefresh, $now): " .
2610  "p = $chance; refresh = " . ( $decision ? 'Y' : 'N' )
2611  );
2612 
2613  return $decision;
2614  }
2615 
2625  protected function isValid( $value, $asOf, $minAsOf, $purgeTime = null ) {
2626  // Avoid reading any key not generated after the latest delete() or touch
2627  $safeMinAsOf = max( $minAsOf, $purgeTime + self::$TINY_POSTIVE );
2628 
2629  if ( $value === false ) {
2630  return false;
2631  } elseif ( $safeMinAsOf > 0 && $asOf < $minAsOf ) {
2632  return false;
2633  }
2634 
2635  return true;
2636  }
2637 
2646  private function wrap( $value, $ttl, $version, $now, $walltime ) {
2647  // Returns keys in ascending integer order for PHP7 array packing:
2648  // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2649  $wrapped = [
2650  self::$FLD_FORMAT_VERSION => self::$VERSION,
2651  self::$FLD_VALUE => $value,
2652  self::$FLD_TTL => $ttl,
2653  self::$FLD_TIME => $now
2654  ];
2655  if ( $version !== null ) {
2656  $wrapped[self::$FLD_VALUE_VERSION] = $version;
2657  }
2658  if ( $walltime >= self::$GENERATION_SLOW_SEC ) {
2659  $wrapped[self::$FLD_GENERATION_TIME] = $walltime;
2660  }
2661 
2662  return $wrapped;
2663  }
2664 
2676  private function unwrap( $wrapped, $now ) {
2677  $value = false;
2678  $info = [ 'asOf' => null, 'curTTL' => null, 'version' => null, 'tombAsOf' => null ];
2679 
2680  if ( is_array( $wrapped ) ) {
2681  // Entry expected to be a cached value; validate it
2682  if (
2683  ( $wrapped[self::$FLD_FORMAT_VERSION] ?? null ) === self::$VERSION &&
2684  $wrapped[self::$FLD_TIME] >= $this->epoch
2685  ) {
2686  if ( $wrapped[self::$FLD_TTL] > 0 ) {
2687  // Get the approximate time left on the key
2688  $age = $now - $wrapped[self::$FLD_TIME];
2689  $curTTL = max( $wrapped[self::$FLD_TTL] - $age, 0.0 );
2690  } else {
2691  // Key had no TTL, so the time left is unbounded
2692  $curTTL = INF;
2693  }
2694  $value = $wrapped[self::$FLD_VALUE];
2695  $info['version'] = $wrapped[self::$FLD_VALUE_VERSION] ?? null;
2696  $info['asOf'] = $wrapped[self::$FLD_TIME];
2697  $info['curTTL'] = $curTTL;
2698  }
2699  } else {
2700  // Entry expected to be a tombstone; parse it
2701  $purge = $this->parsePurgeValue( $wrapped );
2702  if ( $purge !== false ) {
2703  // Tombstoned keys should always have a negative current $ttl
2704  $info['curTTL'] = min( $purge[self::$PURGE_TIME] - $now, self::$TINY_NEGATIVE );
2705  $info['tombAsOf'] = $purge[self::$PURGE_TIME];
2706  }
2707  }
2708 
2709  return [ $value, $info ];
2710  }
2711 
2716  private function determineKeyClassForStats( $key ) {
2717  $parts = explode( ':', $key, 3 );
2718  // Sanity fallback in case the key was not made by makeKey.
2719  // Replace dots because they are special in StatsD (T232907)
2720  return strtr( $parts[1] ?? $parts[0], '.', '_' );
2721  }
2722 
2728  private function parsePurgeValue( $value ) {
2729  if ( !is_string( $value ) ) {
2730  return false;
2731  }
2732 
2733  $segments = explode( ':', $value, 3 );
2734  if (
2735  !isset( $segments[0] ) ||
2736  !isset( $segments[1] ) ||
2737  "{$segments[0]}:" !== self::$PURGE_VAL_PREFIX
2738  ) {
2739  return false;
2740  }
2741 
2742  if ( !isset( $segments[2] ) ) {
2743  // Back-compat with old purge values without holdoff
2744  $segments[2] = self::HOLDOFF_TTL;
2745  }
2746 
2747  if ( $segments[1] < $this->epoch ) {
2748  // Values this old are ignored
2749  return false;
2750  }
2751 
2752  return [
2753  self::$PURGE_TIME => (float)$segments[1],
2754  self::$PURGE_HOLDOFF => (int)$segments[2],
2755  ];
2756  }
2757 
2763  private function makePurgeValue( $timestamp, $holdoff ) {
2764  return self::$PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
2765  }
2766 
2771  private function getProcessCache( $group ) {
2772  if ( !isset( $this->processCaches[$group] ) ) {
2773  list( , $size ) = explode( ':', $group );
2774  $this->processCaches[$group] = new MapCacheLRU( (int)$size );
2775  if ( $this->wallClockOverride !== null ) {
2776  $this->processCaches[$group]->setMockTime( $this->wallClockOverride );
2777  }
2778  }
2779 
2780  return $this->processCaches[$group];
2781  }
2782 
2788  private function getProcessCacheKey( $key, $version ) {
2789  return $key . ' ' . (int)$version;
2790  }
2791 
2797  private function getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
2798  $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
2799 
2800  $keysMissing = [];
2801  if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
2802  $version = $opts['version'] ?? null;
2803  $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
2804  foreach ( $keys as $key => $id ) {
2805  if ( !$pCache->has( $this->getProcessCacheKey( $key, $version ), $pcTTL ) ) {
2806  $keysMissing[$id] = $key;
2807  }
2808  }
2809  }
2810 
2811  return $keysMissing;
2812  }
2813 
2819  private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
2820  if ( !$keys ) {
2821  return [];
2822  }
2823 
2824  // Get all the value keys to fetch...
2825  $keysWarmup = $this->makeSisterKeys( $keys, self::$TYPE_VALUE );
2826  // Get all the check keys to fetch...
2827  foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
2828  // Note: avoid array_merge() inside loop in case there are many keys
2829  if ( is_int( $i ) ) {
2830  // Single check key that applies to all value keys
2831  $keysWarmup[] = $this->makeSisterKey( $checkKeyOrKeyGroup, self::$TYPE_TIMESTAMP );
2832  } else {
2833  // List of check keys that apply to a specific value key
2834  foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
2835  $keysWarmup[] = $this->makeSisterKey( $checkKey, self::$TYPE_TIMESTAMP );
2836  }
2837  }
2838  }
2839 
2840  $warmupCache = $this->cache->getMulti( $keysWarmup );
2841  $warmupCache += array_fill_keys( $keysWarmup, false );
2842 
2843  return $warmupCache;
2844  }
2845 
2850  protected function getCurrentTime() {
2851  if ( $this->wallClockOverride ) {
2852  return $this->wallClockOverride;
2853  }
2854 
2855  $clockTime = (float)time(); // call this first
2856  // microtime() uses an initial gettimeofday() call added to usage clocks.
2857  // This can severely drift from time() and the microtime() value of other threads
2858  // due to undercounting of the amount of time elapsed. Instead of seeing the current
2859  // time as being in the past, use the value of time(). This avoids setting cache values
2860  // that will immediately be seen as expired and possibly cause stampedes.
2861  return max( microtime( true ), $clockTime );
2862  }
2863 
2868  public function setMockTime( &$time ) {
2869  $this->wallClockOverride =& $time;
2870  $this->cache->setMockTime( $time );
2871  foreach ( $this->processCaches as $pCache ) {
2872  $pCache->setMockTime( $time );
2873  }
2874  }
2875 }
WANObjectCache\$RAMPUP_TTL
static int $RAMPUP_TTL
Seconds to ramp up the chance of regeneration due to expected time-till-refresh.
Definition: WANObjectCache.php:222
WANObjectCache\getWithSetCallback
getWithSetCallback( $key, $ttl, $callback, array $opts=[], array $cbParams=[])
Method to fetch/regenerate a cache key.
Definition: WANObjectCache.php:1356
WANObjectCache\getQoS
getQoS( $flag)
Definition: WANObjectCache.php:2335
WANObjectCache\$VERSION
static int $VERSION
Cache format version number.
Definition: WANObjectCache.php:243
WANObjectCache\hash256
hash256( $component)
Hash a possibly long string into a suitable component for makeKey()/makeGlobalKey()
Definition: WANObjectCache.php:2149
WANObjectCache\determineKeyClassForStats
determineKeyClassForStats( $key)
Definition: WANObjectCache.php:2716
WANObjectCache\relayDelete
relayDelete( $key)
Do the actual async bus delete of a key.
Definition: WANObjectCache.php:2461
WANObjectCache\setMockTime
setMockTime(&$time)
Definition: WANObjectCache.php:2868
WANObjectCache\makePurgeValue
makePurgeValue( $timestamp, $holdoff)
Definition: WANObjectCache.php:2763
WANObjectCache\$TYPE_INTERIM
static string $TYPE_INTERIM
Single character interium key component.
Definition: WANObjectCache.php:267
WANObjectCache\$INTERIM_KEY_TTL
static int $INTERIM_KEY_TTL
Seconds to keep interim value keys for tombstoned keys around.
Definition: WANObjectCache.php:215
WANObjectCache\$warmupCache
mixed[] $warmupCache
Temporary warm-up cache.
Definition: WANObjectCache.php:162
WANObjectCache\makeGlobalKey
makeGlobalKey( $class,... $components)
Definition: WANObjectCache.php:2138
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
WANObjectCache\$cache
BagOStuff $cache
The local datacenter cache.
Definition: WANObjectCache.php:127
WANObjectCache\fetchOrRegenerate
fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams)
Do the actual I/O for getWithSetCallback() when needed.
Definition: WANObjectCache.php:1416
WANObjectCache\isValid
isValid( $value, $asOf, $minAsOf, $purgeTime=null)
Check if $value is not false, versioned (if needed), and not older than $minTime (if set)
Definition: WANObjectCache.php:2625
WANObjectCache\$TYPE_COOLOFF
static string $TYPE_COOLOFF
Single character cool-off key component.
Definition: WANObjectCache.php:269
true
return true
Definition: router.php:90
WANObjectCache\$TYPE_VALUE
static string $TYPE_VALUE
Single character value mutex key component.
Definition: WANObjectCache.php:261
Wikimedia\LightweightObjectStore\ExpirationAwareness
Generic interface providing Time-To-Live constants for expirable object storage.
Definition: ExpirationAwareness.php:32
WANObjectCache\$FLD_GENERATION_TIME
static int $FLD_GENERATION_TIME
Key to how long it took to generate the value.
Definition: WANObjectCache.php:258
NullStatsdDataFactory
Definition: NullStatsdDataFactory.php:10
WANObjectCache\getMultiWithSetCallback
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
Definition: WANObjectCache.php:1882
WANObjectCache\$TINY_NEGATIVE
static float $TINY_NEGATIVE
Tiny negative float to use when CTL comes up >= 0 due to clock skew.
Definition: WANObjectCache.php:225
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:71
WANObjectCache\getMultiWithUnionSetCallback
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
Definition: WANObjectCache.php:1981
WANObjectCache\resolveTouched
resolveTouched( $value, $lastPurge, $touchedCallback)
Definition: WANObjectCache.php:1761
WANObjectCache\getMulti
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], &$info=null)
Fetch the value of several keys from cache.
Definition: WANObjectCache.php:446
WANObjectCache\makeKey
makeKey( $class,... $components)
Definition: WANObjectCache.php:2127
$res
$res
Definition: testCompression.php:57
WANObjectCache\reapCheckKey
reapCheckKey( $key, $purgeTimestamp, &$isStale=false)
Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:2097
WANObjectCache\getCheckKeyTime
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
Definition: WANObjectCache.php:868
WANObjectCache\resolveBusyValue
resolveBusyValue( $busyValue)
Definition: WANObjectCache.php:1814
WANObjectCache\$logger
LoggerInterface $logger
Definition: WANObjectCache.php:131
WANObjectCache\$FLD_VALUE
static int $FLD_VALUE
Key to the cached value.
Definition: WANObjectCache.php:248
WANObjectCache\$warmupKeyMisses
int $warmupKeyMisses
Key fetched.
Definition: WANObjectCache.php:164
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:348
WANObjectCache\$RECENT_SET_LOW_MS
static int $RECENT_SET_LOW_MS
Min millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL)
Definition: WANObjectCache.php:230
WANObjectCache\clearProcessCache
clearProcessCache()
Clear the in-process caches; useful for testing.
Definition: WANObjectCache.php:2302
WANObjectCache\getInterimValue
getInterimValue( $key, $minAsOf)
Definition: WANObjectCache.php:1772
WANObjectCache\$secret
string $secret
Stable secret used for hasing long strings into key components.
Definition: WANObjectCache.php:148
WANObjectCache\clearLastError
clearLastError()
Clear the "last error" registry.
Definition: WANObjectCache.php:2293
WANObjectCache\getCurrentTime
getCurrentTime()
Definition: WANObjectCache.php:2850
WANObjectCache\$FLD_FORMAT_VERSION
static int $FLD_FORMAT_VERSION
Key to WAN cache version number.
Definition: WANObjectCache.php:246
WANObjectCache\unwrap
unwrap( $wrapped, $now)
Definition: WANObjectCache.php:2676
WANObjectCache\getLastError
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
Definition: WANObjectCache.php:2276
WANObjectCache\makeSisterKeys
makeSisterKeys(array $baseKeys, $type)
Get cache keys that should be collocated with their corresponding base keys.
Definition: WANObjectCache.php:1617
WANObjectCache\$TYPE_MUTEX
static string $TYPE_MUTEX
Single character mutex key component.
Definition: WANObjectCache.php:265
WANObjectCache\makeMultiKeys
makeMultiKeys(array $ids, $keyCallback)
Get an iterator of (cache key => entity ID) for a list of entity IDs.
Definition: WANObjectCache.php:2203
WANObjectCache\__construct
__construct(array $params)
Definition: WANObjectCache.php:310
WANObjectCache\useInterimHoldOffCaching
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
Definition: WANObjectCache.php:2326
WANObjectCache\reap
reap( $key, $purgeTimestamp, &$isStale=false)
Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:2065
WANObjectCache\$keyHighQps
int $keyHighQps
Reads/second assumed during a hypothetical cache write stampede for a key.
Definition: WANObjectCache.php:155
WANObjectCache\touchCheckKey
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:998
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:38
WANObjectCache\$mcrouterAware
bool $mcrouterAware
Whether to use mcrouter key prefixing for routing.
Definition: WANObjectCache.php:138
WANObjectCache\wrap
wrap( $value, $ttl, $version, $now, $walltime)
Definition: WANObjectCache.php:2646
WANObjectCache\adaptiveTTL
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
Definition: WANObjectCache.php:2402
WANObjectCache\getNonProcessCachedMultiKeys
getNonProcessCachedMultiKeys(ArrayIterator $keys, array $opts)
Definition: WANObjectCache.php:2797
WANObjectCache\$epoch
float $epoch
Unix timestamp of the oldest possible valid values.
Definition: WANObjectCache.php:146
WANObjectCache\relayPurge
relayPurge( $key, $ttl, $holdoff)
Do the actual async bus purge of a key.
Definition: WANObjectCache.php:2434
WANObjectCache\$wallClockOverride
float null $wallClockOverride
Definition: WANObjectCache.php:167
WANObjectCache\$CHECK_KEY_TTL
static int $CHECK_KEY_TTL
Seconds to keep dependency purge keys around.
Definition: WANObjectCache.php:213
Wikimedia\LightweightObjectStore\StorageAwareness
Generic interface providing error code and quality-of-service constants for object stores.
Definition: StorageAwareness.php:32
WANObjectCache\isVolatileValueAgeNegligible
isVolatileValueAgeNegligible( $age)
Definition: WANObjectCache.php:1658
WANObjectCache\getMultiCheckKeyTime
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
Definition: WANObjectCache.php:933
WANObjectCache\$COOLOFF_TTL
static int $COOLOFF_TTL
Seconds to no-op key set() calls to avoid large blob I/O stampedes.
Definition: WANObjectCache.php:220
WANObjectCache\worthRefreshPopular
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
Definition: WANObjectCache.php:2582
WANObjectCache\$LOCK_TTL
static int $LOCK_TTL
Seconds to keep lock keys around.
Definition: WANObjectCache.php:218
WANObjectCache\isAliveOrInGracePeriod
isAliveOrInGracePeriod( $curTTL, $graceTTL)
Check if a key is fresh or in the grace window and thus due for randomized reuse.
Definition: WANObjectCache.php:2515
WANObjectCache\$useInterimHoldOffCaching
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
Definition: WANObjectCache.php:144
WANObjectCache\$cluster
string $cluster
Cache cluster name for mcrouter use.
Definition: WANObjectCache.php:142
WANObjectCache\worthRefreshExpiring
worthRefreshExpiring( $curTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
Definition: WANObjectCache.php:2545
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:125
WANObjectCache\getProcessCache
getProcessCache( $group)
Definition: WANObjectCache.php:2771
WANObjectCache\$PURGE_VAL_PREFIX
static string $PURGE_VAL_PREFIX
Prefix for tombstone key values.
Definition: WANObjectCache.php:272
WANObjectCache\multiRemap
multiRemap(array $ids, array $res)
Get an (ID => value) map from (i) a non-unique list of entity IDs, and (ii) the list of corresponding...
Definition: WANObjectCache.php:2259
WANObjectCache\getRawKeysForWarmup
getRawKeysForWarmup(array $keys, array $checkKeys)
Definition: WANObjectCache.php:2819
WANObjectCache\$keyHighUplinkBps
float $keyHighUplinkBps
Max tolerable bytes/second to spend on a cache write stampede for a key.
Definition: WANObjectCache.php:157
WANObjectCache\checkAndSetCooloff
checkAndSetCooloff( $key, $kClass, $value, $elapsed, $hasLock)
Check whether set() is rate-limited to avoid concurrent I/O spikes.
Definition: WANObjectCache.php:1683
WANObjectCache\$FLD_FLAGS
static int $FLD_FLAGS
@noinspection PhpUnusedPrivateFieldInspection
Definition: WANObjectCache.php:254
WANObjectCache\resetCheckKey
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:1039
WANObjectCache\claimStampedeLock
claimStampedeLock( $key)
Definition: WANObjectCache.php:1585
WANObjectCache\parsePurgeValue
parsePurgeValue( $value)
Definition: WANObjectCache.php:2728
WANObjectCache\$RECENT_SET_HIGH_MS
static int $RECENT_SET_HIGH_MS
Max millisecond set() backoff during hold-off (far less than INTERIM_KEY_TTL)
Definition: WANObjectCache.php:232
WANObjectCache\getProcessCacheKey
getProcessCacheKey( $key, $version)
Definition: WANObjectCache.php:2788
WANObjectCache\$TYPE_TIMESTAMP
static string $TYPE_TIMESTAMP
Single character timestamp key component.
Definition: WANObjectCache.php:263
WANObjectCache\yieldStampedeLock
yieldStampedeLock( $key, $hasLock)
Definition: WANObjectCache.php:1598
WANObjectCache\$TINY_POSTIVE
static float $TINY_POSTIVE
Tiny positive float to use when using "minTime" to assert an inequality.
Definition: WANObjectCache.php:227
WANObjectCache\$asyncHandler
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
Definition: WANObjectCache.php:135
WANObjectCache\getWarmupKeyMisses
getWarmupKeyMisses()
Definition: WANObjectCache.php:2420
$keys
$keys
Definition: testCompression.php:72
WANObjectCache\$GENERATION_SLOW_SEC
static int $GENERATION_SLOW_SEC
Consider value generation slow if it takes more than this many seconds.
Definition: WANObjectCache.php:235
WANObjectCache\makeSisterKey
makeSisterKey( $baseKey, $typeChar)
Get a cache key that should be collocated with a base key.
Definition: WANObjectCache.php:1633
WANObjectCache\resolveCTL
resolveCTL( $value, $curTTL, $curInfo, $touchedCallback)
Definition: WANObjectCache.php:1741
WANObjectCache\$PURGE_HOLDOFF
static int $PURGE_HOLDOFF
Key to the tombstone entry hold-off TTL.
Definition: WANObjectCache.php:240
WANObjectCache\$callbackDepth
int $callbackDepth
Callback stack depth for getWithSetCallback()
Definition: WANObjectCache.php:160
WANObjectCache\$coalesceScheme
int $coalesceScheme
Scheme to use for key coalescing (Hash Tags or Hash Stops)
Definition: WANObjectCache.php:152
IStoreKeyEncoder
Generic interface for object stores with key encoding methods.
Definition: IStoreKeyEncoder.php:9
WANObjectCache\setLogger
setLogger(LoggerInterface $logger)
Definition: WANObjectCache.php:339
WANObjectCache\$FLD_TTL
static int $FLD_TTL
Key to the original TTL.
Definition: WANObjectCache.php:250
WANObjectCache\$coalesceKeys
string bool $coalesceKeys
Whether "sister" keys should be coalesced to the same cache server.
Definition: WANObjectCache.php:150
WANObjectCache\$FLD_VALUE_VERSION
static int $FLD_VALUE_VERSION
Key to collection cache version number.
Definition: WANObjectCache.php:256
WANObjectCache\$FLD_TIME
static int $FLD_TIME
Key to the cache timestamp.
Definition: WANObjectCache.php:252
WANObjectCache\$PURGE_TIME
static int $PURGE_TIME
Key to the tombstone entry timestamp.
Definition: WANObjectCache.php:238
WANObjectCache\$stats
StatsdDataFactoryInterface $stats
Definition: WANObjectCache.php:133
WANObjectCache\setInterimValue
setInterimValue( $key, $value, $ttl, $version, $walltime)
Definition: WANObjectCache.php:1796
WANObjectCache\$processCaches
MapCacheLRU[] $processCaches
Map of group PHP instance caches.
Definition: WANObjectCache.php:129
WANObjectCache\scheduleAsyncRefresh
scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams)
Schedule a deferred cache regeneration if possible.
Definition: WANObjectCache.php:2485
WANObjectCache\processCheckKeys
processCheckKeys(array $timeKeys, array $wrappedValues, $now)
Definition: WANObjectCache.php:557
$type
$type
Definition: testCompression.php:52
WANObjectCache\$region
string $region
Physical region for mcrouter use.
Definition: WANObjectCache.php:140